aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/docutils/writers/odf_odt
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils/writers/odf_odt')
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/__init__.py3461
-rwxr-xr-x.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/prepstyles.py78
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/pygmentsformatter.py109
-rw-r--r--.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/styles.odtbin0 -> 16500 bytes
4 files changed, 3648 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/__init__.py
new file mode 100644
index 00000000..c538af34
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/__init__.py
@@ -0,0 +1,3461 @@
+# $Id: __init__.py 9541 2024-02-17 10:37:13Z milde $
+# Author: Dave Kuhlman <dkuhlman@davekuhlman.org>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Open Document Format (ODF) Writer.
+
+This module is provisional:
+the API is not settled and may change with any minor Docutils version.
+"""
+
+__docformat__ = 'reStructuredText'
+
+
+from configparser import ConfigParser
+import copy
+from io import StringIO
+import itertools
+import locale
+import os
+import os.path
+from pathlib import Path
+import re
+import subprocess
+import tempfile
+import time
+import urllib
+import weakref
+from xml.etree import ElementTree as etree
+from xml.dom import minidom
+import zipfile
+
+import docutils
+from docutils import frontend, nodes, utils, writers, languages
+from docutils.parsers.rst.directives.images import PIL # optional
+from docutils.readers import standalone
+from docutils.transforms import references
+
+# Import pygments and odtwriter pygments formatters if possible.
+try:
+ import pygments
+ import pygments.lexers
+ from .pygmentsformatter import (OdtPygmentsProgFormatter,
+ OdtPygmentsLaTeXFormatter)
+except (ImportError, SyntaxError):
+ pygments = 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.')
+
+VERSION = '1.0a'
+
+IMAGE_NAME_COUNTER = itertools.count()
+
+
+#
+# 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.
+#
+_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, 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)
+ return _ElementInterfaceWrapper(tag, attrib)
+
+
+def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD):
+ if attrib is None:
+ attrib = {}
+ tag, attrib = fix_ns(tag, attrib, nsdict)
+ 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):
+ return tag
+
+
+def ToString(et):
+ outstream = StringIO()
+ et.write(outstream, encoding="unicode")
+ 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
+
+
+#
+# Classes
+#
+
+
+class TableStyle:
+ 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:
+ 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 an ODF-specific configuration/mapping file '
+ 'relative to the current working directory.',
+ ['--odf-config-file'],
+ {'metavar': '<file>'}),
+ ('Obfuscate email addresses to confuse harvesters.',
+ ['--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,
+ 'metavar': '<int>',
+ '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 a native ODF table of contents.',
+ ['--generate-list-toc'],
+ {'action': 'store_false',
+ 'dest': 'generate_oowriter_toc',
+ 'validator': frontend.validate_boolean}),
+ ('Generate a native ODF 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',
+ 'metavar': '<custom header>'}),
+ ('Specify the contents of an custom footer line. '
+ 'See ODF/ODT writer documentation for details.',
+ ['--custom-odt-footer'],
+ {'default': '',
+ 'dest': 'custom_footer',
+ 'metavar': '<custom footer>'}),
+ )
+ )
+
+ settings_defaults = {
+ 'output_encoding_error_handler': 'xmlcharrefreplace',
+ }
+
+ relative_path_settings = ('odf_config_file', 'stylesheet',)
+
+ 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.
+ """
+ return self.visitor.setup_page()
+
+ 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):
+ 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)
+ return doc.toprettyxml(' ')
+
+ def create_meta(self):
+ 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]'
+ for prop, value in self.visitor.get_meta_dict().items():
+ # 'keywords', 'description', and 'subject' have their own fields:
+ if prop == 'keywords':
+ keywords = re.split(', *', value)
+ for keyword in keywords:
+ el1 = SubElement(root, 'meta:keyword', nsdict=METNSD)
+ el1.text = keyword
+ elif prop == 'description':
+ el1 = SubElement(root, 'dc:description', nsdict=METNSD)
+ el1.text = value
+ elif prop == 'subject':
+ el1 = SubElement(root, 'dc:subject', nsdict=METNSD)
+ el1.text = value
+ else: # Store remaining properties as custom/user-defined
+ el1 = SubElement(root, 'meta:user-defined',
+ attrib={'meta:name': prop}, nsdict=METNSD)
+ el1.text = value
+ 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:
+ 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, ))
+ self.format_map[rststyle] = format
+ self.section_level = 0
+ self.section_count = 0
+ # Create ElementTree content and styles documents.
+ 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':
+ with open(stylespath, 'r', encoding='utf-8') as stylesfile:
+ s1 = stylesfile.read()
+ 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
+ else:
+ for child in el:
+ el1 = self.find_first_text_p(child)
+ if el1 is not None:
+ return el1
+ 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
+ return self.format_map.get(name1, 'rststyle-%s' % name1)
+
+ 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)
+ return etree.tostring(self.dom_stylesheet)
+
+ def get_dom_stylesheet(self):
+ return self.dom_stylesheet
+
+ def setup_paper(self, root_el):
+ # TODO: only call paperconf, if it is actually used
+ # (i.e. page size removed from "styles.odt" with rst2odt_prepstyles.py
+ # cf. conditional in walk() below)?
+ try:
+ dimensions = subprocess.check_output(('paperconf', '-s'),
+ stderr=subprocess.STDOUT)
+ w, h = (float(s) for s in dimensions.split())
+ except (subprocess.CalledProcessError, FileNotFoundError, ValueError):
+ self.document.reporter.info(
+ 'Cannot use `paperconf`, defaulting to Letter.')
+ w, h = 612, 792 # default to Letter
+
+ 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:
+ 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:
+ 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:
+ 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)
+ return ToString(et)
+
+ 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:
+ if len(el2) > 0: # and 'id' in el2.attrib:
+ child = el2[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
+ return SubElement(parent, tag, attrib)
+
+ 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
+ return self.append_p('blockindent')
+
+ 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):
+ return text.replace('\n', " ")
+
+ #
+ # 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) > 0:
+ if self.current_element[-1].tail:
+ self.current_element[-1].tail += text
+ else:
+ self.current_element[-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()
+
+ 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):
+ if len(self.current_element) > 0:
+ el = self.current_element[-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.
+ source = node['uri']
+ uri_parts = urllib.parse.urlparse(source)
+ if uri_parts.scheme in ('', 'file'):
+ source = urllib.parse.unquote(uri_parts.path)
+ if source.startswith('/'):
+ root_prefix = Path(self.settings.root_prefix)
+ source = (root_prefix/source[1:]).as_posix()
+ else:
+ # adapt relative paths
+ docsource, line = utils.get_source_line(node)
+ if docsource:
+ dirname = os.path.dirname(docsource)
+ if dirname:
+ source = os.path.join(dirname, source)
+ if not self.check_file_exists(source):
+ self.document.reporter.warning(
+ f'Cannot find image file "{source}".')
+ 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 uri_parts.scheme in ('', 'file'):
+ spec = (os.path.abspath(source), destination,)
+ else:
+ try:
+ with urllib.request.urlopen(source) as imgfile:
+ content = imgfile.read()
+ except urllib.error.URLError as err:
+ self.document.reporter.warning(
+ f'Cannot open image URL "{source}". {err}')
+ return
+ with tempfile.NamedTemporaryFile('wb',
+ delete=False) as imgfile2:
+ imgfile2.write(content)
+ source = imgfile2.name
+ spec = (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]
+ with PIL.Image.open(filename, 'r') as img:
+ img_size = img.size
+ dpi = img.info.get('dpi', dpi)
+ # dpi information can be (xdpi, ydpi) or xydpi
+ try:
+ iter(dpi)
+ except TypeError:
+ dpi = (dpi, dpi)
+ else:
+ img_size = None
+ if width is None or height is None:
+ if img_size is None:
+ raise RuntimeError(
+ 'image size not fully specified and PIL not installed')
+ if width is None:
+ width = img_size[0]
+ width = float(width) * 0.026 # convert px to cm
+ if height is None:
+ height = img_size[1]
+ height = float(height) * 0.026 # convert px to cm
+ if width_unit == '%':
+ factor = width
+ image_width = img_size[0]
+ image_width = float(image_width) * 0.026 # convert px to cm
+ image_height = img_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)
+ return pygments.highlight(insource, lexer, fmtr)
+
+ def fill_line(self, line):
+ line = FILL_PAT1.sub(self.fill_func1, line)
+ return FILL_PAT2.sub(self.fill_func2, line)
+
+ def fill_func1(self, matchobj):
+ spaces = matchobj.group(0)
+ return '<text:s text:c="%d"/>' % (len(spaces), )
+
+ def fill_func2(self, matchobj):
+ spaces = matchobj.group(0)
+ return ' <text:s text:c="%d"/>' % (len(spaces) - 1, )
+
+ 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):
+ 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)
+ s1 = s1.encode("utf-8")
+ el1 = etree.fromstring(s1)
+ for child in el1:
+ 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[-1])
+ self.current_element.remove(self.current_element[-1])
+ elif self.in_footer:
+ self.footer_content.append(self.current_element[-1])
+ self.current_element.remove(self.current_element[-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, )
+ contentstr = contentstr.encode("utf-8")
+ content = etree.fromstring(contentstr)
+ if len(content) > 0:
+ el1 = content[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 ('refuri' not in node
+ and 'refid' not in node
+ and 'refname' not 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:
+ 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):
+ transforms = super().get_transforms()
+ if not self.settings.create_links:
+ transforms.remove(references.DanglingReferences)
+ return transforms
diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/prepstyles.py b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/prepstyles.py
new file mode 100755
index 00000000..b59490f2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/prepstyles.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+# $Id: prepstyles.py 9386 2023-05-16 14:49:31Z milde $
+# Author: Dave Kuhlman <dkuhlman@rexx.com>
+# Copyright: This module has been placed in the public domain.
+
+"""
+Adapt a word-processor-generated styles.odt for odtwriter use:
+
+Drop page size specifications from styles.xml in STYLE_FILE.odt.
+See https://docutils.sourceforge.io/docs/user/odt.html#page-size
+"""
+
+# Author: Michael Schutte <michi@uiae.at>
+
+from xml.etree import ElementTree as etree
+
+import sys
+import zipfile
+from tempfile import mkstemp
+import shutil
+import os
+
+NAMESPACES = {
+ "style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0",
+ "fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
+}
+
+
+def prepstyle(filename):
+
+ zin = zipfile.ZipFile(filename)
+ styles = zin.open("styles.xml")
+
+ root = None
+ # some extra effort to preserve namespace prefixes
+ for event, elem in etree.iterparse(styles, events=("start", "start-ns")):
+ if event == "start-ns":
+ etree.register_namespace(elem[0], elem[1])
+ elif event == "start":
+ if root is None:
+ root = elem
+
+ styles.close()
+
+ for el in root.findall(".//style:page-layout-properties",
+ namespaces=NAMESPACES):
+ for attr in list(el.attrib):
+ if attr.startswith("{%s}" % NAMESPACES["fo"]):
+ del el.attrib[attr]
+
+ tempname = mkstemp()
+ zout = zipfile.ZipFile(os.fdopen(tempname[0], "wb"), "w",
+ zipfile.ZIP_DEFLATED)
+
+ for item in zin.infolist():
+ if item.filename == "styles.xml":
+ zout.writestr(item, etree.tostring(root, encoding="UTF-8"))
+ else:
+ zout.writestr(item, zin.read(item.filename))
+
+ zout.close()
+ zin.close()
+ shutil.move(tempname[1], filename)
+
+
+def main():
+ args = sys.argv[1:]
+ if len(args) != 1 or args[0] in ('-h', '--help'):
+ print(__doc__, file=sys.stderr)
+ print("Usage: %s STYLE_FILE.odt\n" % sys.argv[0], file=sys.stderr)
+ sys.exit(1)
+ filename = args[0]
+ prepstyle(filename)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/pygmentsformatter.py b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/pygmentsformatter.py
new file mode 100644
index 00000000..7880651b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/pygmentsformatter.py
@@ -0,0 +1,109 @@
+# $Id: pygmentsformatter.py 9015 2022-03-03 22:15:00Z milde $
+# Author: Dave Kuhlman <dkuhlman@rexx.com>
+# Copyright: This module has been placed in the public domain.
+
+"""
+
+Additional support for Pygments formatter.
+
+"""
+
+
+import pygments
+import pygments.formatter
+
+
+class OdtPygmentsFormatter(pygments.formatter.Formatter):
+ def __init__(self, rststyle_function, escape_function):
+ pygments.formatter.Formatter.__init__(self)
+ self.rststyle_function = rststyle_function
+ self.escape_function = escape_function
+
+ def rststyle(self, name, parameters=()):
+ return self.rststyle_function(name, parameters)
+
+
+class OdtPygmentsProgFormatter(OdtPygmentsFormatter):
+ def format(self, tokensource, outfile):
+ tokenclass = pygments.token.Token
+ for ttype, value in tokensource:
+ value = self.escape_function(value)
+ if ttype == tokenclass.Keyword:
+ s2 = self.rststyle('codeblock-keyword')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Literal.String:
+ s2 = self.rststyle('codeblock-string')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype in (
+ tokenclass.Literal.Number.Integer,
+ tokenclass.Literal.Number.Integer.Long,
+ tokenclass.Literal.Number.Float,
+ tokenclass.Literal.Number.Hex,
+ tokenclass.Literal.Number.Oct,
+ tokenclass.Literal.Number,
+ ):
+ s2 = self.rststyle('codeblock-number')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Operator:
+ s2 = self.rststyle('codeblock-operator')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Comment:
+ s2 = self.rststyle('codeblock-comment')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Name.Class:
+ s2 = self.rststyle('codeblock-classname')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Name.Function:
+ s2 = self.rststyle('codeblock-functionname')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Name:
+ s2 = self.rststyle('codeblock-name')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ else:
+ s1 = value
+ outfile.write(s1)
+
+
+class OdtPygmentsLaTeXFormatter(OdtPygmentsFormatter):
+ def format(self, tokensource, outfile):
+ tokenclass = pygments.token.Token
+ for ttype, value in tokensource:
+ value = self.escape_function(value)
+ if ttype == tokenclass.Keyword:
+ s2 = self.rststyle('codeblock-keyword')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype in (tokenclass.Literal.String,
+ tokenclass.Literal.String.Backtick,
+ ):
+ s2 = self.rststyle('codeblock-string')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Name.Attribute:
+ s2 = self.rststyle('codeblock-operator')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Comment:
+ if value[-1] == '\n':
+ s2 = self.rststyle('codeblock-comment')
+ s1 = '<text:span text:style-name="%s">%s</text:span>\n' % \
+ (s2, value[:-1], )
+ else:
+ s2 = self.rststyle('codeblock-comment')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ elif ttype == tokenclass.Name.Builtin:
+ s2 = self.rststyle('codeblock-name')
+ s1 = '<text:span text:style-name="%s">%s</text:span>' % \
+ (s2, value, )
+ else:
+ s1 = value
+ outfile.write(s1)
diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/styles.odt b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/styles.odt
new file mode 100644
index 00000000..e17b0072
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/styles.odt
Binary files differ