# $Id: __init__.py 8260 2019-07-18 16:12:29Z grubert $
# Author: Dave Kuhlman <dkuhlman@davekuhlman.org>
# Copyright: This module has been placed in the public domain.

"""
Open Document Format (ODF) Writer.

"""

__docformat__ = 'reStructuredText'


import sys
import os
import os.path
import tempfile
import zipfile
from xml.dom import minidom
import time
import re
import copy
import itertools
import docutils
try:
    import locale   # module missing in Jython
except ImportError:
    pass
from docutils import frontend, nodes, utils, writers, languages
from docutils.readers import standalone
from docutils.transforms import references
if type(sys.version_info)!=type((0,)) and sys.version_info.major >= 3:
    from io import StringIO
    from urllib.request import urlopen
    from urllib.error import HTTPError
else:
    from io import StringIO
    from urllib.request import urlopen
    from urllib.error import HTTPError


VERSION = '1.0a'

IMAGE_NAME_COUNTER = itertools.count()
WhichElementTree = ''
try:
    # 1. Try to use lxml.
    #from lxml import etree
    #WhichElementTree = 'lxml'
    raise ImportError('Ignoring lxml')
except ImportError:
    try:
        # 2. Try to use ElementTree from the Python standard library.
        from xml.etree import ElementTree as etree
        WhichElementTree = 'elementtree'
    except ImportError:
        try:
            # 3. Try to use a version of ElementTree installed as a separate
            #    product.
            from elementtree import ElementTree as etree
            WhichElementTree = 'elementtree'
        except ImportError:
            s1 = 'Must install either a version of Python containing ' \
                 'ElementTree (Python version >=2.5) or install ElementTree.'
            raise ImportError(s1)

#
# Import pygments and odtwriter pygments formatters if possible.
try:
    import pygments
    import pygments.lexers
    if sys.version_info.major >= 3:
        from .pygmentsformatter import OdtPygmentsProgFormatter, \
            OdtPygmentsLaTeXFormatter
    else:
        from .pygmentsformatter import OdtPygmentsProgFormatter, \
            OdtPygmentsLaTeXFormatter
except (ImportError, SyntaxError):
    pygments = None

# check for the Python Imaging Library
try:
    import PIL.Image
except ImportError:
    try:  # sometimes PIL modules are put in PYTHONPATH's root
        import Image

        class PIL(object):
            pass  # dummy wrapper
        PIL.Image = Image
    except ImportError:
        PIL = None

## import warnings
## warnings.warn('importing IPShellEmbed', UserWarning)
## from IPython.Shell import IPShellEmbed
## args = ['-pdb', '-pi1', 'In <\\#>: ', '-pi2', '   .\\D.: ',
##         '-po', 'Out<\\#>: ', '-nosep']
## ipshell = IPShellEmbed(args,
##                        banner = 'Entering IPython.  Press Ctrl-D to exit.',
##                        exit_msg = 'Leaving Interpreter, back to program.')


#
# ElementTree does not support getparent method (lxml does).
# This wrapper class and the following support functions provide
#   that support for the ability to get the parent of an element.
#
if WhichElementTree == 'elementtree':
    import weakref
    _parents = weakref.WeakKeyDictionary()
    if isinstance(etree.Element, type):
        _ElementInterface = etree.Element
    else:
        _ElementInterface = etree._ElementInterface

    class _ElementInterfaceWrapper(_ElementInterface):
        def __init__(self, tag, attrib=None):
            _ElementInterface.__init__(self, tag, attrib)
            _parents[self] = None

        def setparent(self, parent):
            _parents[self] = parent

        def getparent(self):
            return _parents[self]


#
# Constants and globals

SPACES_PATTERN = re.compile(r'( +)')
TABS_PATTERN = re.compile(r'(\t+)')
FILL_PAT1 = re.compile(r'^ +')
FILL_PAT2 = re.compile(r' {2,}')

TABLESTYLEPREFIX = 'rststyle-table-'
TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX
TABLEPROPERTYNAMES = (
    'border', 'border-top', 'border-left',
    'border-right', 'border-bottom', )

GENERATOR_DESC = 'Docutils.org/odf_odt'

NAME_SPACE_1 = 'urn:oasis:names:tc:opendocument:xmlns:office:1.0'

CONTENT_NAMESPACE_DICT = CNSD = {
    #'office:version': '1.0',
    'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
    'dc': 'http://purl.org/dc/elements/1.1/',
    'dom': 'http://www.w3.org/2001/xml-events',
    'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
    'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
    'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
    'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
    'math': 'http://www.w3.org/1998/Math/MathML',
    'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
    'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
    'office': NAME_SPACE_1,
    'ooo': 'http://openoffice.org/2004/office',
    'oooc': 'http://openoffice.org/2004/calc',
    'ooow': 'http://openoffice.org/2004/writer',
    'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',

    'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
    'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
    'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
    'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
    'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
    'xforms': 'http://www.w3.org/2002/xforms',
    'xlink': 'http://www.w3.org/1999/xlink',
    'xsd': 'http://www.w3.org/2001/XMLSchema',
    'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
}

STYLES_NAMESPACE_DICT = SNSD = {
    #'office:version': '1.0',
    'chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
    'dc': 'http://purl.org/dc/elements/1.1/',
    'dom': 'http://www.w3.org/2001/xml-events',
    'dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
    'draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
    'fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
    'form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
    'math': 'http://www.w3.org/1998/Math/MathML',
    'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
    'number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
    'office': NAME_SPACE_1,
    'presentation': 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
    'ooo': 'http://openoffice.org/2004/office',
    'oooc': 'http://openoffice.org/2004/calc',
    'ooow': 'http://openoffice.org/2004/writer',
    'script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
    'style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
    'svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
    'table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
    'text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
    'xlink': 'http://www.w3.org/1999/xlink',
}

MANIFEST_NAMESPACE_DICT = MANNSD = {
    'manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
}

META_NAMESPACE_DICT = METNSD = {
    #'office:version': '1.0',
    'dc': 'http://purl.org/dc/elements/1.1/',
    'meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
    'office': NAME_SPACE_1,
    'ooo': 'http://openoffice.org/2004/office',
    'xlink': 'http://www.w3.org/1999/xlink',
}

#
# Attribute dictionaries for use with ElementTree (not lxml), which
#   does not support use of nsmap parameter on Element() and SubElement().

CONTENT_NAMESPACE_ATTRIB = {
    #'office:version': '1.0',
    'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
    'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
    'xmlns:dom': 'http://www.w3.org/2001/xml-events',
    'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
    'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
    'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
    'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
    'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
    'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
    'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
    'xmlns:office': NAME_SPACE_1,
    'xmlns:presentation':
        'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
    'xmlns:ooo': 'http://openoffice.org/2004/office',
    'xmlns:oooc': 'http://openoffice.org/2004/calc',
    'xmlns:ooow': 'http://openoffice.org/2004/writer',
    'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
    'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
    'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
    'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
    'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
    'xmlns:xforms': 'http://www.w3.org/2002/xforms',
    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
    'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema',
    'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
}

STYLES_NAMESPACE_ATTRIB = {
    #'office:version': '1.0',
    'xmlns:chart': 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
    'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
    'xmlns:dom': 'http://www.w3.org/2001/xml-events',
    'xmlns:dr3d': 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
    'xmlns:draw': 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
    'xmlns:fo': 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
    'xmlns:form': 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
    'xmlns:math': 'http://www.w3.org/1998/Math/MathML',
    'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
    'xmlns:number': 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
    'xmlns:office': NAME_SPACE_1,
    'xmlns:presentation':
        'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
    'xmlns:ooo': 'http://openoffice.org/2004/office',
    'xmlns:oooc': 'http://openoffice.org/2004/calc',
    'xmlns:ooow': 'http://openoffice.org/2004/writer',
    'xmlns:script': 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
    'xmlns:style': 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
    'xmlns:svg': 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
    'xmlns:table': 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
    'xmlns:text': 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
}

MANIFEST_NAMESPACE_ATTRIB = {
    'xmlns:manifest': 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0',
}

META_NAMESPACE_ATTRIB = {
    #'office:version': '1.0',
    'xmlns:dc': 'http://purl.org/dc/elements/1.1/',
    'xmlns:meta': 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
    'xmlns:office': NAME_SPACE_1,
    'xmlns:ooo': 'http://openoffice.org/2004/office',
    'xmlns:xlink': 'http://www.w3.org/1999/xlink',
}


#
# Functions
#

#
# ElementTree support functions.
# In order to be able to get the parent of elements, must use these
#   instead of the functions with same name provided by ElementTree.
#
def Element(tag, attrib=None, nsmap=None, nsdict=CNSD):
    if attrib is None:
        attrib = {}
    tag, attrib = fix_ns(tag, attrib, nsdict)
    if WhichElementTree == 'lxml':
        el = etree.Element(tag, attrib, nsmap=nsmap)
    else:
        el = _ElementInterfaceWrapper(tag, attrib)
    return el


def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
    if attrib is None:
        attrib = {}
    tag, attrib = fix_ns(tag, attrib, nsdict)
    if WhichElementTree == 'lxml':
        el = etree.SubElement(parent, tag, attrib, nsmap=nsmap)
    else:
        el = _ElementInterfaceWrapper(tag, attrib)
        parent.append(el)
        el.setparent(parent)
    return el


def fix_ns(tag, attrib, nsdict):
    nstag = add_ns(tag, nsdict)
    nsattrib = {}
    for key, val in list(attrib.items()):
        nskey = add_ns(key, nsdict)
        nsattrib[nskey] = val
    return nstag, nsattrib


def add_ns(tag, nsdict=CNSD):
    if WhichElementTree == 'lxml':
        nstag, name = tag.split(':')
        ns = nsdict.get(nstag)
        if ns is None:
            raise RuntimeError('Invalid namespace prefix: %s' % nstag)
        tag = '{%s}%s' % (ns, name,)
    return tag


def ToString(et):
    outstream = StringIO()
    if sys.version_info >= (3, 2):
        et.write(outstream, encoding="unicode")
    else:
        et.write(outstream)
    s1 = outstream.getvalue()
    outstream.close()
    return s1


def escape_cdata(text):
    text = text.replace("&", "&amp;")
    text = text.replace("<", "&lt;")
    text = text.replace(">", "&gt;")
    ascii = ''
    for char in text:
        if ord(char) >= ord("\x7f"):
            ascii += "&#x%X;" % (ord(char), )
        else:
            ascii += char
    return ascii


WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*')


def split_words(line):
    # We need whitespace at the end of the string for our regexpr.
    line += ' '
    words = []
    pos1 = 0
    mo = WORD_SPLIT_PAT1.search(line, pos1)
    while mo is not None:
        word = mo.groups()[0]
        words.append(word)
        pos1 = mo.end()
        mo = WORD_SPLIT_PAT1.search(line, pos1)
    return words


#
# Classes
#


class TableStyle(object):
    def __init__(self, border=None, backgroundcolor=None):
        self.border = border
        self.backgroundcolor = backgroundcolor

    def get_border_(self):
        return self.border_

    def set_border_(self, border):
        self.border_ = border

    border = property(get_border_, set_border_)

    def get_backgroundcolor_(self):
        return self.backgroundcolor_

    def set_backgroundcolor_(self, backgroundcolor):
        self.backgroundcolor_ = backgroundcolor
    backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_)


BUILTIN_DEFAULT_TABLE_STYLE = TableStyle(
    border='0.0007in solid #000000')


#
# Information about the indentation level for lists nested inside
#   other contexts, e.g. dictionary lists.
class ListLevel(object):
    def __init__(self, level, sibling_level=True, nested_level=True):
        self.level = level
        self.sibling_level = sibling_level
        self.nested_level = nested_level

    def set_sibling(self, sibling_level):
        self.sibling_level = sibling_level

    def get_sibling(self):
        return self.sibling_level

    def set_nested(self, nested_level):
        self.nested_level = nested_level

    def get_nested(self):
        return self.nested_level

    def set_level(self, level):
        self.level = level

    def get_level(self):
        return self.level


class Writer(writers.Writer):

    MIME_TYPE = 'application/vnd.oasis.opendocument.text'
    EXTENSION = '.odt'

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

    default_stylesheet = 'styles' + EXTENSION

    default_stylesheet_path = utils.relative_path(
        os.path.join(os.getcwd(), 'dummy'),
        os.path.join(os.path.dirname(__file__), default_stylesheet))

    default_template = 'template.txt'

    default_template_path = utils.relative_path(
        os.path.join(os.getcwd(), 'dummy'),
        os.path.join(os.path.dirname(__file__), default_template))

    settings_spec = (
        'ODF-Specific Options',
        None,
        (
            ('Specify a stylesheet.  '
                'Default: "%s"' % default_stylesheet_path,
                ['--stylesheet'],
                {
                    'default': default_stylesheet_path,
                    'dest': 'stylesheet'
                }),
            ('Specify a configuration/mapping file relative to the '
                'current working '
                'directory for additional ODF options.  '
                'In particular, this file may contain a section named '
                '"Formats" that maps default style names to '
                'names to be used in the resulting output file allowing for '
                'adhering to external standards. '
                'For more info and the format of the '
                'configuration/mapping file, '
                'see the odtwriter doc.',
                ['--odf-config-file'],
                {'metavar': '<file>'}),
            ('Obfuscate email addresses to confuse harvesters while still '
                'keeping email links usable with '
                'standards-compliant browsers.',
                ['--cloak-email-addresses'],
                {'default': False,
                    'action': 'store_true',
                    'dest': 'cloak_email_addresses',
                    'validator': frontend.validate_boolean}),
            ('Do not obfuscate email addresses.',
                ['--no-cloak-email-addresses'],
                {'default': False,
                    'action': 'store_false',
                    'dest': 'cloak_email_addresses',
                    'validator': frontend.validate_boolean}),
            ('Specify the thickness of table borders in thousands of a cm.  '
                'Default is 35.',
                ['--table-border-thickness'],
                {'default': None,
                    'validator': frontend.validate_nonnegative_int}),
            ('Add syntax highlighting in literal code blocks.',
                ['--add-syntax-highlighting'],
                {'default': False,
                    'action': 'store_true',
                    'dest': 'add_syntax_highlighting',
                    'validator': frontend.validate_boolean}),
            ('Do not add syntax highlighting in '
             'literal code blocks. (default)',
                ['--no-syntax-highlighting'],
                {'default': False,
                    'action': 'store_false',
                    'dest': 'add_syntax_highlighting',
                    'validator': frontend.validate_boolean}),
            ('Create sections for headers.  (default)',
                ['--create-sections'],
                {'default': True,
                    'action': 'store_true',
                    'dest': 'create_sections',
                    'validator': frontend.validate_boolean}),
            ('Do not create sections for headers.',
                ['--no-sections'],
                {'default': True,
                    'action': 'store_false',
                    'dest': 'create_sections',
                    'validator': frontend.validate_boolean}),
            ('Create links.',
                ['--create-links'],
                {'default': False,
                    'action': 'store_true',
                    'dest': 'create_links',
                    'validator': frontend.validate_boolean}),
            ('Do not create links.  (default)',
                ['--no-links'],
                {'default': False,
                    'action': 'store_false',
                    'dest': 'create_links',
                    'validator': frontend.validate_boolean}),
            ('Generate endnotes at end of document, not footnotes '
                'at bottom of page.',
                ['--endnotes-end-doc'],
                {'default': False,
                    'action': 'store_true',
                    'dest': 'endnotes_end_doc',
                    'validator': frontend.validate_boolean}),
            ('Generate footnotes at bottom of page, not endnotes '
                'at end of document. (default)',
                ['--no-endnotes-end-doc'],
                {'default': False,
                    'action': 'store_false',
                    'dest': 'endnotes_end_doc',
                    'validator': frontend.validate_boolean}),
            ('Generate a bullet list table of contents, not '
                'an ODF/oowriter table of contents.',
                ['--generate-list-toc'],
                {'default': True,
                    'action': 'store_false',
                    'dest': 'generate_oowriter_toc',
                    'validator': frontend.validate_boolean}),
            ('Generate an ODF/oowriter table of contents, not '
                'a bullet list. (default)',
                ['--generate-oowriter-toc'],
                {'default': True,
                    'action': 'store_true',
                    'dest': 'generate_oowriter_toc',
                    'validator': frontend.validate_boolean}),
            ('Specify the contents of an custom header line.  '
                'See odf_odt writer documentation for details '
                'about special field character sequences.',
                ['--custom-odt-header'],
                {'default': '',
                    'dest': 'custom_header', }),
            ('Specify the contents of an custom footer line.  '
                'See odf_odt writer documentation for details '
                'about special field character sequences.',
                ['--custom-odt-footer'],
                {'default': '',
                    'dest': 'custom_footer', }),
        )
    )

    settings_defaults = {
        'output_encoding_error_handler': 'xmlcharrefreplace',
    }

    relative_path_settings = (
        'stylesheet_path',
    )

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

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

    def translate(self):
        self.settings = self.document.settings
        self.visitor = self.translator_class(self.document)
        self.visitor.retrieve_styles(self.EXTENSION)
        self.document.walkabout(self.visitor)
        self.visitor.add_doc_title()
        self.assemble_my_parts()
        self.output = self.parts['whole']

    def assemble_my_parts(self):
        """Assemble the `self.parts` dictionary.  Extend in subclasses.
        """
        writers.Writer.assemble_parts(self)
        f = tempfile.NamedTemporaryFile()
        zfile = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
        self.write_zip_str(
            zfile, 'mimetype', self.MIME_TYPE,
            compress_type=zipfile.ZIP_STORED)
        content = self.visitor.content_astext()
        self.write_zip_str(zfile, 'content.xml', content)
        s1 = self.create_manifest()
        self.write_zip_str(zfile, 'META-INF/manifest.xml', s1)
        s1 = self.create_meta()
        self.write_zip_str(zfile, 'meta.xml', s1)
        s1 = self.get_stylesheet()
        # Set default language in document to be generated.
        # Language is specified by the -l/--language command line option.
        # The format is described in BCP 47.  If region is omitted, we use
        # local.normalize(ll) to obtain a region.
        language_code = None
        region_code = None
        if self.visitor.language_code:
            language_ids = self.visitor.language_code.replace('_', '-')
            language_ids = language_ids.split('-')
            # first tag is primary language tag
            language_code = language_ids[0].lower()
            # 2-letter region subtag may follow in 2nd or 3rd position
            for subtag in language_ids[1:]:
                if len(subtag) == 2 and subtag.isalpha():
                    region_code = subtag.upper()
                    break
                elif len(subtag) == 1:
                    break   # 1-letter tag is never before valid region tag
            if region_code is None:
                try:
                    rcode = locale.normalize(language_code)
                except NameError:
                    rcode = language_code
                rcode = rcode.split('_')
                if len(rcode) > 1:
                    rcode = rcode[1].split('.')
                    region_code = rcode[0]
                if region_code is None:
                    self.document.reporter.warning(
                        'invalid language-region.\n'
                        '  Could not find region with locale.normalize().\n'
                        '  Please specify both language and region (ll-RR).\n'
                        '  Examples: es-MX (Spanish, Mexico),\n'
                        '  en-AU (English, Australia).')
        # Update the style ElementTree with the language and region.
        # Note that we keep a reference to the modified node because
        # it is possible that ElementTree will throw away the Python
        # representation of the updated node if we do not.
        updated, new_dom_styles, updated_node = self.update_stylesheet(
            self.visitor.get_dom_stylesheet(), language_code, region_code)
        if updated:
            s1 = etree.tostring(new_dom_styles)
        self.write_zip_str(zfile, 'styles.xml', s1)
        self.store_embedded_files(zfile)
        self.copy_from_stylesheet(zfile)
        zfile.close()
        f.seek(0)
        whole = f.read()
        f.close()
        self.parts['whole'] = whole
        self.parts['encoding'] = self.document.settings.output_encoding
        self.parts['version'] = docutils.__version__

    def update_stylesheet(self, stylesheet_root, language_code, region_code):
        """Update xml style sheet element with language and region/country."""
        updated = False
        modified_nodes = set()
        if language_code is not None or region_code is not None:
            n1 = stylesheet_root.find(
                '{urn:oasis:names:tc:opendocument:xmlns:office:1.0}'
                'styles')
            if n1 is None:
                raise RuntimeError(
                    "Cannot find 'styles' element in styles.odt/styles.xml")
            n2_nodes = n1.findall(
                '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
                'default-style')
            if not n2_nodes:
                raise RuntimeError(
                    "Cannot find 'default-style' "
                    "element in styles.xml")
            for node in n2_nodes:
                family = node.attrib.get(
                    '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
                    'family')
                if family == 'paragraph' or family == 'graphic':
                    n3 = node.find(
                        '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
                        'text-properties')
                    if n3 is None:
                        raise RuntimeError(
                            "Cannot find 'text-properties' "
                            "element in styles.xml")
                    if language_code is not None:
                        n3.attrib[
                            '{urn:oasis:names:tc:opendocument:xmlns:'
                            'xsl-fo-compatible:1.0}language'] = language_code
                        n3.attrib[
                            '{urn:oasis:names:tc:opendocument:xmlns:'
                            'style:1.0}language-complex'] = language_code
                        updated = True
                        modified_nodes.add(n3)
                    if region_code is not None:
                        n3.attrib[
                            '{urn:oasis:names:tc:opendocument:xmlns:'
                            'xsl-fo-compatible:1.0}country'] = region_code
                        n3.attrib[
                            '{urn:oasis:names:tc:opendocument:xmlns:'
                            'style:1.0}country-complex'] = region_code
                        updated = True
                        modified_nodes.add(n3)
        return updated, stylesheet_root, modified_nodes

    def write_zip_str(
            self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED):
        localtime = time.localtime(time.time())
        zinfo = zipfile.ZipInfo(name, localtime)
        # Add some standard UNIX file access permissions (-rw-r--r--).
        zinfo.external_attr = (0x81a4 & 0xFFFF) << 16
        zinfo.compress_type = compress_type
        zfile.writestr(zinfo, bytes)

    def store_embedded_files(self, zfile):
        embedded_files = self.visitor.get_embedded_file_list()
        for source, destination in embedded_files:
            if source is None:
                continue
            try:
                zfile.write(source, destination)
            except OSError:
                self.document.reporter.warning(
                    "Can't open file %s." % (source, ))

    def get_settings(self):
        """
        modeled after get_stylesheet
        """
        stylespath = self.settings.stylesheet
        zfile = zipfile.ZipFile(stylespath, 'r')
        s1 = zfile.read('settings.xml')
        zfile.close()
        return s1

    def get_stylesheet(self):
        """Get the stylesheet from the visitor.
        Ask the visitor to setup the page.
        """
        s1 = self.visitor.setup_page()
        return s1

    def copy_from_stylesheet(self, outzipfile):
        """Copy images, settings, etc from the stylesheet doc into target doc.
        """
        stylespath = self.settings.stylesheet
        inzipfile = zipfile.ZipFile(stylespath, 'r')
        # Copy the styles.
        s1 = inzipfile.read('settings.xml')
        self.write_zip_str(outzipfile, 'settings.xml', s1)
        # Copy the images.
        namelist = inzipfile.namelist()
        for name in namelist:
            if name.startswith('Pictures/'):
                imageobj = inzipfile.read(name)
                outzipfile.writestr(name, imageobj)
        inzipfile.close()

    def assemble_parts(self):
        pass

    def create_manifest(self):
        if WhichElementTree == 'lxml':
            root = Element(
                'manifest:manifest',
                nsmap=MANIFEST_NAMESPACE_DICT,
                nsdict=MANIFEST_NAMESPACE_DICT,
            )
        else:
            root = Element(
                'manifest:manifest',
                attrib=MANIFEST_NAMESPACE_ATTRIB,
                nsdict=MANIFEST_NAMESPACE_DICT,
            )
        doc = etree.ElementTree(root)
        SubElement(root, 'manifest:file-entry', attrib={
            'manifest:media-type': self.MIME_TYPE,
            'manifest:full-path': '/',
        }, nsdict=MANNSD)
        SubElement(root, 'manifest:file-entry', attrib={
            'manifest:media-type': 'text/xml',
            'manifest:full-path': 'content.xml',
        }, nsdict=MANNSD)
        SubElement(root, 'manifest:file-entry', attrib={
            'manifest:media-type': 'text/xml',
            'manifest:full-path': 'styles.xml',
        }, nsdict=MANNSD)
        SubElement(root, 'manifest:file-entry', attrib={
            'manifest:media-type': 'text/xml',
            'manifest:full-path': 'settings.xml',
        }, nsdict=MANNSD)
        SubElement(root, 'manifest:file-entry', attrib={
            'manifest:media-type': 'text/xml',
            'manifest:full-path': 'meta.xml',
        }, nsdict=MANNSD)
        s1 = ToString(doc)
        doc = minidom.parseString(s1)
        s1 = doc.toprettyxml('  ')
        return s1

    def create_meta(self):
        if WhichElementTree == 'lxml':
            root = Element(
                'office:document-meta',
                nsmap=META_NAMESPACE_DICT,
                nsdict=META_NAMESPACE_DICT,
            )
        else:
            root = Element(
                'office:document-meta',
                attrib=META_NAMESPACE_ATTRIB,
                nsdict=META_NAMESPACE_DICT,
            )
        doc = etree.ElementTree(root)
        root = SubElement(root, 'office:meta', nsdict=METNSD)
        el1 = SubElement(root, 'meta:generator', nsdict=METNSD)
        el1.text = 'Docutils/rst2odf.py/%s' % (VERSION, )
        s1 = os.environ.get('USER', '')
        el1 = SubElement(root, 'meta:initial-creator', nsdict=METNSD)
        el1.text = s1
        s2 = time.strftime('%Y-%m-%dT%H:%M:%S', time.localtime())
        el1 = SubElement(root, 'meta:creation-date', nsdict=METNSD)
        el1.text = s2
        el1 = SubElement(root, 'dc:creator', nsdict=METNSD)
        el1.text = s1
        el1 = SubElement(root, 'dc:date', nsdict=METNSD)
        el1.text = s2
        el1 = SubElement(root, 'dc:language', nsdict=METNSD)
        el1.text = 'en-US'
        el1 = SubElement(root, 'meta:editing-cycles', nsdict=METNSD)
        el1.text = '1'
        el1 = SubElement(root, 'meta:editing-duration', nsdict=METNSD)
        el1.text = 'PT00M01S'
        title = self.visitor.get_title()
        el1 = SubElement(root, 'dc:title', nsdict=METNSD)
        if title:
            el1.text = title
        else:
            el1.text = '[no title]'
        meta_dict = self.visitor.get_meta_dict()
        keywordstr = meta_dict.get('keywords')
        if keywordstr is not None:
            keywords = split_words(keywordstr)
            for keyword in keywords:
                el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
                el1.text = keyword
        description = meta_dict.get('description')
        if description is not None:
            el1 = SubElement(root, 'dc:description', nsdict=METNSD)
            el1.text = description
        s1 = ToString(doc)
        #doc = minidom.parseString(s1)
        #s1 = doc.toprettyxml('  ')
        return s1


# class ODFTranslator(nodes.SparseNodeVisitor):
class ODFTranslator(nodes.GenericNodeVisitor):

    used_styles = (
        'attribution', 'blockindent', 'blockquote', 'blockquote-bulletitem',
        'blockquote-bulletlist', 'blockquote-enumitem', 'blockquote-enumlist',
        'bulletitem', 'bulletlist',
        'caption', 'legend',
        'centeredtextbody', 'codeblock', 'codeblock-indented',
        'codeblock-classname', 'codeblock-comment', 'codeblock-functionname',
        'codeblock-keyword', 'codeblock-name', 'codeblock-number',
        'codeblock-operator', 'codeblock-string', 'emphasis', 'enumitem',
        'enumlist', 'epigraph', 'epigraph-bulletitem', 'epigraph-bulletlist',
        'epigraph-enumitem', 'epigraph-enumlist', 'footer',
        'footnote', 'citation',
        'header', 'highlights', 'highlights-bulletitem',
        'highlights-bulletlist', 'highlights-enumitem', 'highlights-enumlist',
        'horizontalline', 'inlineliteral', 'quotation', 'rubric',
        'strong', 'table-title', 'textbody', 'tocbulletlist', 'tocenumlist',
        'title',
        'subtitle',
        'heading1',
        'heading2',
        'heading3',
        'heading4',
        'heading5',
        'heading6',
        'heading7',
        'admon-attention-hdr',
        'admon-attention-body',
        'admon-caution-hdr',
        'admon-caution-body',
        'admon-danger-hdr',
        'admon-danger-body',
        'admon-error-hdr',
        'admon-error-body',
        'admon-generic-hdr',
        'admon-generic-body',
        'admon-hint-hdr',
        'admon-hint-body',
        'admon-important-hdr',
        'admon-important-body',
        'admon-note-hdr',
        'admon-note-body',
        'admon-tip-hdr',
        'admon-tip-body',
        'admon-warning-hdr',
        'admon-warning-body',
        'tableoption',
        'tableoption.%c', 'tableoption.%c%d', 'Table%d', 'Table%d.%c',
        'Table%d.%c%d',
        'lineblock1',
        'lineblock2',
        'lineblock3',
        'lineblock4',
        'lineblock5',
        'lineblock6',
        'image', 'figureframe',
    )

    def __init__(self, document):
        #nodes.SparseNodeVisitor.__init__(self, document)
        nodes.GenericNodeVisitor.__init__(self, document)
        self.settings = document.settings
        self.language_code = self.settings.language_code
        self.language = languages.get_language(
            self.language_code,
            document.reporter)
        self.format_map = {}
        if self.settings.odf_config_file:
            from configparser import ConfigParser

            parser = ConfigParser()
            parser.read(self.settings.odf_config_file)
            for rststyle, format in parser.items("Formats"):
                if rststyle not in self.used_styles:
                    self.document.reporter.warning(
                        'Style "%s" is not a style used by odtwriter.' % (
                            rststyle, ))
                if sys.version_info.major >= 3:
                    self.format_map[rststyle] = format
                else:
                    self.format_map[rststyle] = format.decode('utf-8')
        self.section_level = 0
        self.section_count = 0
        # Create ElementTree content and styles documents.
        if WhichElementTree == 'lxml':
            root = Element(
                'office:document-content',
                nsmap=CONTENT_NAMESPACE_DICT,
            )
        else:
            root = Element(
                'office:document-content',
                attrib=CONTENT_NAMESPACE_ATTRIB,
            )
        self.content_tree = etree.ElementTree(element=root)
        self.current_element = root
        SubElement(root, 'office:scripts')
        SubElement(root, 'office:font-face-decls')
        el = SubElement(root, 'office:automatic-styles')
        self.automatic_styles = el
        el = SubElement(root, 'office:body')
        el = self.generate_content_element(el)
        self.current_element = el
        self.body_text_element = el
        self.paragraph_style_stack = [self.rststyle('textbody'), ]
        self.list_style_stack = []
        self.table_count = 0
        self.column_count = ord('A') - 1
        self.trace_level = -1
        self.optiontablestyles_generated = False
        self.field_name = None
        self.field_element = None
        self.title = None
        self.image_count = 0
        self.image_style_count = 0
        self.image_dict = {}
        self.embedded_file_list = []
        self.syntaxhighlighting = 1
        self.syntaxhighlight_lexer = 'python'
        self.header_content = []
        self.footer_content = []
        self.in_header = False
        self.in_footer = False
        self.blockstyle = ''
        self.in_table_of_contents = False
        self.table_of_content_index_body = None
        self.list_level = 0
        self.def_list_level = 0
        self.footnote_ref_dict = {}
        self.footnote_list = []
        self.footnote_chars_idx = 0
        self.footnote_level = 0
        self.pending_ids = []
        self.in_paragraph = False
        self.found_doc_title = False
        self.bumped_list_level_stack = []
        self.meta_dict = {}
        self.line_block_level = 0
        self.line_indent_level = 0
        self.citation_id = None
        self.style_index = 0        # use to form unique style names
        self.str_stylesheet = ''
        self.str_stylesheetcontent = ''
        self.dom_stylesheet = None
        self.table_styles = None
        self.in_citation = False

        # Keep track of nested styling classes
        self.inline_style_count_stack = []

    def get_str_stylesheet(self):
        return self.str_stylesheet

    def retrieve_styles(self, extension):
        """Retrieve the stylesheet from either a .xml file or from
        a .odt (zip) file.  Return the content as a string.
        """
        s2 = None
        stylespath = self.settings.stylesheet
        ext = os.path.splitext(stylespath)[1]
        if ext == '.xml':
            stylesfile = open(stylespath, 'r')
            s1 = stylesfile.read()
            stylesfile.close()
        elif ext == extension:
            zfile = zipfile.ZipFile(stylespath, 'r')
            s1 = zfile.read('styles.xml')
            s2 = zfile.read('content.xml')
            zfile.close()
        else:
            raise RuntimeError('stylesheet path (%s) must be %s or '
                               '.xml file' % (stylespath, extension))
        self.str_stylesheet = s1
        self.str_stylesheetcontent = s2
        self.dom_stylesheet = etree.fromstring(self.str_stylesheet)
        self.dom_stylesheetcontent = etree.fromstring(
            self.str_stylesheetcontent)
        self.table_styles = self.extract_table_styles(s2)

    def extract_table_styles(self, styles_str):
        root = etree.fromstring(styles_str)
        table_styles = {}
        auto_styles = root.find(
            '{%s}automatic-styles' % (CNSD['office'], ))
        for stylenode in auto_styles:
            name = stylenode.get('{%s}name' % (CNSD['style'], ))
            tablename = name.split('.')[0]
            family = stylenode.get('{%s}family' % (CNSD['style'], ))
            if name.startswith(TABLESTYLEPREFIX):
                tablestyle = table_styles.get(tablename)
                if tablestyle is None:
                    tablestyle = TableStyle()
                    table_styles[tablename] = tablestyle
                if family == 'table':
                    properties = stylenode.find(
                        '{%s}table-properties' % (CNSD['style'], ))
                    property = properties.get(
                        '{%s}%s' % (CNSD['fo'], 'background-color', ))
                    if property is not None and property != 'none':
                        tablestyle.backgroundcolor = property
                elif family == 'table-cell':
                    properties = stylenode.find(
                        '{%s}table-cell-properties' % (CNSD['style'], ))
                    if properties is not None:
                        border = self.get_property(properties)
                        if border is not None:
                            tablestyle.border = border
        return table_styles

    def get_property(self, stylenode):
        border = None
        for propertyname in TABLEPROPERTYNAMES:
            border = stylenode.get('{%s}%s' % (CNSD['fo'], propertyname, ))
            if border is not None and border != 'none':
                return border
        return border

    def add_doc_title(self):
        text = self.settings.title
        if text:
            self.title = text
            if not self.found_doc_title:
                el = Element('text:p', attrib={
                    'text:style-name': self.rststyle('title'),
                })
                el.text = text
                self.body_text_element.insert(0, el)
        el = self.find_first_text_p(self.body_text_element)
        if el is not None:
            self.attach_page_style(el)

    def find_first_text_p(self, el):
        """Search the generated doc and return the first <text:p> element.
        """
        if (
                el.tag == 'text:p' or
                el.tag == 'text:h'):
            return el
        elif el.getchildren():
            for child in el.getchildren():
                el1 = self.find_first_text_p(child)
                if el1 is not None:
                    return el1
            return None
        else:
            return None

    def attach_page_style(self, el):
        """Attach the default page style.

        Create an automatic-style that refers to the current style
        of this element and that refers to the default page style.
        """
        current_style = el.get('text:style-name')
        style_name = 'P1003'
        el1 = SubElement(
            self.automatic_styles, 'style:style', attrib={
                'style:name': style_name,
                'style:master-page-name': "rststyle-pagedefault",
                'style:family': "paragraph",
            }, nsdict=SNSD)
        if current_style:
            el1.set('style:parent-style-name', current_style)
        el.set('text:style-name', style_name)

    def rststyle(self, name, parameters=()):
        """
        Returns the style name to use for the given style.

        If `parameters` is given `name` must contain a matching number of
        ``%`` and is used as a format expression with `parameters` as
        the value.
        """
        name1 = name % parameters
        stylename = self.format_map.get(name1, 'rststyle-%s' % name1)
        return stylename

    def generate_content_element(self, root):
        return SubElement(root, 'office:text')

    def setup_page(self):
        self.setup_paper(self.dom_stylesheet)
        if (len(self.header_content) > 0 or
                len(self.footer_content) > 0 or
                self.settings.custom_header or
                self.settings.custom_footer):
            self.add_header_footer(self.dom_stylesheet)
        new_content = etree.tostring(self.dom_stylesheet)
        return new_content

    def get_dom_stylesheet(self):
        return self.dom_stylesheet

    def setup_paper(self, root_el):
        try:
            fin = os.popen("paperconf -s 2> /dev/null")
            content = fin.read()
            content = content.split()
            content = list(map(float, content))
            content = list(content)
            w, h = content
        except (IOError, ValueError):
            w, h = 612, 792     # default to Letter
        finally:
            fin.close()

        def walk(el):
            if el.tag == "{%s}page-layout-properties" % SNSD["style"] and \
                    "{%s}page-width" % SNSD["fo"] not in el.attrib:
                el.attrib["{%s}page-width" % SNSD["fo"]] = "%.3fpt" % w
                el.attrib["{%s}page-height" % SNSD["fo"]] = "%.3fpt" % h
                el.attrib["{%s}margin-left" % SNSD["fo"]] = \
                    el.attrib["{%s}margin-right" % SNSD["fo"]] = \
                    "%.3fpt" % (.1 * w)
                el.attrib["{%s}margin-top" % SNSD["fo"]] = \
                    el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \
                    "%.3fpt" % (.1 * h)
            else:
                for subel in el.getchildren():
                    walk(subel)
        walk(root_el)

    def add_header_footer(self, root_el):
        automatic_styles = root_el.find(
            '{%s}automatic-styles' % SNSD['office'])
        path = '{%s}master-styles' % (NAME_SPACE_1, )
        master_el = root_el.find(path)
        if master_el is None:
            return
        path = '{%s}master-page' % (SNSD['style'], )
        master_el_container = master_el.findall(path)
        master_el = None
        target_attrib = '{%s}name' % (SNSD['style'], )
        target_name = self.rststyle('pagedefault')
        for el in master_el_container:
            if el.get(target_attrib) == target_name:
                master_el = el
                break
        if master_el is None:
            return
        el1 = master_el
        if self.header_content or self.settings.custom_header:
            if WhichElementTree == 'lxml':
                el2 = SubElement(el1, 'style:header', nsdict=SNSD)
            else:
                el2 = SubElement(
                    el1, 'style:header',
                    attrib=STYLES_NAMESPACE_ATTRIB,
                    nsdict=STYLES_NAMESPACE_DICT,
                )
            for el in self.header_content:
                attrkey = add_ns('text:style-name', nsdict=SNSD)
                el.attrib[attrkey] = self.rststyle('header')
                el2.append(el)
            if self.settings.custom_header:
                self.create_custom_headfoot(
                    el2,
                    self.settings.custom_header, 'header', automatic_styles)
        if self.footer_content or self.settings.custom_footer:
            if WhichElementTree == 'lxml':
                el2 = SubElement(el1, 'style:footer', nsdict=SNSD)
            else:
                el2 = SubElement(
                    el1, 'style:footer',
                    attrib=STYLES_NAMESPACE_ATTRIB,
                    nsdict=STYLES_NAMESPACE_DICT,
                )
            for el in self.footer_content:
                attrkey = add_ns('text:style-name', nsdict=SNSD)
                el.attrib[attrkey] = self.rststyle('footer')
                el2.append(el)
            if self.settings.custom_footer:
                self.create_custom_headfoot(
                    el2,
                    self.settings.custom_footer, 'footer', automatic_styles)

    code_none, code_field, code_text = list(range(3))
    field_pat = re.compile(r'%(..?)%')

    def create_custom_headfoot(
            self, parent, text, style_name, automatic_styles):
        parent = SubElement(parent, 'text:p', attrib={
            'text:style-name': self.rststyle(style_name),
        })
        current_element = None
        field_iter = self.split_field_specifiers_iter(text)
        for item in field_iter:
            if item[0] == ODFTranslator.code_field:
                if item[1] not in (
                        'p', 'P',
                        't1', 't2', 't3', 't4',
                        'd1', 'd2', 'd3', 'd4', 'd5',
                        's', 't', 'a'):
                    msg = 'bad field spec: %%%s%%' % (item[1], )
                    raise RuntimeError(msg)
                el1 = self.make_field_element(
                    parent,
                    item[1], style_name, automatic_styles)
                if el1 is None:
                    msg = 'bad field spec: %%%s%%' % (item[1], )
                    raise RuntimeError(msg)
                else:
                    current_element = el1
            else:
                if current_element is None:
                    parent.text = item[1]
                else:
                    current_element.tail = item[1]

    def make_field_element(self, parent, text, style_name, automatic_styles):
        if text == 'p':
            el1 = SubElement(parent, 'text:page-number', attrib={
                #'text:style-name': self.rststyle(style_name),
                'text:select-page': 'current',
            })
        elif text == 'P':
            el1 = SubElement(parent, 'text:page-count', attrib={
                #'text:style-name': self.rststyle(style_name),
            })
        elif text == 't1':
            self.style_index += 1
            el1 = SubElement(parent, 'text:time', attrib={
                'text:style-name': self.rststyle(style_name),
                'text:fixed': 'true',
                'style:data-style-name':
                    'rst-time-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:time-style', attrib={
                'style:name': 'rst-time-style-%d' % self.style_index,
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:hours', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ':'
            el3 = SubElement(el2, 'number:minutes', attrib={
                'number:style': 'long',
            })
        elif text == 't2':
            self.style_index += 1
            el1 = SubElement(parent, 'text:time', attrib={
                'text:style-name': self.rststyle(style_name),
                'text:fixed': 'true',
                'style:data-style-name':
                    'rst-time-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:time-style', attrib={
                'style:name': 'rst-time-style-%d' % self.style_index,
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:hours', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ':'
            el3 = SubElement(el2, 'number:minutes', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ':'
            el3 = SubElement(el2, 'number:seconds', attrib={
                'number:style': 'long',
            })
        elif text == 't3':
            self.style_index += 1
            el1 = SubElement(parent, 'text:time', attrib={
                'text:style-name': self.rststyle(style_name),
                'text:fixed': 'true',
                'style:data-style-name':
                    'rst-time-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:time-style', attrib={
                'style:name': 'rst-time-style-%d' % self.style_index,
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:hours', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ':'
            el3 = SubElement(el2, 'number:minutes', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ' '
            el3 = SubElement(el2, 'number:am-pm')
        elif text == 't4':
            self.style_index += 1
            el1 = SubElement(parent, 'text:time', attrib={
                'text:style-name': self.rststyle(style_name),
                'text:fixed': 'true',
                'style:data-style-name':
                    'rst-time-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:time-style', attrib={
                'style:name': 'rst-time-style-%d' % self.style_index,
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:hours', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ':'
            el3 = SubElement(el2, 'number:minutes', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ':'
            el3 = SubElement(el2, 'number:seconds', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ' '
            el3 = SubElement(el2, 'number:am-pm')
        elif text == 'd1':
            self.style_index += 1
            el1 = SubElement(parent, 'text:date', attrib={
                'text:style-name': self.rststyle(style_name),
                'style:data-style-name':
                    'rst-date-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:date-style', attrib={
                'style:name': 'rst-date-style-%d' % self.style_index,
                'number:automatic-order': 'true',
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:month', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = '/'
            el3 = SubElement(el2, 'number:day', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = '/'
            el3 = SubElement(el2, 'number:year')
        elif text == 'd2':
            self.style_index += 1
            el1 = SubElement(parent, 'text:date', attrib={
                'text:style-name': self.rststyle(style_name),
                'style:data-style-name':
                    'rst-date-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:date-style', attrib={
                'style:name': 'rst-date-style-%d' % self.style_index,
                'number:automatic-order': 'true',
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:month', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = '/'
            el3 = SubElement(el2, 'number:day', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = '/'
            el3 = SubElement(el2, 'number:year', attrib={
                'number:style': 'long',
            })
        elif text == 'd3':
            self.style_index += 1
            el1 = SubElement(parent, 'text:date', attrib={
                'text:style-name': self.rststyle(style_name),
                'style:data-style-name':
                    'rst-date-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:date-style', attrib={
                'style:name': 'rst-date-style-%d' % self.style_index,
                'number:automatic-order': 'true',
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:month', attrib={
                'number:textual': 'true',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ' '
            el3 = SubElement(el2, 'number:day', attrib={})
            el3 = SubElement(el2, 'number:text')
            el3.text = ', '
            el3 = SubElement(el2, 'number:year', attrib={
                'number:style': 'long',
            })
        elif text == 'd4':
            self.style_index += 1
            el1 = SubElement(parent, 'text:date', attrib={
                'text:style-name': self.rststyle(style_name),
                'style:data-style-name':
                    'rst-date-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:date-style', attrib={
                'style:name': 'rst-date-style-%d' % self.style_index,
                'number:automatic-order': 'true',
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:month', attrib={
                'number:textual': 'true',
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = ' '
            el3 = SubElement(el2, 'number:day', attrib={})
            el3 = SubElement(el2, 'number:text')
            el3.text = ', '
            el3 = SubElement(el2, 'number:year', attrib={
                'number:style': 'long',
            })
        elif text == 'd5':
            self.style_index += 1
            el1 = SubElement(parent, 'text:date', attrib={
                'text:style-name': self.rststyle(style_name),
                'style:data-style-name':
                    'rst-date-style-%d' % self.style_index,
            })
            el2 = SubElement(automatic_styles, 'number:date-style', attrib={
                'style:name': 'rst-date-style-%d' % self.style_index,
                'xmlns:number': SNSD['number'],
                'xmlns:style': SNSD['style'],
            })
            el3 = SubElement(el2, 'number:year', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = '-'
            el3 = SubElement(el2, 'number:month', attrib={
                'number:style': 'long',
            })
            el3 = SubElement(el2, 'number:text')
            el3.text = '-'
            el3 = SubElement(el2, 'number:day', attrib={
                'number:style': 'long',
            })
        elif text == 's':
            el1 = SubElement(parent, 'text:subject', attrib={
                'text:style-name': self.rststyle(style_name),
            })
        elif text == 't':
            el1 = SubElement(parent, 'text:title', attrib={
                'text:style-name': self.rststyle(style_name),
            })
        elif text == 'a':
            el1 = SubElement(parent, 'text:author-name', attrib={
                'text:fixed': 'false',
            })
        else:
            el1 = None
        return el1

    def split_field_specifiers_iter(self, text):
        pos1 = 0
        while True:
            mo = ODFTranslator.field_pat.search(text, pos1)
            if mo:
                pos2 = mo.start()
                if pos2 > pos1:
                    yield (ODFTranslator.code_text, text[pos1:pos2])
                yield (ODFTranslator.code_field, mo.group(1))
                pos1 = mo.end()
            else:
                break
        trailing = text[pos1:]
        if trailing:
            yield (ODFTranslator.code_text, trailing)

    def astext(self):
        root = self.content_tree.getroot()
        et = etree.ElementTree(root)
        s1 = ToString(et)
        return s1

    def content_astext(self):
        return self.astext()

    def set_title(self, title):
        self.title = title

    def get_title(self):
        return self.title

    def set_embedded_file_list(self, embedded_file_list):
        self.embedded_file_list = embedded_file_list

    def get_embedded_file_list(self):
        return self.embedded_file_list

    def get_meta_dict(self):
        return self.meta_dict

    def process_footnotes(self):
        for node, el1 in self.footnote_list:
            backrefs = node.attributes.get('backrefs', [])
            first = True
            for ref in backrefs:
                el2 = self.footnote_ref_dict.get(ref)
                if el2 is not None:
                    if first:
                        first = False
                        el3 = copy.deepcopy(el1)
                        el2.append(el3)
                    else:
                        children = el2.getchildren()
                        if len(children) > 0:   # and 'id' in el2.attrib:
                            child = children[0]
                            ref1 = child.text
                            attribkey = add_ns('text:id', nsdict=SNSD)
                            id1 = el2.get(attribkey, 'footnote-error')
                            if id1 is None:
                                id1 = ''
                            tag = add_ns('text:note-ref', nsdict=SNSD)
                            el2.tag = tag
                            if self.settings.endnotes_end_doc:
                                note_class = 'endnote'
                            else:
                                note_class = 'footnote'
                            el2.attrib.clear()
                            attribkey = add_ns('text:note-class', nsdict=SNSD)
                            el2.attrib[attribkey] = note_class
                            attribkey = add_ns('text:ref-name', nsdict=SNSD)
                            el2.attrib[attribkey] = id1
                            attribkey = add_ns(
                                'text:reference-format', nsdict=SNSD)
                            el2.attrib[attribkey] = 'page'
                            el2.text = ref1

    #
    # Utility methods

    def append_child(self, tag, attrib=None, parent=None):
        if parent is None:
            parent = self.current_element
        el = SubElement(parent, tag, attrib)
        return el

    def append_p(self, style, text=None):
        result = self.append_child('text:p', attrib={
            'text:style-name': self.rststyle(style)})
        self.append_pending_ids(result)
        if text is not None:
            result.text = text
        return result

    def append_pending_ids(self, el):
        if self.settings.create_links:
            for id in self.pending_ids:
                SubElement(el, 'text:reference-mark', attrib={
                    'text:name': id})
        self.pending_ids = []

    def set_current_element(self, el):
        self.current_element = el

    def set_to_parent(self):
        self.current_element = self.current_element.getparent()

    def generate_labeled_block(self, node, label):
        label = '%s:' % (self.language.labels[label], )
        el = self.append_p('textbody')
        el1 = SubElement(
            el, 'text:span',
            attrib={'text:style-name': self.rststyle('strong')})
        el1.text = label
        el = self.append_p('blockindent')
        return el

    def generate_labeled_line(self, node, label):
        label = '%s:' % (self.language.labels[label], )
        el = self.append_p('textbody')
        el1 = SubElement(
            el, 'text:span',
            attrib={'text:style-name': self.rststyle('strong')})
        el1.text = label
        el1.tail = node.astext()
        return el

    def encode(self, text):
        text = text.replace('\n', " ")
        return text

    #
    # Visitor functions
    #
    # In alphabetic order, more or less.
    #   See docutils.docutils.nodes.node_class_names.
    #

    def dispatch_visit(self, node):
        """Override to catch basic attributes which many nodes have."""
        self.handle_basic_atts(node)
        nodes.GenericNodeVisitor.dispatch_visit(self, node)

    def handle_basic_atts(self, node):
        if isinstance(node, nodes.Element) and node['ids']:
            self.pending_ids += node['ids']

    def default_visit(self, node):
        self.document.reporter.warning('missing visit_%s' % (node.tagname, ))

    def default_departure(self, node):
        self.document.reporter.warning('missing depart_%s' % (node.tagname, ))

    def visit_Text(self, node):
        # Skip nodes whose text has been processed in parent nodes.
        if isinstance(node.parent, docutils.nodes.literal_block):
            return
        text = node.astext()
        # Are we in mixed content?  If so, add the text to the
        #   etree tail of the previous sibling element.
        if len(self.current_element.getchildren()) > 0:
            if self.current_element.getchildren()[-1].tail:
                self.current_element.getchildren()[-1].tail += text
            else:
                self.current_element.getchildren()[-1].tail = text
        else:
            if self.current_element.text:
                self.current_element.text += text
            else:
                self.current_element.text = text

    def depart_Text(self, node):
        pass

    #
    # Pre-defined fields
    #

    def visit_address(self, node):
        el = self.generate_labeled_block(node, 'address')
        self.set_current_element(el)

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

    def visit_author(self, node):
        if isinstance(node.parent, nodes.authors):
            el = self.append_p('blockindent')
        else:
            el = self.generate_labeled_block(node, 'author')
        self.set_current_element(el)

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

    def visit_authors(self, node):
        label = '%s:' % (self.language.labels['authors'], )
        el = self.append_p('textbody')
        el1 = SubElement(
            el, 'text:span',
            attrib={'text:style-name': self.rststyle('strong')})
        el1.text = label

    def depart_authors(self, node):
        pass

    def visit_contact(self, node):
        el = self.generate_labeled_block(node, 'contact')
        self.set_current_element(el)

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

    def visit_copyright(self, node):
        el = self.generate_labeled_block(node, 'copyright')
        self.set_current_element(el)

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

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

    def depart_date(self, node):
        pass

    def visit_organization(self, node):
        el = self.generate_labeled_block(node, 'organization')
        self.set_current_element(el)

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

    def visit_status(self, node):
        el = self.generate_labeled_block(node, 'status')
        self.set_current_element(el)

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

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

    def depart_revision(self, node):
        pass

    def visit_version(self, node):
        self.generate_labeled_line(node, 'version')
        #self.set_current_element(el)

    def depart_version(self, node):
        #self.set_to_parent()
        pass

    def visit_attribution(self, node):
        self.append_p('attribution', node.astext())

    def depart_attribution(self, node):
        pass

    def visit_block_quote(self, node):
        if 'epigraph' in node.attributes['classes']:
            self.paragraph_style_stack.append(self.rststyle('epigraph'))
            self.blockstyle = self.rststyle('epigraph')
        elif 'highlights' in node.attributes['classes']:
            self.paragraph_style_stack.append(self.rststyle('highlights'))
            self.blockstyle = self.rststyle('highlights')
        else:
            self.paragraph_style_stack.append(self.rststyle('blockquote'))
            self.blockstyle = self.rststyle('blockquote')
        self.line_indent_level += 1

    def depart_block_quote(self, node):
        self.paragraph_style_stack.pop()
        self.blockstyle = ''
        self.line_indent_level -= 1

    def visit_bullet_list(self, node):
        self.list_level += 1
        if self.in_table_of_contents:
            if self.settings.generate_oowriter_toc:
                pass
            else:
                if 'classes' in node and \
                        'auto-toc' in node.attributes['classes']:
                    el = SubElement(self.current_element, 'text:list', attrib={
                        'text:style-name': self.rststyle('tocenumlist'),
                    })
                    self.list_style_stack.append(self.rststyle('enumitem'))
                else:
                    el = SubElement(self.current_element, 'text:list', attrib={
                        'text:style-name': self.rststyle('tocbulletlist'),
                    })
                    self.list_style_stack.append(self.rststyle('bulletitem'))
                self.set_current_element(el)
        else:
            if self.blockstyle == self.rststyle('blockquote'):
                el = SubElement(self.current_element, 'text:list', attrib={
                    'text:style-name': self.rststyle('blockquote-bulletlist'),
                })
                self.list_style_stack.append(
                    self.rststyle('blockquote-bulletitem'))
            elif self.blockstyle == self.rststyle('highlights'):
                el = SubElement(self.current_element, 'text:list', attrib={
                    'text:style-name': self.rststyle('highlights-bulletlist'),
                })
                self.list_style_stack.append(
                    self.rststyle('highlights-bulletitem'))
            elif self.blockstyle == self.rststyle('epigraph'):
                el = SubElement(self.current_element, 'text:list', attrib={
                    'text:style-name': self.rststyle('epigraph-bulletlist'),
                })
                self.list_style_stack.append(
                    self.rststyle('epigraph-bulletitem'))
            else:
                el = SubElement(self.current_element, 'text:list', attrib={
                    'text:style-name': self.rststyle('bulletlist'),
                })
                self.list_style_stack.append(self.rststyle('bulletitem'))
            self.set_current_element(el)

    def depart_bullet_list(self, node):
        if self.in_table_of_contents:
            if self.settings.generate_oowriter_toc:
                pass
            else:
                self.set_to_parent()
                self.list_style_stack.pop()
        else:
            self.set_to_parent()
            self.list_style_stack.pop()
        self.list_level -= 1

    def visit_caption(self, node):
        raise nodes.SkipChildren()
        pass

    def depart_caption(self, node):
        pass

    def visit_comment(self, node):
        el = self.append_p('textbody')
        el1 = SubElement(el, 'office:annotation', attrib={})
        el2 = SubElement(el1, 'dc:creator', attrib={})
        s1 = os.environ.get('USER', '')
        el2.text = s1
        el2 = SubElement(el1, 'text:p', attrib={})
        el2.text = node.astext()

    def depart_comment(self, node):
        pass

    def visit_compound(self, node):
        # The compound directive currently receives no special treatment.
        pass

    def depart_compound(self, node):
        pass

    def visit_container(self, node):
        styles = node.attributes.get('classes', ())
        if len(styles) > 0:
            self.paragraph_style_stack.append(self.rststyle(styles[0]))

    def depart_container(self, node):
        styles = node.attributes.get('classes', ())
        if len(styles) > 0:
            self.paragraph_style_stack.pop()

    def visit_decoration(self, node):
        pass

    def depart_decoration(self, node):
        pass

    def visit_definition_list(self, node):
        self.def_list_level += 1
        if self.list_level > 5:
            raise RuntimeError(
                'max definition list nesting level exceeded')

    def depart_definition_list(self, node):
        self.def_list_level -= 1

    def visit_definition_list_item(self, node):
        pass

    def depart_definition_list_item(self, node):
        pass

    def visit_term(self, node):
        el = self.append_p('deflist-term-%d' % self.def_list_level)
        el.text = node.astext()
        self.set_current_element(el)
        raise nodes.SkipChildren()

    def depart_term(self, node):
        self.set_to_parent()

    def visit_definition(self, node):
        self.paragraph_style_stack.append(
            self.rststyle('deflist-def-%d' % self.def_list_level))
        self.bumped_list_level_stack.append(ListLevel(1))

    def depart_definition(self, node):
        self.paragraph_style_stack.pop()
        self.bumped_list_level_stack.pop()

    def visit_classifier(self, node):
        els = self.current_element.getchildren()
        if len(els) > 0:
            el = els[-1]
            el1 = SubElement(
                el, 'text:span',
                attrib={'text:style-name': self.rststyle('emphasis')})
            el1.text = ' (%s)' % (node.astext(), )

    def depart_classifier(self, node):
        pass

    def visit_document(self, node):
        pass

    def depart_document(self, node):
        self.process_footnotes()

    def visit_docinfo(self, node):
        self.section_level += 1
        self.section_count += 1
        if self.settings.create_sections:
            el = self.append_child(
                'text:section', attrib={
                    'text:name': 'Section%d' % self.section_count,
                    'text:style-name': 'Sect%d' % self.section_level,
                }
            )
            self.set_current_element(el)

    def depart_docinfo(self, node):
        self.section_level -= 1
        if self.settings.create_sections:
            self.set_to_parent()

    def visit_emphasis(self, node):
        el = SubElement(
            self.current_element, 'text:span',
            attrib={'text:style-name': self.rststyle('emphasis')})
        self.set_current_element(el)

    def depart_emphasis(self, node):
        self.set_to_parent()

    def visit_enumerated_list(self, node):
        el1 = self.current_element
        if self.blockstyle == self.rststyle('blockquote'):
            el2 = SubElement(el1, 'text:list', attrib={
                'text:style-name': self.rststyle('blockquote-enumlist'),
            })
            self.list_style_stack.append(self.rststyle('blockquote-enumitem'))
        elif self.blockstyle == self.rststyle('highlights'):
            el2 = SubElement(el1, 'text:list', attrib={
                'text:style-name': self.rststyle('highlights-enumlist'),
            })
            self.list_style_stack.append(self.rststyle('highlights-enumitem'))
        elif self.blockstyle == self.rststyle('epigraph'):
            el2 = SubElement(el1, 'text:list', attrib={
                'text:style-name': self.rststyle('epigraph-enumlist'),
            })
            self.list_style_stack.append(self.rststyle('epigraph-enumitem'))
        else:
            liststylename = 'enumlist-%s' % (node.get('enumtype', 'arabic'), )
            el2 = SubElement(el1, 'text:list', attrib={
                'text:style-name': self.rststyle(liststylename),
            })
            self.list_style_stack.append(self.rststyle('enumitem'))
        self.set_current_element(el2)

    def depart_enumerated_list(self, node):
        self.set_to_parent()
        self.list_style_stack.pop()

    def visit_list_item(self, node):
        # If we are in a "bumped" list level, then wrap this
        #   list in an outer lists in order to increase the
        #   indentation level.
        if self.in_table_of_contents:
            if self.settings.generate_oowriter_toc:
                self.paragraph_style_stack.append(
                    self.rststyle('contents-%d' % (self.list_level, )))
            else:
                el1 = self.append_child('text:list-item')
                self.set_current_element(el1)
        else:
            el1 = self.append_child('text:list-item')
            el3 = el1
            if len(self.bumped_list_level_stack) > 0:
                level_obj = self.bumped_list_level_stack[-1]
                if level_obj.get_sibling():
                    level_obj.set_nested(False)
                    for level_obj1 in self.bumped_list_level_stack:
                        for idx in range(level_obj1.get_level()):
                            el2 = self.append_child('text:list', parent=el3)
                            el3 = self.append_child(
                                'text:list-item', parent=el2)
            self.paragraph_style_stack.append(self.list_style_stack[-1])
            self.set_current_element(el3)

    def depart_list_item(self, node):
        if self.in_table_of_contents:
            if self.settings.generate_oowriter_toc:
                self.paragraph_style_stack.pop()
            else:
                self.set_to_parent()
        else:
            if len(self.bumped_list_level_stack) > 0:
                level_obj = self.bumped_list_level_stack[-1]
                if level_obj.get_sibling():
                    level_obj.set_nested(True)
                    for level_obj1 in self.bumped_list_level_stack:
                        for idx in range(level_obj1.get_level()):
                            self.set_to_parent()
                            self.set_to_parent()
            self.paragraph_style_stack.pop()
            self.set_to_parent()

    def visit_header(self, node):
        self.in_header = True

    def depart_header(self, node):
        self.in_header = False

    def visit_footer(self, node):
        self.in_footer = True

    def depart_footer(self, node):
        self.in_footer = False

    def visit_field(self, node):
        pass

    def depart_field(self, node):
        pass

    def visit_field_list(self, node):
        pass

    def depart_field_list(self, node):
        pass

    def visit_field_name(self, node):
        el = self.append_p('textbody')
        el1 = SubElement(
            el, 'text:span',
            attrib={'text:style-name': self.rststyle('strong')})
        el1.text = node.astext()

    def depart_field_name(self, node):
        pass

    def visit_field_body(self, node):
        self.paragraph_style_stack.append(self.rststyle('blockindent'))

    def depart_field_body(self, node):
        self.paragraph_style_stack.pop()

    def visit_figure(self, node):
        pass

    def depart_figure(self, node):
        pass

    def visit_footnote(self, node):
        self.footnote_level += 1
        self.save_footnote_current = self.current_element
        el1 = Element('text:note-body')
        self.current_element = el1
        self.footnote_list.append((node, el1))
        if isinstance(node, docutils.nodes.citation):
            self.paragraph_style_stack.append(self.rststyle('citation'))
        else:
            self.paragraph_style_stack.append(self.rststyle('footnote'))

    def depart_footnote(self, node):
        self.paragraph_style_stack.pop()
        self.current_element = self.save_footnote_current
        self.footnote_level -= 1

    footnote_chars = [
        '*', '**', '***',
        '++', '+++',
        '##', '###',
        '@@', '@@@',
    ]

    def visit_footnote_reference(self, node):
        if self.footnote_level <= 0:
            id = node.attributes['ids'][0]
            refid = node.attributes.get('refid')
            if refid is None:
                refid = ''
            if self.settings.endnotes_end_doc:
                note_class = 'endnote'
            else:
                note_class = 'footnote'
            el1 = self.append_child('text:note', attrib={
                'text:id': '%s' % (refid, ),
                'text:note-class': note_class,
            })
            note_auto = str(node.attributes.get('auto', 1))
            if isinstance(node, docutils.nodes.citation_reference):
                citation = '[%s]' % node.astext()
                el2 = SubElement(el1, 'text:note-citation', attrib={
                    'text:label': citation,
                })
                el2.text = citation
            elif note_auto == '1':
                el2 = SubElement(el1, 'text:note-citation', attrib={
                    'text:label': node.astext(),
                })
                el2.text = node.astext()
            elif note_auto == '*':
                if self.footnote_chars_idx >= len(
                        ODFTranslator.footnote_chars):
                    self.footnote_chars_idx = 0
                footnote_char = ODFTranslator.footnote_chars[
                    self.footnote_chars_idx]
                self.footnote_chars_idx += 1
                el2 = SubElement(el1, 'text:note-citation', attrib={
                    'text:label': footnote_char,
                })
                el2.text = footnote_char
            self.footnote_ref_dict[id] = el1
        raise nodes.SkipChildren()

    def depart_footnote_reference(self, node):
        pass

    def visit_citation(self, node):
        self.in_citation = True
        for id in node.attributes['ids']:
            self.citation_id = id
            break
        self.paragraph_style_stack.append(self.rststyle('blockindent'))
        self.bumped_list_level_stack.append(ListLevel(1))

    def depart_citation(self, node):
        self.citation_id = None
        self.paragraph_style_stack.pop()
        self.bumped_list_level_stack.pop()
        self.in_citation = False

    def visit_citation_reference(self, node):
        if self.settings.create_links:
            id = node.attributes['refid']
            el = self.append_child('text:reference-ref', attrib={
                'text:ref-name': '%s' % (id, ),
                'text:reference-format': 'text',
            })
            el.text = '['
            self.set_current_element(el)
        elif self.current_element.text is None:
            self.current_element.text = '['
        else:
            self.current_element.text += '['

    def depart_citation_reference(self, node):
        self.current_element.text += ']'
        if self.settings.create_links:
            self.set_to_parent()

    def visit_label(self, node):
        if isinstance(node.parent, docutils.nodes.footnote):
            raise nodes.SkipChildren()
        elif self.citation_id is not None:
            el = self.append_p('textbody')
            self.set_current_element(el)
            if self.settings.create_links:
                el0 = SubElement(el, 'text:span')
                el0.text = '['
                self.append_child('text:reference-mark-start', attrib={
                    'text:name': '%s' % (self.citation_id, ),
                })
            else:
                el.text = '['

    def depart_label(self, node):
        if isinstance(node.parent, docutils.nodes.footnote):
            pass
        elif self.citation_id is not None:
            if self.settings.create_links:
                self.append_child('text:reference-mark-end', attrib={
                    'text:name': '%s' % (self.citation_id, ),
                })
                el0 = SubElement(self.current_element, 'text:span')
                el0.text = ']'
            else:
                self.current_element.text += ']'
            self.set_to_parent()

    def visit_generated(self, node):
        pass

    def depart_generated(self, node):
        pass

    def check_file_exists(self, path):
        if os.path.exists(path):
            return 1
        else:
            return 0

    def visit_image(self, node):
        # Capture the image file.
        if 'uri' in node.attributes:
            source = node.attributes['uri']
            if not (source.startswith('http:') or source.startswith('https:')):
                if not source.startswith(os.sep):
                    docsource, line = utils.get_source_line(node)
                    if docsource:
                        dirname = os.path.dirname(docsource)
                        if dirname:
                            source = '%s%s%s' % (dirname, os.sep, source, )
                if not self.check_file_exists(source):
                    self.document.reporter.warning(
                        'Cannot find image file %s.' % (source, ))
                    return
        else:
            return
        if source in self.image_dict:
            filename, destination = self.image_dict[source]
        else:
            self.image_count += 1
            filename = os.path.split(source)[1]
            destination = 'Pictures/1%08x%s' % (self.image_count, filename, )
            if source.startswith('http:') or source.startswith('https:'):
                try:
                    imgfile = urlopen(source)
                    content = imgfile.read()
                    imgfile.close()
                    imgfile2 = tempfile.NamedTemporaryFile('wb', delete=False)
                    imgfile2.write(content)
                    imgfile2.close()
                    imgfilename = imgfile2.name
                    source = imgfilename
                except HTTPError:
                    self.document.reporter.warning(
                        "Can't open image url %s." % (source, ))
                spec = (source, destination,)
            else:
                spec = (os.path.abspath(source), destination,)
            self.embedded_file_list.append(spec)
            self.image_dict[source] = (source, destination,)
        # Is this a figure (containing an image) or just a plain image?
        if self.in_paragraph:
            el1 = self.current_element
        else:
            el1 = SubElement(
                self.current_element, 'text:p',
                attrib={'text:style-name': self.rststyle('textbody')})
        el2 = el1
        if isinstance(node.parent, docutils.nodes.figure):
            el3, el4, el5, caption = self.generate_figure(
                node, source,
                destination, el2)
            attrib = {}
            el6, width = self.generate_image(
                node, source, destination,
                el5, attrib)
            if caption is not None:
                el6.tail = caption
        else:   # if isinstance(node.parent, docutils.nodes.image):
            self.generate_image(node, source, destination, el2)

    def depart_image(self, node):
        pass

    def get_image_width_height(self, node, attr):
        size = None
        unit = None
        if attr in node.attributes:
            size = node.attributes[attr]
            size = size.strip()
            # For conversion factors, see:
            # http://www.unitconversion.org/unit_converter/typography-ex.html
            try:
                if size.endswith('%'):
                    if attr == 'height':
                        # Percentage allowed for width but not height.
                        raise ValueError('percentage not allowed for height')
                    size = size.rstrip(' %')
                    size = float(size) / 100.0
                    unit = '%'
                else:
                    size, unit = self.convert_to_cm(size)
            except ValueError as exp:
                self.document.reporter.warning(
                    'Invalid %s for image: "%s".  '
                    'Error: "%s".' % (
                        attr, node.attributes[attr], exp))
        return size, unit

    def convert_to_cm(self, size):
        """Convert various units to centimeters.

        Note that a call to this method should be wrapped in:
            try: except ValueError:
        """
        size = size.strip()
        if size.endswith('px'):
            size = float(size[:-2]) * 0.026     # convert px to cm
        elif size.endswith('in'):
            size = float(size[:-2]) * 2.54      # convert in to cm
        elif size.endswith('pt'):
            size = float(size[:-2]) * 0.035     # convert pt to cm
        elif size.endswith('pc'):
            size = float(size[:-2]) * 2.371     # convert pc to cm
        elif size.endswith('mm'):
            size = float(size[:-2]) * 0.1       # convert mm to cm
        elif size.endswith('cm'):
            size = float(size[:-2])
        else:
            raise ValueError('unknown unit type')
        unit = 'cm'
        return size, unit

    def get_image_scale(self, node):
        if 'scale' in node.attributes:
            scale = node.attributes['scale']
            try:
                scale = int(scale)
            except ValueError:
                self.document.reporter.warning(
                    'Invalid scale for image: "%s"' % (
                        node.attributes['scale'], ))
            if scale < 1:       # or scale > 100:
                self.document.reporter.warning(
                    'scale out of range (%s), using 1.' % (scale, ))
                scale = 1
            scale = scale * 0.01
        else:
            scale = 1.0
        return scale

    def get_image_scaled_width_height(self, node, source):
        """Return the image size in centimeters adjusted by image attrs."""
        scale = self.get_image_scale(node)
        width, width_unit = self.get_image_width_height(node, 'width')
        height, _ = self.get_image_width_height(node, 'height')
        dpi = (72, 72)
        if PIL is not None and source in self.image_dict:
            filename, destination = self.image_dict[source]
            imageobj = PIL.Image.open(filename, 'r')
            dpi = imageobj.info.get('dpi', dpi)
            # dpi information can be (xdpi, ydpi) or xydpi
            try:
                iter(dpi)
            except TypeError:
                dpi = (dpi, dpi)
        else:
            imageobj = None
        if width is None or height is None:
            if imageobj is None:
                raise RuntimeError(
                    'image size not fully specified and PIL not installed')
            if width is None:
                width = imageobj.size[0]
                width = float(width) * 0.026        # convert px to cm
            if height is None:
                height = imageobj.size[1]
                height = float(height) * 0.026      # convert px to cm
            if width_unit == '%':
                factor = width
                image_width = imageobj.size[0]
                image_width = float(image_width) * 0.026    # convert px to cm
                image_height = imageobj.size[1]
                image_height = float(image_height) * 0.026  # convert px to cm
                line_width = self.get_page_width()
                width = factor * line_width
                factor = (factor * line_width) / image_width
                height = factor * image_height
        width *= scale
        height *= scale
        width = '%.2fcm' % width
        height = '%.2fcm' % height
        return width, height

    def get_page_width(self):
        """Return the document's page width in centimeters."""
        root = self.get_dom_stylesheet()
        nodes = root.iterfind(
            './/{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
            'page-layout/'
            '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}'
            'page-layout-properties')
        width = None
        for node in nodes:
            page_width = node.get(
                '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
                'page-width')
            margin_left = node.get(
                '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
                'margin-left')
            margin_right = node.get(
                '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}'
                'margin-right')
            if (page_width is None or
                    margin_left is None or
                    margin_right is None):
                continue
            try:
                page_width, _ = self.convert_to_cm(page_width)
                margin_left, _ = self.convert_to_cm(margin_left)
                margin_right, _ = self.convert_to_cm(margin_right)
            except ValueError:
                self.document.reporter.warning(
                    'Stylesheet file contains invalid page width '
                    'or margin size.')
            width = page_width - margin_left - margin_right
        if width is None:
            # We can't find the width in styles, so we make a guess.
            # Use a width of 6 in = 15.24 cm.
            width = 15.24
        return width

    def generate_figure(self, node, source, destination, current_element):
        caption = None
        width, height = self.get_image_scaled_width_height(node, source)
        for node1 in node.parent.children:
            if node1.tagname == 'caption':
                caption = node1.astext()
        self.image_style_count += 1
        #
        # Add the style for the caption.
        if caption is not None:
            attrib = {
                'style:class': 'extra',
                'style:family': 'paragraph',
                'style:name': 'Caption',
                'style:parent-style-name': 'Standard',
            }
            el1 = SubElement(self.automatic_styles, 'style:style',
                             attrib=attrib, nsdict=SNSD)
            attrib = {
                'fo:margin-bottom': '0.0835in',
                'fo:margin-top': '0.0835in',
                'text:line-number': '0',
                'text:number-lines': 'false',
            }
            SubElement(el1, 'style:paragraph-properties',
                       attrib=attrib, nsdict=SNSD)
            attrib = {
                'fo:font-size': '12pt',
                'fo:font-style': 'italic',
                'style:font-name': 'Times',
                'style:font-name-complex': 'Lucidasans1',
                'style:font-size-asian': '12pt',
                'style:font-size-complex': '12pt',
                'style:font-style-asian': 'italic',
                'style:font-style-complex': 'italic',
            }
            SubElement(el1, 'style:text-properties',
                       attrib=attrib, nsdict=SNSD)
        style_name = 'rstframestyle%d' % self.image_style_count
        draw_name = 'graphics%d' % next(IMAGE_NAME_COUNTER)
        # Add the styles
        attrib = {
            'style:name': style_name,
            'style:family': 'graphic',
            'style:parent-style-name': self.rststyle('figureframe'),
        }
        el1 = SubElement(self.automatic_styles,
                         'style:style', attrib=attrib, nsdict=SNSD)
        attrib = {}
        wrap = False
        classes = node.parent.attributes.get('classes')
        if classes and 'wrap' in classes:
            wrap = True
        if wrap:
            attrib['style:wrap'] = 'dynamic'
        else:
            attrib['style:wrap'] = 'none'
        SubElement(el1, 'style:graphic-properties',
                   attrib=attrib, nsdict=SNSD)
        attrib = {
            'draw:style-name': style_name,
            'draw:name': draw_name,
            'text:anchor-type': 'paragraph',
            'draw:z-index': '0',
        }
        attrib['svg:width'] = width
        el3 = SubElement(current_element, 'draw:frame', attrib=attrib)
        attrib = {}
        el4 = SubElement(el3, 'draw:text-box', attrib=attrib)
        attrib = {
            'text:style-name': self.rststyle('caption'),
        }
        el5 = SubElement(el4, 'text:p', attrib=attrib)
        return el3, el4, el5, caption

    def generate_image(self, node, source, destination, current_element,
                       frame_attrs=None):
        width, height = self.get_image_scaled_width_height(node, source)
        self.image_style_count += 1
        style_name = 'rstframestyle%d' % self.image_style_count
        # Add the style.
        attrib = {
            'style:name': style_name,
            'style:family': 'graphic',
            'style:parent-style-name': self.rststyle('image'),
        }
        el1 = SubElement(self.automatic_styles,
                         'style:style', attrib=attrib, nsdict=SNSD)
        halign = None
        valign = None
        if 'align' in node.attributes:
            align = node.attributes['align'].split()
            for val in align:
                if val in ('left', 'center', 'right'):
                    halign = val
                elif val in ('top', 'middle', 'bottom'):
                    valign = val
        if frame_attrs is None:
            attrib = {
                'style:vertical-pos': 'top',
                'style:vertical-rel': 'paragraph',
                'style:horizontal-rel': 'paragraph',
                'style:mirror': 'none',
                'fo:clip': 'rect(0cm 0cm 0cm 0cm)',
                'draw:luminance': '0%',
                'draw:contrast': '0%',
                'draw:red': '0%',
                'draw:green': '0%',
                'draw:blue': '0%',
                'draw:gamma': '100%',
                'draw:color-inversion': 'false',
                'draw:image-opacity': '100%',
                'draw:color-mode': 'standard',
            }
        else:
            attrib = frame_attrs
        if halign is not None:
            attrib['style:horizontal-pos'] = halign
        if valign is not None:
            attrib['style:vertical-pos'] = valign
        # If there is a classes/wrap directive or we are
        #   inside a table, add a no-wrap style.
        wrap = False
        classes = node.attributes.get('classes')
        if classes and 'wrap' in classes:
            wrap = True
        if wrap:
            attrib['style:wrap'] = 'dynamic'
        else:
            attrib['style:wrap'] = 'none'
        # If we are inside a table, add a no-wrap style.
        if self.is_in_table(node):
            attrib['style:wrap'] = 'none'
        SubElement(el1, 'style:graphic-properties',
                   attrib=attrib, nsdict=SNSD)
        draw_name = 'graphics%d' % next(IMAGE_NAME_COUNTER)
        # Add the content.
        #el = SubElement(current_element, 'text:p',
        #    attrib={'text:style-name': self.rststyle('textbody')})
        attrib = {
            'draw:style-name': style_name,
            'draw:name': draw_name,
            'draw:z-index': '1',
        }
        if isinstance(node.parent, nodes.TextElement):
            attrib['text:anchor-type'] = 'as-char'      # vds
        else:
            attrib['text:anchor-type'] = 'paragraph'
        attrib['svg:width'] = width
        attrib['svg:height'] = height
        el1 = SubElement(current_element, 'draw:frame', attrib=attrib)
        SubElement(el1, 'draw:image', attrib={
            'xlink:href': '%s' % (destination, ),
            'xlink:type': 'simple',
            'xlink:show': 'embed',
            'xlink:actuate': 'onLoad',
        })
        return el1, width

    def is_in_table(self, node):
        node1 = node.parent
        while node1:
            if isinstance(node1, docutils.nodes.entry):
                return True
            node1 = node1.parent
        return False

    def visit_legend(self, node):
        if isinstance(node.parent, docutils.nodes.figure):
            el1 = self.current_element[-1]
            el1 = el1[0][0]
            self.current_element = el1
            self.paragraph_style_stack.append(self.rststyle('legend'))

    def depart_legend(self, node):
        if isinstance(node.parent, docutils.nodes.figure):
            self.paragraph_style_stack.pop()
            self.set_to_parent()
            self.set_to_parent()
            self.set_to_parent()

    def visit_line_block(self, node):
        self.line_indent_level += 1
        self.line_block_level += 1

    def depart_line_block(self, node):
        self.line_indent_level -= 1
        self.line_block_level -= 1

    def visit_line(self, node):
        style = 'lineblock%d' % self.line_indent_level
        el1 = SubElement(self.current_element, 'text:p',
                         attrib={'text:style-name': self.rststyle(style), })
        self.current_element = el1

    def depart_line(self, node):
        self.set_to_parent()

    def visit_literal(self, node):
        el = SubElement(
            self.current_element, 'text:span',
            attrib={'text:style-name': self.rststyle('inlineliteral')})
        self.set_current_element(el)

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

    def visit_inline(self, node):
        styles = node.attributes.get('classes', ())
        if styles:
            el = self.current_element
            for inline_style in styles:
                el = SubElement(el, 'text:span',
                                attrib={'text:style-name':
                                        self.rststyle(inline_style)})
            count = len(styles)
        else:
            # No style was specified so use a default style (old code
            # crashed if no style was given)
            el = SubElement(self.current_element, 'text:span')
            count = 1

        self.set_current_element(el)
        self.inline_style_count_stack.append(count)

    def depart_inline(self, node):
        count = self.inline_style_count_stack.pop()
        for x in range(count):
            self.set_to_parent()

    def _calculate_code_block_padding(self, line):
        count = 0
        matchobj = SPACES_PATTERN.match(line)
        if matchobj:
            pad = matchobj.group()
            count = len(pad)
        else:
            matchobj = TABS_PATTERN.match(line)
            if matchobj:
                pad = matchobj.group()
                count = len(pad) * 8
        return count

    def _add_syntax_highlighting(self, insource, language):
        lexer = pygments.lexers.get_lexer_by_name(language, stripall=True)
        if language in ('latex', 'tex'):
            fmtr = OdtPygmentsLaTeXFormatter(
                lambda name, parameters=():
                self.rststyle(name, parameters),
                escape_function=escape_cdata)
        else:
            fmtr = OdtPygmentsProgFormatter(
                lambda name, parameters=():
                self.rststyle(name, parameters),
                escape_function=escape_cdata)
        outsource = pygments.highlight(insource, lexer, fmtr)
        return outsource

    def fill_line(self, line):
        line = FILL_PAT1.sub(self.fill_func1, line)
        line = FILL_PAT2.sub(self.fill_func2, line)
        return line

    def fill_func1(self, matchobj):
        spaces = matchobj.group(0)
        repl = '<text:s text:c="%d"/>' % (len(spaces), )
        return repl

    def fill_func2(self, matchobj):
        spaces = matchobj.group(0)
        repl = ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
        return repl

    def visit_literal_block(self, node):
        if len(self.paragraph_style_stack) > 1:
            wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
                self.rststyle('codeblock-indented'), )
        else:
            wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % (
                self.rststyle('codeblock'), )
        source = node.astext()
        if (
                pygments and
                self.settings.add_syntax_highlighting
                #and
                #node.get('hilight', False)
        ):
            language = node.get('language', 'python')
            source = self._add_syntax_highlighting(source, language)
        else:
            source = escape_cdata(source)
        lines = source.split('\n')
        # If there is an empty last line, remove it.
        if lines[-1] == '':
            del lines[-1]
        lines1 = ['<wrappertag1 xmlns:text="urn:oasis:names:tc:'
                  'opendocument:xmlns:text:1.0">']
        my_lines = []
        for my_line in lines:
            my_line = self.fill_line(my_line)
            my_line = my_line.replace("&#10;", "\n")
            my_lines.append(my_line)
        my_lines_str = '<text:line-break/>'.join(my_lines)
        my_lines_str2 = wrapper1 % (my_lines_str, )
        lines1.append(my_lines_str2)
        lines1.append('</wrappertag1>')
        s1 = ''.join(lines1)
        if WhichElementTree != "lxml":
            s1 = s1.encode("utf-8")
        el1 = etree.fromstring(s1)
        children = el1.getchildren()
        for child in children:
            self.current_element.append(child)

    def depart_literal_block(self, node):
        pass

    visit_doctest_block = visit_literal_block
    depart_doctest_block = depart_literal_block

    # placeholder for math (see docs/dev/todo.txt)
    def visit_math(self, node):
        self.document.reporter.warning('"math" role not supported',
                                       base_node=node)
        self.visit_literal(node)

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

    def visit_math_block(self, node):
        self.document.reporter.warning('"math" directive not supported',
                                       base_node=node)
        self.visit_literal_block(node)

    def depart_math_block(self, node):
        self.depart_literal_block(node)

    def visit_meta(self, node):
        name = node.attributes.get('name')
        content = node.attributes.get('content')
        if name is not None and content is not None:
            self.meta_dict[name] = content

    def depart_meta(self, node):
        pass

    def visit_option_list(self, node):
        table_name = 'tableoption'
        #
        # Generate automatic styles
        if not self.optiontablestyles_generated:
            self.optiontablestyles_generated = True
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle(table_name),
                'style:family': 'table'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-properties', attrib={
                'style:width': '17.59cm',
                'table:align': 'left',
                'style:shadow': 'none'}, nsdict=SNSD)
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle('%s.%%c' % table_name, ('A', )),
                'style:family': 'table-column'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-column-properties', attrib={
                'style:column-width': '4.999cm'}, nsdict=SNSD)
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle('%s.%%c' % table_name, ('B', )),
                'style:family': 'table-column'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-column-properties', attrib={
                'style:column-width': '12.587cm'}, nsdict=SNSD)
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle(
                    '%s.%%c%%d' % table_name, ('A', 1, )),
                'style:family': 'table-cell'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-cell-properties', attrib={
                'fo:background-color': 'transparent',
                'fo:padding': '0.097cm',
                'fo:border-left': '0.035cm solid #000000',
                'fo:border-right': 'none',
                'fo:border-top': '0.035cm solid #000000',
                'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
            el2 = SubElement(el1, 'style:background-image', nsdict=SNSD)
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle(
                    '%s.%%c%%d' % table_name, ('B', 1, )),
                'style:family': 'table-cell'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-cell-properties', attrib={
                'fo:padding': '0.097cm',
                'fo:border': '0.035cm solid #000000'}, nsdict=SNSD)
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle(
                    '%s.%%c%%d' % table_name, ('A', 2, )),
                'style:family': 'table-cell'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-cell-properties', attrib={
                'fo:padding': '0.097cm',
                'fo:border-left': '0.035cm solid #000000',
                'fo:border-right': 'none',
                'fo:border-top': 'none',
                'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
            el = SubElement(self.automatic_styles, 'style:style', attrib={
                'style:name': self.rststyle(
                    '%s.%%c%%d' % table_name, ('B', 2, )),
                'style:family': 'table-cell'}, nsdict=SNSD)
            el1 = SubElement(el, 'style:table-cell-properties', attrib={
                'fo:padding': '0.097cm',
                'fo:border-left': '0.035cm solid #000000',
                'fo:border-right': '0.035cm solid #000000',
                'fo:border-top': 'none',
                'fo:border-bottom': '0.035cm solid #000000'}, nsdict=SNSD)
        #
        # Generate table data
        el = self.append_child('table:table', attrib={
            'table:name': self.rststyle(table_name),
            'table:style-name': self.rststyle(table_name),
        })
        el1 = SubElement(el, 'table:table-column', attrib={
            'table:style-name': self.rststyle(
                '%s.%%c' % table_name, ('A', ))})
        el1 = SubElement(el, 'table:table-column', attrib={
            'table:style-name': self.rststyle(
                '%s.%%c' % table_name, ('B', ))})
        el1 = SubElement(el, 'table:table-header-rows')
        el2 = SubElement(el1, 'table:table-row')
        el3 = SubElement(el2, 'table:table-cell', attrib={
            'table:style-name': self.rststyle(
                '%s.%%c%%d' % table_name, ('A', 1, )),
            'office:value-type': 'string'})
        el4 = SubElement(el3, 'text:p', attrib={
            'text:style-name': 'Table_20_Heading'})
        el4.text = 'Option'
        el3 = SubElement(el2, 'table:table-cell', attrib={
            'table:style-name': self.rststyle(
                '%s.%%c%%d' % table_name, ('B', 1, )),
            'office:value-type': 'string'})
        el4 = SubElement(el3, 'text:p', attrib={
            'text:style-name': 'Table_20_Heading'})
        el4.text = 'Description'
        self.set_current_element(el)

    def depart_option_list(self, node):
        self.set_to_parent()

    def visit_option_list_item(self, node):
        el = self.append_child('table:table-row')
        self.set_current_element(el)

    def depart_option_list_item(self, node):
        self.set_to_parent()

    def visit_option_group(self, node):
        el = self.append_child('table:table-cell', attrib={
            'table:style-name': 'Table%d.A2' % self.table_count,
            'office:value-type': 'string',
        })
        self.set_current_element(el)

    def depart_option_group(self, node):
        self.set_to_parent()

    def visit_option(self, node):
        el = self.append_child('text:p', attrib={
            'text:style-name': 'Table_20_Contents'})
        el.text = node.astext()

    def depart_option(self, node):
        pass

    def visit_option_string(self, node):
        pass

    def depart_option_string(self, node):
        pass

    def visit_option_argument(self, node):
        pass

    def depart_option_argument(self, node):
        pass

    def visit_description(self, node):
        el = self.append_child('table:table-cell', attrib={
            'table:style-name': 'Table%d.B2' % self.table_count,
            'office:value-type': 'string',
        })
        el1 = SubElement(el, 'text:p', attrib={
            'text:style-name': 'Table_20_Contents'})
        el1.text = node.astext()
        raise nodes.SkipChildren()

    def depart_description(self, node):
        pass

    def visit_paragraph(self, node):
        self.in_paragraph = True
        if self.in_header:
            el = self.append_p('header')
        elif self.in_footer:
            el = self.append_p('footer')
        else:
            style_name = self.paragraph_style_stack[-1]
            el = self.append_child(
                'text:p',
                attrib={'text:style-name': style_name})
            self.append_pending_ids(el)
        self.set_current_element(el)

    def depart_paragraph(self, node):
        self.in_paragraph = False
        self.set_to_parent()
        if self.in_header:
            self.header_content.append(
                self.current_element.getchildren()[-1])
            self.current_element.remove(
                self.current_element.getchildren()[-1])
        elif self.in_footer:
            self.footer_content.append(
                self.current_element.getchildren()[-1])
            self.current_element.remove(
                self.current_element.getchildren()[-1])

    def visit_problematic(self, node):
        pass

    def depart_problematic(self, node):
        pass

    def visit_raw(self, node):
        if 'format' in node.attributes:
            formats = node.attributes['format']
            formatlist = formats.split()
            if 'odt' in formatlist:
                rawstr = node.astext()
                attrstr = ' '.join([
                    '%s="%s"' % (k, v, )
                    for k, v in list(CONTENT_NAMESPACE_ATTRIB.items())])
                contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, )
                if WhichElementTree != "lxml":
                    contentstr = contentstr.encode("utf-8")
                content = etree.fromstring(contentstr)
                elements = content.getchildren()
                if len(elements) > 0:
                    el1 = elements[0]
                    if self.in_header:
                        pass
                    elif self.in_footer:
                        pass
                    else:
                        self.current_element.append(el1)
        raise nodes.SkipChildren()

    def depart_raw(self, node):
        if self.in_header:
            pass
        elif self.in_footer:
            pass
        else:
            pass

    def visit_reference(self, node):
        #text = node.astext()
        if self.settings.create_links:
            if 'refuri' in node:
                href = node['refuri']
                if (
                        self.settings.cloak_email_addresses and
                        href.startswith('mailto:')):
                    href = self.cloak_mailto(href)
                el = self.append_child('text:a', attrib={
                    'xlink:href': '%s' % href,
                    'xlink:type': 'simple',
                })
                self.set_current_element(el)
            elif 'refid' in node:
                if self.settings.create_links:
                    href = node['refid']
                    el = self.append_child('text:reference-ref', attrib={
                        'text:ref-name': '%s' % href,
                        'text:reference-format': 'text',
                    })
            else:
                self.document.reporter.warning(
                    'References must have "refuri" or "refid" attribute.')
        if (
                self.in_table_of_contents and
                len(node.children) >= 1 and
                isinstance(node.children[0], docutils.nodes.generated)):
            node.remove(node.children[0])

    def depart_reference(self, node):
        if self.settings.create_links:
            if 'refuri' in node:
                self.set_to_parent()

    def visit_rubric(self, node):
        style_name = self.rststyle('rubric')
        classes = node.get('classes')
        if classes:
            class1 = classes[0]
            if class1:
                style_name = class1
        el = SubElement(self.current_element, 'text:h', attrib={
            #'text:outline-level': '%d' % section_level,
            #'text:style-name': 'Heading_20_%d' % section_level,
            'text:style-name': style_name,
        })
        text = node.astext()
        el.text = self.encode(text)

    def depart_rubric(self, node):
        pass

    def visit_section(self, node, move_ids=1):
        self.section_level += 1
        self.section_count += 1
        if self.settings.create_sections:
            el = self.append_child('text:section', attrib={
                'text:name': 'Section%d' % self.section_count,
                'text:style-name': 'Sect%d' % self.section_level,
            })
            self.set_current_element(el)

    def depart_section(self, node):
        self.section_level -= 1
        if self.settings.create_sections:
            self.set_to_parent()

    def visit_strong(self, node):
        el = SubElement(self.current_element, 'text:span',
                        attrib={'text:style-name': self.rststyle('strong')})
        self.set_current_element(el)

    def depart_strong(self, node):
        self.set_to_parent()

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

    def depart_substitution_definition(self, node):
        pass

    def visit_system_message(self, node):
        pass

    def depart_system_message(self, node):
        pass

    def get_table_style(self, node):
        table_style = None
        table_name = None
        str_classes = node.get('classes')
        if str_classes is not None:
            for str_class in str_classes:
                if str_class.startswith(TABLESTYLEPREFIX):
                    table_name = str_class
                    break
        if table_name is not None:
            table_style = self.table_styles.get(table_name)
            if table_style is None:
                # If we can't find the table style, issue warning
                #   and use the default table style.
                self.document.reporter.warning(
                    'Can\'t find table style "%s".  Using default.' % (
                        table_name, ))
                table_name = TABLENAMEDEFAULT
                table_style = self.table_styles.get(table_name)
                if table_style is None:
                    # If we can't find the default table style, issue a warning
                    #   and use a built-in default style.
                    self.document.reporter.warning(
                        'Can\'t find default table style "%s".  '
                        'Using built-in default.' % (
                            table_name, ))
                    table_style = BUILTIN_DEFAULT_TABLE_STYLE
        else:
            table_name = TABLENAMEDEFAULT
            table_style = self.table_styles.get(table_name)
            if table_style is None:
                # If we can't find the default table style, issue a warning
                #   and use a built-in default style.
                self.document.reporter.warning(
                    'Can\'t find default table style "%s".  '
                    'Using built-in default.' % (
                        table_name, ))
                table_style = BUILTIN_DEFAULT_TABLE_STYLE
        return table_style

    def visit_table(self, node):
        self.table_count += 1
        table_style = self.get_table_style(node)
        table_name = '%s%%d' % TABLESTYLEPREFIX
        el1 = SubElement(self.automatic_styles, 'style:style', attrib={
            'style:name': self.rststyle(
                '%s' % table_name, (self.table_count, )),
            'style:family': 'table',
        }, nsdict=SNSD)
        if table_style.backgroundcolor is None:
            SubElement(el1, 'style:table-properties', attrib={
                #'style:width': '17.59cm',
                #'table:align': 'margins',
                'table:align': 'left',
                'fo:margin-top': '0in',
                'fo:margin-bottom': '0.10in',
            }, nsdict=SNSD)
        else:
            SubElement(el1, 'style:table-properties', attrib={
                #'style:width': '17.59cm',
                'table:align': 'margins',
                'fo:margin-top': '0in',
                'fo:margin-bottom': '0.10in',
                'fo:background-color': table_style.backgroundcolor,
            }, nsdict=SNSD)
        # We use a single cell style for all cells in this table.
        # That's probably not correct, but seems to work.
        el2 = SubElement(self.automatic_styles, 'style:style', attrib={
            'style:name': self.rststyle(
                '%s.%%c%%d' % table_name, (self.table_count, 'A', 1, )),
            'style:family': 'table-cell',
        }, nsdict=SNSD)
        thickness = self.settings.table_border_thickness
        if thickness is None:
            line_style1 = table_style.border
        else:
            line_style1 = '0.%03dcm solid #000000' % (thickness, )
        SubElement(el2, 'style:table-cell-properties', attrib={
            'fo:padding': '0.049cm',
            'fo:border-left': line_style1,
            'fo:border-right': line_style1,
            'fo:border-top': line_style1,
            'fo:border-bottom': line_style1,
        }, nsdict=SNSD)
        title = None
        for child in node.children:
            if child.tagname == 'title':
                title = child.astext()
                break
        if title is not None:
            self.append_p('table-title', title)
        else:
            pass
        el4 = SubElement(self.current_element, 'table:table', attrib={
            'table:name': self.rststyle(
                '%s' % table_name, (self.table_count, )),
            'table:style-name': self.rststyle(
                '%s' % table_name, (self.table_count, )),
        })
        self.set_current_element(el4)
        self.current_table_style = el1
        self.table_width = 0.0

    def depart_table(self, node):
        attribkey = add_ns('style:width', nsdict=SNSD)
        attribval = '%.4fin' % (self.table_width, )
        el1 = self.current_table_style
        el2 = el1[0]
        el2.attrib[attribkey] = attribval
        self.set_to_parent()

    def visit_tgroup(self, node):
        self.column_count = ord('A') - 1

    def depart_tgroup(self, node):
        pass

    def visit_colspec(self, node):
        self.column_count += 1
        colspec_name = self.rststyle(
            '%s%%d.%%s' % TABLESTYLEPREFIX,
            (self.table_count, chr(self.column_count), )
        )
        colwidth = node['colwidth'] / 12.0
        el1 = SubElement(self.automatic_styles, 'style:style', attrib={
            'style:name': colspec_name,
            'style:family': 'table-column',
        }, nsdict=SNSD)
        SubElement(el1, 'style:table-column-properties',
                   attrib={'style:column-width': '%.4fin' % colwidth},
                   nsdict=SNSD)
        self.append_child('table:table-column',
                          attrib={'table:style-name': colspec_name, })
        self.table_width += colwidth

    def depart_colspec(self, node):
        pass

    def visit_thead(self, node):
        el = self.append_child('table:table-header-rows')
        self.set_current_element(el)
        self.in_thead = True
        self.paragraph_style_stack.append('Table_20_Heading')

    def depart_thead(self, node):
        self.set_to_parent()
        self.in_thead = False
        self.paragraph_style_stack.pop()

    def visit_row(self, node):
        self.column_count = ord('A') - 1
        el = self.append_child('table:table-row')
        self.set_current_element(el)

    def depart_row(self, node):
        self.set_to_parent()

    def visit_entry(self, node):
        self.column_count += 1
        cellspec_name = self.rststyle(
            '%s%%d.%%c%%d' % TABLESTYLEPREFIX,
            (self.table_count, 'A', 1, )
        )
        attrib = {
            'table:style-name': cellspec_name,
            'office:value-type': 'string',
        }
        morecols = node.get('morecols', 0)
        if morecols > 0:
            attrib['table:number-columns-spanned'] = '%d' % (morecols + 1,)
            self.column_count += morecols
        morerows = node.get('morerows', 0)
        if morerows > 0:
            attrib['table:number-rows-spanned'] = '%d' % (morerows + 1,)
        el1 = self.append_child('table:table-cell', attrib=attrib)
        self.set_current_element(el1)

    def depart_entry(self, node):
        self.set_to_parent()

    def visit_tbody(self, node):
        pass

    def depart_tbody(self, node):
        pass

    def visit_target(self, node):
        #
        # I don't know how to implement targets in ODF.
        # How do we create a target in oowriter?  A cross-reference?
        if not ('refuri' in node or
                'refid' in node or
                'refname' in node):
            pass
        else:
            pass

    def depart_target(self, node):
        pass

    def visit_title(self, node, move_ids=1, title_type='title'):
        if isinstance(node.parent, docutils.nodes.section):
            section_level = self.section_level
            if section_level > 7:
                self.document.reporter.warning(
                    'Heading/section levels greater than 7 not supported.')
                self.document.reporter.warning(
                    '    Reducing to heading level 7 for heading: "%s"' % (
                        node.astext(), ))
                section_level = 7
            el1 = self.append_child(
                'text:h', attrib={
                    'text:outline-level': '%d' % section_level,
                    #'text:style-name': 'Heading_20_%d' % section_level,
                    'text:style-name': self.rststyle(
                        'heading%d', (section_level, )),
                })
            self.append_pending_ids(el1)
            self.set_current_element(el1)
        elif isinstance(node.parent, docutils.nodes.document):
            #    text = self.settings.title
            #else:
            #    text = node.astext()
            el1 = SubElement(self.current_element, 'text:p', attrib={
                'text:style-name': self.rststyle(title_type),
            })
            self.append_pending_ids(el1)
            text = node.astext()
            self.title = text
            self.found_doc_title = True
            self.set_current_element(el1)

    def depart_title(self, node):
        if (
                isinstance(node.parent, docutils.nodes.section) or
                isinstance(node.parent, docutils.nodes.document)):
            self.set_to_parent()

    def visit_subtitle(self, node, move_ids=1):
        self.visit_title(node, move_ids, title_type='subtitle')

    def depart_subtitle(self, node):
        self.depart_title(node)

    def visit_title_reference(self, node):
        el = self.append_child('text:span', attrib={
            'text:style-name': self.rststyle('quotation')})
        el.text = self.encode(node.astext())
        raise nodes.SkipChildren()

    def depart_title_reference(self, node):
        pass

    def generate_table_of_content_entry_template(self, el1):
        for idx in range(1, 11):
            el2 = SubElement(
                el1,
                'text:table-of-content-entry-template',
                attrib={
                    'text:outline-level': "%d" % (idx, ),
                    'text:style-name': self.rststyle('contents-%d' % (idx, )),
                })
            SubElement(el2, 'text:index-entry-chapter')
            SubElement(el2, 'text:index-entry-text')
            SubElement(el2, 'text:index-entry-tab-stop', attrib={
                'style:leader-char': ".",
                'style:type': "right",
            })
            SubElement(el2, 'text:index-entry-page-number')

    def find_title_label(self, node, class_type, label_key):
        label = ''
        title_node = None
        for child in node.children:
            if isinstance(child, class_type):
                title_node = child
                break
        if title_node is not None:
            label = title_node.astext()
        else:
            label = self.language.labels[label_key]
        return label

    def visit_topic(self, node):
        if 'classes' in node.attributes:
            if 'contents' in node.attributes['classes']:
                label = self.find_title_label(
                    node, docutils.nodes.title, 'contents')
                if self.settings.generate_oowriter_toc:
                    el1 = self.append_child('text:table-of-content', attrib={
                        'text:name': 'Table of Contents1',
                        'text:protected': 'true',
                        'text:style-name': 'Sect1',
                    })
                    el2 = SubElement(
                        el1,
                        'text:table-of-content-source',
                        attrib={
                            'text:outline-level': '10',
                        })
                    el3 = SubElement(el2, 'text:index-title-template', attrib={
                        'text:style-name': 'Contents_20_Heading',
                    })
                    el3.text = label
                    self.generate_table_of_content_entry_template(el2)
                    el4 = SubElement(el1, 'text:index-body')
                    el5 = SubElement(el4, 'text:index-title')
                    el6 = SubElement(el5, 'text:p', attrib={
                        'text:style-name': self.rststyle('contents-heading'),
                    })
                    el6.text = label
                    self.save_current_element = self.current_element
                    self.table_of_content_index_body = el4
                    self.set_current_element(el4)
                else:
                    el = self.append_p('horizontalline')
                    el = self.append_p('centeredtextbody')
                    el1 = SubElement(
                        el, 'text:span',
                        attrib={'text:style-name': self.rststyle('strong')})
                    el1.text = label
                self.in_table_of_contents = True
            elif 'abstract' in node.attributes['classes']:
                el = self.append_p('horizontalline')
                el = self.append_p('centeredtextbody')
                el1 = SubElement(
                    el, 'text:span',
                    attrib={'text:style-name': self.rststyle('strong')})
                label = self.find_title_label(
                    node, docutils.nodes.title,
                    'abstract')
                el1.text = label
            elif 'dedication' in node.attributes['classes']:
                el = self.append_p('horizontalline')
                el = self.append_p('centeredtextbody')
                el1 = SubElement(
                    el, 'text:span',
                    attrib={'text:style-name': self.rststyle('strong')})
                label = self.find_title_label(
                    node, docutils.nodes.title,
                    'dedication')
                el1.text = label

    def depart_topic(self, node):
        if 'classes' in node.attributes:
            if 'contents' in node.attributes['classes']:
                if self.settings.generate_oowriter_toc:
                    self.update_toc_page_numbers(
                        self.table_of_content_index_body)
                    self.set_current_element(self.save_current_element)
                else:
                    self.append_p('horizontalline')
                self.in_table_of_contents = False

    def update_toc_page_numbers(self, el):
        collection = []
        self.update_toc_collect(el, 0, collection)
        self.update_toc_add_numbers(collection)

    def update_toc_collect(self, el, level, collection):
        collection.append((level, el))
        level += 1
        for child_el in el.getchildren():
            if child_el.tag != 'text:index-body':
                self.update_toc_collect(child_el, level, collection)

    def update_toc_add_numbers(self, collection):
        for level, el1 in collection:
            if (
                    el1.tag == 'text:p' and
                    el1.text != 'Table of Contents'):
                el2 = SubElement(el1, 'text:tab')
                el2.tail = '9999'

    def visit_transition(self, node):
        self.append_p('horizontalline')

    def depart_transition(self, node):
        pass

    #
    # Admonitions
    #
    def visit_warning(self, node):
        self.generate_admonition(node, 'warning')

    def depart_warning(self, node):
        self.paragraph_style_stack.pop()

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

    depart_attention = depart_warning

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

    depart_caution = depart_warning

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

    depart_danger = depart_warning

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

    depart_error = depart_warning

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

    depart_hint = depart_warning

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

    depart_important = depart_warning

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

    depart_note = depart_warning

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

    depart_tip = depart_warning

    def visit_admonition(self, node):
        title = None
        for child in node.children:
            if child.tagname == 'title':
                title = child.astext()
        if title is None:
            classes1 = node.get('classes')
            if classes1:
                title = classes1[0]
        self.generate_admonition(node, 'generic', title)

    depart_admonition = depart_warning

    def generate_admonition(self, node, label, title=None):
        if hasattr(self.language, 'labels'):
            translated_label = self.language.labels.get(label, label)
        else:
            translated_label = label
        el1 = SubElement(self.current_element, 'text:p', attrib={
            'text:style-name': self.rststyle(
                'admon-%s-hdr', (label, )),
        })
        if title:
            el1.text = title
        else:
            el1.text = '%s!' % (translated_label.capitalize(), )
        s1 = self.rststyle('admon-%s-body', (label, ))
        self.paragraph_style_stack.append(s1)

    #
    # Roles (e.g. subscript, superscript, strong, ...
    #
    def visit_subscript(self, node):
        el = self.append_child('text:span', attrib={
            'text:style-name': 'rststyle-subscript',
        })
        self.set_current_element(el)

    def depart_subscript(self, node):
        self.set_to_parent()

    def visit_superscript(self, node):
        el = self.append_child('text:span', attrib={
            'text:style-name': 'rststyle-superscript',
        })
        self.set_current_element(el)

    def depart_superscript(self, node):
        self.set_to_parent()

    def visit_abbreviation(self, node):
        pass

    def depart_abbreviation(self, node):
        pass

    def visit_acronym(self, node):
        pass

    def depart_acronym(self, node):
        pass

    def visit_sidebar(self, node):
        pass

    def depart_sidebar(self, node):
        pass


# Use an own reader to modify transformations done.
class Reader(standalone.Reader):

    def get_transforms(self):
        default = standalone.Reader.get_transforms(self)
        if self.settings.create_links:
            return default
        return [i for i in default
                if i is not references.DanglingReferences]
