diff options
author | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
---|---|---|
committer | S. Solomon Darnell | 2025-03-28 21:52:21 -0500 |
commit | 4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch) | |
tree | ee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/docutils/writers | |
parent | cc961e04ba734dd72309fb548a2f97d67d578813 (diff) | |
download | gn-ai-master.tar.gz |
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils/writers')
54 files changed, 17027 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/__init__.py new file mode 100644 index 00000000..eb6d3d27 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/__init__.py @@ -0,0 +1,159 @@ +# $Id: __init__.py 9368 2023-04-28 21:26:36Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This package contains Docutils Writer modules. +""" + +__docformat__ = 'reStructuredText' + +from importlib import import_module + +import docutils +from docutils import languages, Component +from docutils.transforms import universal + + +class Writer(Component): + + """ + Abstract base class for docutils Writers. + + Each writer module or package must export a subclass also called 'Writer'. + Each writer must support all standard node types listed in + `docutils.nodes.node_class_names`. + + The `write()` method is the main entry point. + """ + + component_type = 'writer' + config_section = 'writers' + + def get_transforms(self): + return super().get_transforms() + [universal.Messages, + universal.FilterMessages, + universal.StripClassesAndElements] + + document = None + """The document to write (Docutils doctree); set by `write()`.""" + + output = None + """Final translated form of `document` + + (`str` for text, `bytes` for binary formats); set by `translate()`. + """ + + language = None + """Language module for the document; set by `write()`.""" + + destination = None + """`docutils.io` Output object; where to write the document. + + Set by `write()`. + """ + + def __init__(self): + + self.parts = {} + """Mapping of document part names to fragments of `self.output`. + + See `Writer.assemble_parts()` below and + <https://docutils.sourceforge.io/docs/api/publisher.html>. + """ + + def write(self, document, destination): + """ + Process a document into its final form. + + Translate `document` (a Docutils document tree) into the Writer's + native format, and write it out to its `destination` (a + `docutils.io.Output` subclass object). + + Normally not overridden or extended in subclasses. + """ + self.document = document + self.language = languages.get_language( + document.settings.language_code, + document.reporter) + self.destination = destination + self.translate() + return self.destination.write(self.output) + + def translate(self): + """ + Do final translation of `self.document` into `self.output`. Called + from `write`. Override in subclasses. + + Usually done with a `docutils.nodes.NodeVisitor` subclass, in + combination with a call to `docutils.nodes.Node.walk()` or + `docutils.nodes.Node.walkabout()`. The ``NodeVisitor`` subclass must + support all standard elements (listed in + `docutils.nodes.node_class_names`) and possibly non-standard elements + used by the current Reader as well. + """ + raise NotImplementedError('subclass must override this method') + + def assemble_parts(self): + """Assemble the `self.parts` dictionary. Extend in subclasses. + + See <https://docutils.sourceforge.io/docs/api/publisher.html>. + """ + self.parts['whole'] = self.output + self.parts['encoding'] = self.document.settings.output_encoding + self.parts['errors'] = ( + self.document.settings.output_encoding_error_handler) + self.parts['version'] = docutils.__version__ + + +class UnfilteredWriter(Writer): + + """ + A writer that passes the document tree on unchanged (e.g. a + serializer.) + + Documents written by UnfilteredWriters are typically reused at a + later date using a subclass of `readers.ReReader`. + """ + + def get_transforms(self): + # Do not add any transforms. When the document is reused + # later, the then-used writer will add the appropriate + # transforms. + return Component.get_transforms(self) + + +_writer_aliases = { + 'html': 'html4css1', # may change to html5 some day + 'html4': 'html4css1', + 'xhtml10': 'html4css1', + 'html5': 'html5_polyglot', + 'xhtml': 'html5_polyglot', + 's5': 's5_html', + 'latex': 'latex2e', + 'xelatex': 'xetex', + 'luatex': 'xetex', + 'lualatex': 'xetex', + 'odf': 'odf_odt', + 'odt': 'odf_odt', + 'ooffice': 'odf_odt', + 'openoffice': 'odf_odt', + 'libreoffice': 'odf_odt', + 'pprint': 'pseudoxml', + 'pformat': 'pseudoxml', + 'pdf': 'rlpdf', + 'xml': 'docutils_xml'} + + +def get_writer_class(writer_name): + """Return the Writer class from the `writer_name` module.""" + name = writer_name.lower() + name = _writer_aliases.get(name, name) + try: + module = import_module('docutils.writers.'+name) + except ImportError: + try: + module = import_module(name) + except ImportError as err: + raise ImportError(f'Writer "{writer_name}" not found. {err}') + return module.Writer diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/_html_base.py b/.venv/lib/python3.12/site-packages/docutils/writers/_html_base.py new file mode 100644 index 00000000..8122f4b2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/_html_base.py @@ -0,0 +1,1887 @@ +#!/usr/bin/env python3 +# :Author: David Goodger, Günter Milde +# Based on the html4css1 writer by David Goodger. +# :Maintainer: docutils-develop@lists.sourceforge.net +# :Revision: $Revision: 9614 $ +# :Date: $Date: 2005-06-28$ +# :Copyright: © 2016 David Goodger, Günter Milde +# :License: Released under the terms of the `2-Clause BSD license`_, in short: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause + +"""common definitions for Docutils HTML writers""" + +import base64 +import mimetypes +import os +import os.path +from pathlib import Path +import re +import urllib +import warnings +import xml.etree.ElementTree as ET # TODO: lazy import in prepare_svg()? + +import docutils +from docutils import frontend, languages, nodes, utils, writers +from docutils.parsers.rst.directives import length_or_percentage_or_unitless +from docutils.parsers.rst.directives.images import PIL +from docutils.transforms import writer_aux +from docutils.utils.math import (latex2mathml, math2html, tex2mathml_extern, + unichar2tex, wrap_math_code, MathError) + + +class Writer(writers.Writer): + + supported = ('html', 'xhtml') # update in subclass + """Formats this writer supports.""" + + settings_spec = ( + 'HTML Writer Options', + None, + (('Specify the template file (UTF-8 encoded). ' + '(default: writer dependent)', + ['--template'], + {'metavar': '<file>'}), + ('Comma separated list of stylesheet URLs. ' + 'Overrides previous --stylesheet and --stylesheet-path settings.', + ['--stylesheet'], + {'metavar': '<URL[,URL,...]>', 'overrides': 'stylesheet_path', + 'validator': frontend.validate_comma_separated_list}), + ('Comma separated list of stylesheet paths. ' + 'Relative paths are expanded if a matching file is found in ' + 'the --stylesheet-dirs. With --link-stylesheet, ' + 'the path is rewritten relative to the output HTML file. ' + '(default: writer dependent)', + ['--stylesheet-path'], + {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', + 'validator': frontend.validate_comma_separated_list}), + ('Comma-separated list of directories where stylesheets are found. ' + 'Used by --stylesheet-path when expanding relative path arguments. ' + '(default: writer dependent)', + ['--stylesheet-dirs'], + {'metavar': '<dir[,dir,...]>', + 'validator': frontend.validate_comma_separated_list}), + ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' + 'files must be accessible during processing. (default)', + ['--embed-stylesheet'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Link to the stylesheet(s) in the output HTML file. ', + ['--link-stylesheet'], + {'dest': 'embed_stylesheet', 'action': 'store_false'}), + ('Specify the initial header level. ' + 'Does not affect document title & subtitle (see --no-doc-title).' + '(default: writer dependent).', + ['--initial-header-level'], + {'choices': '1 2 3 4 5 6'.split(), 'default': '2', + 'metavar': '<level>'}), + ('Format for footnote references: one of "superscript" or ' + '"brackets". (default: "brackets")', + ['--footnote-references'], + {'choices': ['superscript', 'brackets'], 'default': 'brackets', + 'metavar': '<format>', + 'overrides': 'trim_footnote_reference_space'}), + ('Format for block quote attributions: ' + 'one of "dash" (em-dash prefix), "parentheses"/"parens", or "none". ' + '(default: "dash")', + ['--attribution'], + {'choices': ['dash', 'parentheses', 'parens', 'none'], + 'default': 'dash', 'metavar': '<format>'}), + ('Remove extra vertical whitespace between items of "simple" bullet ' + 'lists and enumerated lists. (default)', + ['--compact-lists'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Disable compact simple bullet and enumerated lists.', + ['--no-compact-lists'], + {'dest': 'compact_lists', 'action': 'store_false'}), + ('Remove extra vertical whitespace between items of simple field ' + 'lists. (default)', + ['--compact-field-lists'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Disable compact simple field lists.', + ['--no-compact-field-lists'], + {'dest': 'compact_field_lists', 'action': 'store_false'}), + ('Added to standard table classes. ' + 'Defined styles: borderless, booktabs, ' + 'align-left, align-center, align-right, ' + 'colwidths-auto, colwidths-grid.', + ['--table-style'], + {'default': ''}), + ('Math output format (one of "MathML", "HTML", "MathJax", ' + 'or "LaTeX") and option(s). ' + '(default: "HTML math.css")', + ['--math-output'], + {'default': 'HTML math.css', + 'validator': frontend.validate_math_output}), + ('Prepend an XML declaration. ', + ['--xml-declaration'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Omit the XML declaration.', + ['--no-xml-declaration'], + {'dest': 'xml_declaration', 'action': 'store_false'}), + ('Obfuscate email addresses to confuse harvesters while still ' + 'keeping email links usable with standards-compliant browsers.', + ['--cloak-email-addresses'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ) + ) + + settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'} + + relative_path_settings = ('template',) + + config_section = 'html base writer' # overwrite in subclass + config_section_dependencies = ('writers', 'html writers') + + visitor_attributes = ( + 'head_prefix', 'head', 'stylesheet', 'body_prefix', + 'body_pre_docinfo', 'docinfo', 'body', 'body_suffix', + 'title', 'subtitle', 'header', 'footer', 'meta', 'fragment', + 'html_prolog', 'html_head', 'html_title', 'html_subtitle', + 'html_body') + + def get_transforms(self): + return super().get_transforms() + [writer_aux.Admonitions] + + def translate(self): + self.visitor = visitor = self.translator_class(self.document) + self.document.walkabout(visitor) + for attr in self.visitor_attributes: + setattr(self, attr, getattr(visitor, attr)) + self.output = self.apply_template() + + def apply_template(self): + with open(self.document.settings.template, encoding='utf-8') as fp: + template = fp.read() + subs = self.interpolation_dict() + return template % subs + + def interpolation_dict(self): + subs = {} + settings = self.document.settings + for attr in self.visitor_attributes: + subs[attr] = ''.join(getattr(self, attr)).rstrip('\n') + subs['encoding'] = settings.output_encoding + subs['version'] = docutils.__version__ + return subs + + def assemble_parts(self): + writers.Writer.assemble_parts(self) + for part in self.visitor_attributes: + self.parts[part] = ''.join(getattr(self, part)) + + +class HTMLTranslator(nodes.NodeVisitor): + + """ + Generic Docutils to HTML translator. + + See the `html4css1` and `html5_polyglot` writers for full featured + HTML writers. + + .. IMPORTANT:: + The `visit_*` and `depart_*` methods use a + heterogeneous stack, `self.context`. + When subclassing, make sure to be consistent in its use! + + Examples for robust coding: + + a) Override both `visit_*` and `depart_*` methods, don't call the + parent functions. + + b) Extend both and unconditionally call the parent functions:: + + def visit_example(self, node): + if foo: + self.body.append('<div class="foo">') + html4css1.HTMLTranslator.visit_example(self, node) + + def depart_example(self, node): + html4css1.HTMLTranslator.depart_example(self, node) + if foo: + self.body.append('</div>') + + c) Extend both, calling the parent functions under the same + conditions:: + + def visit_example(self, node): + if foo: + self.body.append('<div class="foo">\n') + else: # call the parent method + _html_base.HTMLTranslator.visit_example(self, node) + + def depart_example(self, node): + if foo: + self.body.append('</div>\n') + else: # call the parent method + _html_base.HTMLTranslator.depart_example(self, node) + + d) Extend one method (call the parent), but don't otherwise use the + `self.context` stack:: + + def depart_example(self, node): + _html_base.HTMLTranslator.depart_example(self, node) + if foo: + # implementation-specific code + # that does not use `self.context` + self.body.append('</div>\n') + + This way, changes in stack use will not bite you. + """ + + doctype = '<!DOCTYPE html>\n' + doctype_mathml = doctype + + head_prefix_template = ('<html xmlns="http://www.w3.org/1999/xhtml"' + ' xml:lang="%(lang)s" lang="%(lang)s">\n<head>\n') + content_type = '<meta charset="%s" />\n' + generator = ( + f'<meta name="generator" content="Docutils {docutils.__version__}: ' + 'https://docutils.sourceforge.io/" />\n') + # `starttag()` arguments for the main document (HTML5 uses <main>) + documenttag_args = {'tagname': 'div', 'CLASS': 'document'} + + # Template for the MathJax script in the header: + mathjax_script = '<script type="text/javascript" src="%s"></script>\n' + + mathjax_url = 'file:/usr/share/javascript/mathjax/MathJax.js' + """ + URL of the MathJax javascript library. + + The MathJax library ought to be installed on the same + server as the rest of the deployed site files and specified + in the `math-output` setting appended to "mathjax". + See `Docutils Configuration`__. + + __ https://docutils.sourceforge.io/docs/user/config.html#math-output + + The fallback tries a local MathJax installation at + ``/usr/share/javascript/mathjax/MathJax.js``. + """ + + stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n' + embedded_stylesheet = '<style type="text/css">\n\n%s\n</style>\n' + words_and_spaces = re.compile(r'[^ \n]+| +|\n') + # wrap point inside word: + in_word_wrap_point = re.compile(r'.+\W\W.+|[-?].+') + lang_attribute = 'lang' # name changes to 'xml:lang' in XHTML 1.1 + + special_characters = {ord('&'): '&', + ord('<'): '<', + ord('"'): '"', + ord('>'): '>', + ord('@'): '@', # may thwart address harvesters + } + """Character references for characters with a special meaning in HTML.""" + + videotypes = ('video/mp4', 'video/webm', 'video/ogg') + """MIME types supported by the HTML5 <video> element.""" + + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + # process settings + self.settings = settings = document.settings + self.language = languages.get_language( + settings.language_code, document.reporter) + self.initial_header_level = int(settings.initial_header_level) + # image_loading (only defined for HTML5 writer) + _image_loading_default = 'link' + # convert legacy setting embed_images: + if getattr(settings, 'embed_images', None) is not None: + if settings.embed_images: + _image_loading_default = 'embed' + warnings.warn('The configuration setting "embed_images"\n' + ' will be removed in Docutils 2.0. ' + f'Use "image_loading: {_image_loading_default}".', + FutureWarning, stacklevel=8) + self.image_loading = getattr(settings, + 'image_loading', _image_loading_default) + # backwards compatibiltiy: validate/convert programatically set strings + if isinstance(self.settings.math_output, str): + self.settings.math_output = frontend.validate_math_output( + self.settings.math_output) + (self.math_output, + self.math_options) = self.settings.math_output + + # set up "parts" (cf. docs/api/publisher.html#publish-parts-details) + # + self.body = [] # equivalent to `fragment`, ≠ `html_body` + self.body_prefix = ['</head>\n<body>\n'] # + optional header + self.body_pre_docinfo = [] # document heading (title and subtitle) + self.body_suffix = ['</body>\n</html>\n'] # + optional footer + self.docinfo = [] + self.footer = [] + self.fragment = [] # main content of the document ("naked" body) + self.head = [] + self.head_prefix = [] # everything up to and including <head> + self.header = [] + self.html_body = [] + self.html_head = [self.content_type] # charset not interpolated + self.html_prolog = [] + self.html_subtitle = [] + self.html_title = [] + self.meta = [self.generator] + self.stylesheet = [self.stylesheet_call(path) + for path in utils.get_stylesheet_list(settings)] + self.title = [] + self.subtitle = [] + if settings.xml_declaration: + self.head_prefix.append( + utils.xml_declaration(settings.output_encoding)) + self.html_prolog.append( + utils.xml_declaration('%s')) # encoding not interpolated + if (settings.output_encoding + and settings.output_encoding.lower() != 'unicode'): + self.meta.insert(0, self.content_type % settings.output_encoding) + + # bookkeeping attributes; reflect state of translator + # + self.context = [] + """Heterogeneous stack. + + Used by visit_* and depart_* functions in conjunction with the tree + traversal. Make sure that the pops correspond to the pushes. + """ + self.section_level = 0 + self.colspecs = [] + self.compact_p = True + self.compact_simple = False + self.compact_field_list = False + self.in_docinfo = False + self.in_sidebar = False + self.in_document_title = 0 # len(self.body) or 0 + self.in_mailto = False + self.author_in_authors = False # for html4css1 + self.math_header = [] + self.messages = [] + """Queue of system_message nodes (writing issues). + + Call `report_messages()` in `depart_*_block()` methods to clean up! + """ + + def astext(self): + return ''.join(self.head_prefix + self.head + + self.stylesheet + self.body_prefix + + self.body_pre_docinfo + self.docinfo + + self.body + self.body_suffix) + + def attval(self, text, + whitespace=re.compile('[\n\r\t\v\f]')): + """Cleanse, HTML encode, and return attribute value text.""" + encoded = self.encode(whitespace.sub(' ', text)) + if self.in_mailto and self.settings.cloak_email_addresses: + # Cloak at-signs ("%40") and periods with HTML entities. + encoded = encoded.replace('%40', '%40') + encoded = encoded.replace('.', '.') + return encoded + + def cloak_email(self, addr): + """Try to hide the link text of a email link from harversters.""" + # Surround at-signs and periods with <span> tags. ("@" has + # already been encoded to "@" by the `encode` method.) + addr = addr.replace('@', '<span>@</span>') + return addr.replace('.', '<span>.</span>') + + def cloak_mailto(self, uri): + """Try to hide a mailto: URL from harvesters.""" + # Encode "@" using a URL octet reference (see RFC 1738). + # Further cloaking with HTML entities will be done in the + # `attval` function. + return uri.replace('@', '%40') + + def encode(self, text): + """Encode special characters in `text` & return.""" + # Use only named entities known in both XML and HTML + # other characters are automatically encoded "by number" if required. + # @@@ A codec to do these and all other HTML entities would be nice. + text = str(text) + return text.translate(self.special_characters) + + def image_size(self, node): + # Determine the image size from the node arguments or the image file. + # Return a size declaration suitable as "style" argument value, + # e.g., ``'width: 4px; height: 2em;'``. + # TODO: consider feature-request #102? + size = [node.get('width', None), node.get('height', None)] + if 'scale' in node: + if 'width' not in node or 'height' not in node: + # try reading size from image file + reading_problems = [] + uri = node['uri'] + if not PIL: + reading_problems.append('Requires Python Imaging Library.') + if mimetypes.guess_type(uri)[0] in self.videotypes: + reading_problems.append('PIL cannot read video images.') + if not self.settings.file_insertion_enabled: + reading_problems.append('Reading external files disabled.') + if not reading_problems: + try: + imagepath = self.uri2imagepath(uri) + with PIL.Image.open(imagepath) as img: + imgsize = img.size + except (ValueError, OSError, UnicodeEncodeError) as err: + reading_problems.append(str(err)) + else: + self.settings.record_dependencies.add( + imagepath.replace('\\', '/')) + if reading_problems: + msg = ['Cannot scale image!', + f'Could not get size from "{uri}":', + *reading_problems] + self.messages.append(self.document.reporter.warning( + '\n '.join(msg), base_node=node)) + else: + for i in range(2): + size[i] = size[i] or '%dpx' % imgsize[i] + # scale provided/determined size values: + factor = float(node['scale']) / 100 + for i in range(2): + if size[i]: + match = re.match(r'([0-9.]+)(\S*)$', size[i]) + size[i] = '%s%s' % (factor * float(match.group(1)), + match.group(2)) + size_declarations = [] + for i, dimension in enumerate(('width', 'height')): + if size[i]: + # Interpret unitless values as pixels: + if re.match(r'^[0-9.]+$', size[i]): + size[i] += 'px' + size_declarations.append(f'{dimension}: {size[i]};') + return ' '.join(size_declarations) + + def prepare_svg(self, node, imagedata, size_declaration): + # Edit `imagedata` for embedding as SVG image. + # Use ElementTree to add node attributes. + # ET also removes comments and preamble code. + # + # Provisional: + # interface and behaviour may change without notice. + + # SVG namespace + svg_ns = {'': 'http://www.w3.org/2000/svg', + 'xlink': 'http://www.w3.org/1999/xlink'} + # don't add SVG namespace to all elements + ET.register_namespace('', svg_ns['']) + ET.register_namespace('xlink', svg_ns['xlink']) + try: + svg = ET.fromstring(imagedata.decode('utf-8')) + except ET.ParseError as err: + self.messages.append(self.document.reporter.error( + f'Cannot parse SVG image "{node["uri"]}":\n {err}', + base_node=node)) + return imagedata.decode('utf-8') + # apply image node attributes: + if size_declaration: # append to style, replacing width & height + declarations = [d.strip() for d in svg.get('style', '').split(';')] + declarations = [d for d in declarations + if d + and not d.startswith('width') + and not d.startswith('height')] + svg.set('style', '; '.join(declarations+[size_declaration])) + if node['classes'] or 'align' in node: + classes = svg.get('class', '').split() + classes += node.get('classes', []) + if 'align' in node: + classes.append(f'align-{node["align"]}') + svg.set('class', ' '.join(classes)) + if 'alt' in node and svg.find('title', svg_ns) is None: + svg_title = ET.Element('title') + svg_title.text = node['alt'] + svg.insert(0, svg_title) + return ET.tostring(svg, encoding='unicode') + + def stylesheet_call(self, path, adjust_path=None): + """Return code to reference or embed stylesheet file `path`""" + if adjust_path is None: + adjust_path = bool(self.settings.stylesheet_path) + if self.settings.embed_stylesheet: + try: + with open(path, encoding='utf-8') as f: + content = f.read() + except OSError as err: + msg = f'Cannot embed stylesheet: {err}' + self.document.reporter.error(msg) + return '<--- %s --->\n' % msg + else: + self.settings.record_dependencies.add(path) + return self.embedded_stylesheet % content + # else link to style file: + if adjust_path: + # rewrite path relative to output (cf. config.html#stylesheet-path) + path = utils.relative_path(self.settings._destination, path) + return self.stylesheet_link % self.encode(path) + + def starttag(self, node, tagname, suffix='\n', empty=False, **attributes): + """ + Construct and return a start tag given a node (id & class attributes + are extracted), tag name, and optional attributes. + """ + tagname = tagname.lower() + prefix = [] + atts = {} + for (name, value) in attributes.items(): + atts[name.lower()] = value + classes = atts.pop('classes', []) + languages = [] + # unify class arguments and move language specification + for cls in node.get('classes', []) + atts.pop('class', '').split(): + if cls.startswith('language-'): + languages.append(cls[9:]) + elif cls.strip() and cls not in classes: + classes.append(cls) + if languages: + # attribute name is 'lang' in XHTML 1.0 but 'xml:lang' in 1.1 + atts[self.lang_attribute] = languages[0] + # filter classes that are processed by the writer: + internal = ('colwidths-auto', 'colwidths-given', 'colwidths-grid') + if isinstance(node, nodes.table): + classes = [cls for cls in classes if cls not in internal] + if classes: + atts['class'] = ' '.join(classes) + assert 'id' not in atts + ids = node.get('ids', []) + ids.extend(atts.pop('ids', [])) + if ids: + atts['id'] = ids[0] + for id in ids[1:]: + # Add empty "span" elements for additional IDs. Note + # that we cannot use empty "a" elements because there + # may be targets inside of references, but nested "a" + # elements aren't allowed in XHTML (even if they do + # not all have a "href" attribute). + if empty or isinstance(node, (nodes.Sequential, + nodes.docinfo, + nodes.table)): + # Insert target right in front of element. + prefix.append('<span id="%s"></span>' % id) + else: + # Non-empty tag. Place the auxiliary <span> tag + # *inside* the element, as the first child. + suffix += '<span id="%s"></span>' % id + attlist = sorted(atts.items()) + parts = [tagname] + for name, value in attlist: + # value=None was used for boolean attributes without + # value, but this isn't supported by XHTML. + assert value is not None + if isinstance(value, list): + values = [str(v) for v in value] + parts.append('%s="%s"' % (name.lower(), + self.attval(' '.join(values)))) + else: + parts.append('%s="%s"' % (name.lower(), + self.attval(str(value)))) + if empty: + infix = ' /' + else: + infix = '' + return ''.join(prefix) + '<%s%s>' % (' '.join(parts), infix) + suffix + + def emptytag(self, node, tagname, suffix='\n', **attributes): + """Construct and return an XML-compatible empty tag.""" + return self.starttag(node, tagname, suffix, empty=True, **attributes) + + def report_messages(self, node): + if isinstance(node.parent, (nodes.system_message, nodes.entry)): + return + while self.messages: + message = self.messages.pop(0) + if self.settings.report_level <= message['level']: + message.walkabout(self) + + def set_class_on_child(self, node, class_, index=0): + """ + Set class `class_` on the visible child no. index of `node`. + Do nothing if node has fewer children than `index`. + """ + children = [n for n in node if not isinstance(n, nodes.Invisible)] + try: + child = children[index] + except IndexError: + return + child['classes'].append(class_) + + def uri2imagepath(self, uri): + """Get filesystem path corresponding to an URI. + + The image directive expects an image URI. Some writers require the + corresponding image path to read the image size from the file or to + embed the image in the output. + + Absolute URIs consider the "root_prefix" setting. + + In order to work in the output document, relative image URIs relate + to the output directory. For access by the writer, the corresponding + image path must be relative to the current working directory. + + Provisional: the function's location, interface and behaviour + may change without advance warning. + """ + destination = self.settings._destination or '' + uri_parts = urllib.parse.urlparse(uri) + if uri_parts.scheme not in ('', 'file'): + raise ValueError('Can only read local images.') + imagepath = urllib.request.url2pathname(uri_parts.path) + if imagepath.startswith('/'): + root_prefix = Path(self.settings.root_prefix) + imagepath = (root_prefix/imagepath[1:]).as_posix() + elif not os.path.isabs(imagepath): # exclude absolute Windows paths + destdir = os.path.abspath(os.path.dirname(destination)) + imagepath = utils.relative_path(None, + os.path.join(destdir, imagepath)) + return imagepath + + def visit_Text(self, node): + text = node.astext() + encoded = self.encode(text) + if self.in_mailto and self.settings.cloak_email_addresses: + encoded = self.cloak_email(encoded) + self.body.append(encoded) + + def depart_Text(self, node): + pass + + def visit_abbreviation(self, node): + # @@@ implementation incomplete ("title" attribute) + self.body.append(self.starttag(node, 'abbr', '')) + + def depart_abbreviation(self, node): + self.body.append('</abbr>') + + def visit_acronym(self, node): + # @@@ implementation incomplete ("title" attribute) + self.body.append(self.starttag(node, 'acronym', '')) + + def depart_acronym(self, node): + self.body.append('</acronym>') + + def visit_address(self, node): + self.visit_docinfo_item(node, 'address', meta=False) + self.body.append(self.starttag(node, 'pre', + suffix='', CLASS='address')) + + def depart_address(self, node): + self.body.append('\n</pre>\n') + self.depart_docinfo_item() + + def visit_admonition(self, node): + self.body.append(self.starttag(node, 'aside', classes=['admonition'])) + + def depart_admonition(self, node=None): + self.body.append('</aside>\n') + + attribution_formats = {'dash': ('\u2014', ''), + 'parentheses': ('(', ')'), + 'parens': ('(', ')'), + 'none': ('', '')} + + def visit_attribution(self, node): + prefix, suffix = self.attribution_formats[self.settings.attribution] + self.context.append(suffix) + self.body.append( + self.starttag(node, 'p', prefix, CLASS='attribution')) + + def depart_attribution(self, node): + self.body.append(self.context.pop() + '</p>\n') + + def visit_author(self, node): + if not isinstance(node.parent, nodes.authors): + self.visit_docinfo_item(node, 'author') + self.body.append('<p>') + + def depart_author(self, node): + self.body.append('</p>') + if isinstance(node.parent, nodes.authors): + self.body.append('\n') + else: + self.depart_docinfo_item() + + def visit_authors(self, node): + self.visit_docinfo_item(node, 'authors') + + def depart_authors(self, node): + self.depart_docinfo_item() + + def visit_block_quote(self, node): + self.body.append(self.starttag(node, 'blockquote')) + + def depart_block_quote(self, node): + self.body.append('</blockquote>\n') + + def check_simple_list(self, node): + """Check for a simple list that can be rendered compactly.""" + visitor = SimpleListChecker(self.document) + try: + node.walk(visitor) + except nodes.NodeFound: + return False + else: + return True + + # Compact lists + # ------------ + # Include definition lists and field lists (in addition to ordered + # and unordered lists) in the test if a list is "simple" (cf. the + # html4css1.HTMLTranslator docstring and the SimpleListChecker class at + # the end of this file). + + def is_compactable(self, node): + # explicit class arguments have precedence + if 'compact' in node['classes']: + return True + if 'open' in node['classes']: + return False + # check config setting: + if (isinstance(node, (nodes.field_list, nodes.definition_list)) + and not self.settings.compact_field_lists): + return False + if (isinstance(node, (nodes.enumerated_list, nodes.bullet_list)) + and not self.settings.compact_lists): + return False + # Table of Contents: + if 'contents' in node.parent['classes']: + return True + # check the list items: + return self.check_simple_list(node) + + def visit_bullet_list(self, node): + atts = {} + old_compact_simple = self.compact_simple + self.context.append((self.compact_simple, self.compact_p)) + self.compact_p = None + self.compact_simple = self.is_compactable(node) + if self.compact_simple and not old_compact_simple: + atts['class'] = 'simple' + self.body.append(self.starttag(node, 'ul', **atts)) + + def depart_bullet_list(self, node): + self.compact_simple, self.compact_p = self.context.pop() + self.body.append('</ul>\n') + + def visit_caption(self, node): + self.body.append(self.starttag(node, 'p', '', CLASS='caption')) + + def depart_caption(self, node): + self.body.append('</p>\n') + + # Use semantic tag and DPub role (HTML4 uses a table) + def visit_citation(self, node): + # role 'doc-bibloentry' requires wrapping in an element with + # role 'list' and an element with role 'doc-bibliography' + # https://www.w3.org/TR/dpub-aria-1.0/#doc-biblioentry) + if not isinstance(node.previous_sibling(), type(node)): + self.body.append('<div role="list" class="citation-list">\n') + self.body.append(self.starttag(node, 'div', classes=[node.tagname], + role="doc-biblioentry")) + + def depart_citation(self, node): + self.body.append('</div>\n') + if not isinstance(node.next_node(descend=False, siblings=True), + type(node)): + self.body.append('</div>\n') + + # Use DPub role (overwritten in HTML4) + def visit_citation_reference(self, node): + href = '#' + if 'refid' in node: + href += node['refid'] + elif 'refname' in node: + href += self.document.nameids[node['refname']] + # else: # TODO system message (or already in the transform)? + # 'Citation reference missing.' + self.body.append(self.starttag(node, 'a', suffix='[', href=href, + classes=['citation-reference'], + role='doc-biblioref')) + + def depart_citation_reference(self, node): + self.body.append(']</a>') + + def visit_classifier(self, node): + self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) + + def depart_classifier(self, node): + self.body.append('</span>') + self.depart_term(node) # close the term element after last classifier + + def visit_colspec(self, node): + self.colspecs.append(node) + # "stubs" list is an attribute of the tgroup element: + node.parent.stubs.append(node.attributes.get('stub')) + + def depart_colspec(self, node): + # write out <colgroup> when all colspecs are processed + if isinstance(node.next_node(descend=False, siblings=True), + nodes.colspec): + return + if 'colwidths-auto' in node.parent.parent['classes'] or ( + 'colwidths-grid' not in self.settings.table_style + and 'colwidths-given' not in node.parent.parent['classes']): + return + self.body.append(self.starttag(node, 'colgroup')) + total_width = sum(node['colwidth'] for node in self.colspecs) + for node in self.colspecs: + colwidth = node['colwidth'] / total_width + self.body.append(self.emptytag(node, 'col', + style=f'width: {colwidth:.1%}')) + self.body.append('</colgroup>\n') + + def visit_comment(self, node, + sub=re.compile('-(?=-)').sub): + """Escape double-dashes in comment text.""" + self.body.append('<!-- %s -->\n' % sub('- ', node.astext())) + # Content already processed: + raise nodes.SkipNode + + def visit_compound(self, node): + self.body.append(self.starttag(node, 'div', CLASS='compound')) + + def depart_compound(self, node): + self.body.append('</div>\n') + + def visit_container(self, node): + self.body.append(self.starttag(node, 'div', + CLASS='docutils container')) + + def depart_container(self, node): + self.body.append('</div>\n') + + def visit_contact(self, node): + self.visit_docinfo_item(node, 'contact', meta=False) + + def depart_contact(self, node): + self.depart_docinfo_item() + + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright') + + def depart_copyright(self, node): + self.depart_docinfo_item() + + def visit_date(self, node): + self.visit_docinfo_item(node, 'date') + + def depart_date(self, node): + self.depart_docinfo_item() + + def visit_decoration(self, node): + pass + + def depart_decoration(self, node): + pass + + def visit_definition(self, node): + if 'details' not in node.parent.parent['classes']: + self.body.append(self.starttag(node, 'dd', '')) + + def depart_definition(self, node): + if 'details' not in node.parent.parent['classes']: + self.body.append('</dd>\n') + + def visit_definition_list(self, node): + if 'details' in node['classes']: + self.body.append(self.starttag(node, 'div')) + else: + classes = ['simple'] if self.is_compactable(node) else [] + self.body.append(self.starttag(node, 'dl', classes=classes)) + + def depart_definition_list(self, node): + if 'details' in node['classes']: + self.body.append('</div>\n') + else: + self.body.append('</dl>\n') + + # Use a "details" disclosure element if parent has "class" arg "details". + def visit_definition_list_item(self, node): + if 'details' in node.parent['classes']: + atts = {} + if "open" in node.parent['classes']: + atts['open'] = 'open' + self.body.append(self.starttag(node, 'details', **atts)) + + def depart_definition_list_item(self, node): + if 'details' in node.parent['classes']: + self.body.append('</details>\n') + + def visit_description(self, node): + self.body.append(self.starttag(node, 'dd', '')) + + def depart_description(self, node): + self.body.append('</dd>\n') + + def visit_docinfo(self, node): + self.context.append(len(self.body)) + classes = ['docinfo'] + if self.is_compactable(node): + classes.append('simple') + self.body.append(self.starttag(node, 'dl', classes=classes)) + + def depart_docinfo(self, node): + self.body.append('</dl>\n') + start = self.context.pop() + self.docinfo = self.body[start:] + self.body = [] + + def visit_docinfo_item(self, node, name, meta=True): + if meta: + self.meta.append(f'<meta name="{name}" ' + f'content="{self.attval(node.astext())}" />\n') + self.body.append(f'<dt class="{name}">{self.language.labels[name]}' + '<span class="colon">:</span></dt>\n') + self.body.append(self.starttag(node, 'dd', '', CLASS=name)) + + def depart_docinfo_item(self): + self.body.append('</dd>\n') + + def visit_doctest_block(self, node): + self.body.append(self.starttag(node, 'pre', suffix='', + classes=['code', 'python', 'doctest'])) + + def depart_doctest_block(self, node): + self.body.append('\n</pre>\n') + + def visit_document(self, node): + title = (node.get('title') or os.path.basename(node['source']) + or 'untitled Docutils document') + self.head.append(f'<title>{self.encode(title)}</title>\n') + + def depart_document(self, node): + self.head_prefix.extend([self.doctype, + self.head_prefix_template % + {'lang': self.settings.language_code}]) + self.html_prolog.append(self.doctype) + self.head = self.meta[:] + self.head + if 'name="dcterms.' in ''.join(self.meta): + self.head.append('<link rel="schema.dcterms"' + ' href="http://purl.org/dc/terms/"/>') + if self.math_header: + if self.math_output == 'mathjax': + self.head.extend(self.math_header) + else: + self.stylesheet.extend(self.math_header) + # skip content-type meta tag with interpolated charset value: + self.html_head.extend(self.head[1:]) + self.body_prefix.append(self.starttag(node, **self.documenttag_args)) + self.body_suffix.insert(0, f'</{self.documenttag_args["tagname"]}>\n') + self.fragment.extend(self.body) # self.fragment is the "naked" body + self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo + + self.docinfo + self.body + + self.body_suffix[:-1]) + assert not self.context, f'len(context) = {len(self.context)}' + + def visit_emphasis(self, node): + self.body.append(self.starttag(node, 'em', '')) + + def depart_emphasis(self, node): + self.body.append('</em>') + + def visit_entry(self, node): + atts = {'classes': []} + if isinstance(node.parent.parent, nodes.thead): + atts['classes'].append('head') + if node.parent.parent.parent.stubs[node.parent.column]: + # "stubs" list is an attribute of the tgroup element + atts['classes'].append('stub') + if atts['classes']: + tagname = 'th' + else: + tagname = 'td' + node.parent.column += 1 + if 'morerows' in node: + atts['rowspan'] = node['morerows'] + 1 + if 'morecols' in node: + atts['colspan'] = node['morecols'] + 1 + node.parent.column += node['morecols'] + self.body.append(self.starttag(node, tagname, '', **atts)) + self.context.append('</%s>\n' % tagname.lower()) + + def depart_entry(self, node): + self.body.append(self.context.pop()) + + def visit_enumerated_list(self, node): + atts = {'classes': []} + if 'start' in node: + atts['start'] = node['start'] + if 'enumtype' in node: + atts['classes'].append(node['enumtype']) + if self.is_compactable(node): + atts['classes'].append('simple') + self.body.append(self.starttag(node, 'ol', **atts)) + + def depart_enumerated_list(self, node): + self.body.append('</ol>\n') + + def visit_field_list(self, node): + atts = {} + classes = node.setdefault('classes', []) + for i, cls in enumerate(classes): + if cls.startswith('field-indent-'): + try: + indent_length = length_or_percentage_or_unitless( + cls[13:], 'px') + except ValueError: + break + atts['style'] = '--field-indent: %s;' % indent_length + classes.pop(i) + break + classes.append('field-list') + if self.is_compactable(node): + classes.append('simple') + self.body.append(self.starttag(node, 'dl', **atts)) + + def depart_field_list(self, node): + self.body.append('</dl>\n') + + def visit_field(self, node): + # Insert children (<field_name> and <field_body>) directly. + # Transfer "id" attribute to the <field_name> child node. + for child in node: + if isinstance(child, nodes.field_name): + child['ids'].extend(node['ids']) + + def depart_field(self, node): + pass + + # as field is ignored, pass class arguments to field-name and field-body: + def visit_field_name(self, node): + self.body.append(self.starttag(node, 'dt', '', + classes=node.parent['classes'])) + + def depart_field_name(self, node): + self.body.append('<span class="colon">:</span></dt>\n') + + def visit_field_body(self, node): + self.body.append(self.starttag(node, 'dd', '', + classes=node.parent['classes'])) + # prevent misalignment of following content if the field is empty: + if not node.children: + self.body.append('<p></p>') + + def depart_field_body(self, node): + self.body.append('</dd>\n') + + def visit_figure(self, node): + atts = {'class': 'figure'} + if node.get('width'): + atts['style'] = 'width: %s' % node['width'] + if node.get('align'): + atts['class'] += " align-" + node['align'] + self.body.append(self.starttag(node, 'div', **atts)) + + def depart_figure(self, node): + self.body.append('</div>\n') + + def visit_footer(self, node): + self.context.append(len(self.body)) + + def depart_footer(self, node): + start = self.context.pop() + footer = [self.starttag(node, 'div', CLASS='footer'), + '<hr class="footer" />\n'] + footer.extend(self.body[start:]) + footer.append('\n</div>\n') + self.footer.extend(footer) + self.body_suffix[:0] = footer + del self.body[start:] + + def visit_footnote(self, node): + # No native HTML element: use <aside> with ARIA role + # (html4css1 uses tables). + # Wrap groups of footnotes for easier styling. + label_style = self.settings.footnote_references # brackets/superscript + if not isinstance(node.previous_sibling(), type(node)): + self.body.append(f'<aside class="footnote-list {label_style}">\n') + self.body.append(self.starttag(node, 'aside', + classes=[node.tagname, label_style], + role="doc-footnote")) + + def depart_footnote(self, node): + self.body.append('</aside>\n') + if not isinstance(node.next_node(descend=False, siblings=True), + type(node)): + self.body.append('</aside>\n') + + def visit_footnote_reference(self, node): + href = '#' + node['refid'] + classes = [self.settings.footnote_references] + self.body.append(self.starttag(node, 'a', suffix='', classes=classes, + role='doc-noteref', href=href)) + self.body.append('<span class="fn-bracket">[</span>') + + def depart_footnote_reference(self, node): + self.body.append('<span class="fn-bracket">]</span>') + self.body.append('</a>') + + # Docutils-generated text: put section numbers in a span for CSS styling: + def visit_generated(self, node): + if 'sectnum' in node['classes']: + # get section number (strip trailing no-break-spaces) + sectnum = node.astext().rstrip(' ') + self.body.append('<span class="sectnum">%s </span>' + % self.encode(sectnum)) + # Content already processed: + raise nodes.SkipNode + + def depart_generated(self, node): + pass + + def visit_header(self, node): + self.context.append(len(self.body)) + + def depart_header(self, node): + start = self.context.pop() + header = [self.starttag(node, 'div', CLASS='header')] + header.extend(self.body[start:]) + header.append('\n<hr class="header"/>\n</div>\n') + self.body_prefix.extend(header) + self.header.extend(header) + del self.body[start:] + + def visit_image(self, node): + # reference/embed images (still images and videos) + uri = node['uri'] + alt = node.get('alt', uri) + mimetype = mimetypes.guess_type(uri)[0] + element = '' # the HTML element (including potential children) + atts = {} # attributes for the HTML tag + # alignment is handled by CSS rules + if 'align' in node: + atts['class'] = 'align-%s' % node['align'] + # set size with "style" attribute (more universal, accepts dimensions) + size_declaration = self.image_size(node) + if size_declaration: + atts['style'] = size_declaration + + # ``:loading:`` option (embed, link, lazy), default from setting, + # exception: only embed videos if told via directive option + loading = 'link' if mimetype in self.videotypes else self.image_loading + loading = node.get('loading', loading) + if loading == 'lazy': + atts['loading'] = 'lazy' + elif loading == 'embed': + try: + imagepath = self.uri2imagepath(uri) + with open(imagepath, 'rb') as imagefile: + imagedata = imagefile.read() + except (ValueError, OSError) as err: + self.messages.append(self.document.reporter.error( + f'Cannot embed image "{uri}":\n {err}', base_node=node)) + # TODO: get external files with urllib.request (cf. odtwriter)? + else: + self.settings.record_dependencies.add(imagepath) + if mimetype == 'image/svg+xml': + element = self.prepare_svg(node, imagedata, + size_declaration) + else: + data64 = base64.b64encode(imagedata).decode() + uri = f'data:{mimetype};base64,{data64}' + + # No newlines around inline images (but all images may be nested + # in a `reference` node which is a `TextElement` instance): + if (not isinstance(node.parent, nodes.TextElement) + or isinstance(node.parent, nodes.reference) + and not isinstance(node.parent.parent, nodes.TextElement)): + suffix = '\n' + else: + suffix = '' + + if mimetype in self.videotypes: + atts['title'] = alt + if 'controls' in node['classes']: + node['classes'].remove('controls') + atts['controls'] = 'controls' + element = (self.starttag(node, "video", suffix, src=uri, **atts) + + f'<a href="{node["uri"]}">{alt}</a>{suffix}' + + f'</video>{suffix}') + elif mimetype == 'application/x-shockwave-flash': + atts['type'] = mimetype + element = (self.starttag(node, 'object', '', data=uri, **atts) + + f'{alt}</object>{suffix}') + elif element: # embedded SVG, see above + element += suffix + else: + atts['alt'] = alt + element = self.emptytag(node, 'img', suffix, src=uri, **atts) + self.body.append(element) + if suffix: # block-element + self.report_messages(node) + + def depart_image(self, node): + pass + + def visit_inline(self, node): + self.body.append(self.starttag(node, 'span', '')) + + def depart_inline(self, node): + self.body.append('</span>') + + # footnote and citation labels: + def visit_label(self, node): + self.body.append('<span class="label">') + self.body.append('<span class="fn-bracket">[</span>') + # footnote/citation backrefs: + if self.settings.footnote_backlinks: + backrefs = node.parent.get('backrefs', []) + if len(backrefs) == 1: + self.body.append('<a role="doc-backlink"' + ' href="#%s">' % backrefs[0]) + + def depart_label(self, node): + backrefs = [] + if self.settings.footnote_backlinks: + backrefs = node.parent.get('backrefs', backrefs) + if len(backrefs) == 1: + self.body.append('</a>') + self.body.append('<span class="fn-bracket">]</span></span>\n') + if len(backrefs) > 1: + backlinks = ['<a role="doc-backlink" href="#%s">%s</a>' % (ref, i) + for (i, ref) in enumerate(backrefs, 1)] + self.body.append('<span class="backrefs">(%s)</span>\n' + % ','.join(backlinks)) + + def visit_legend(self, node): + self.body.append(self.starttag(node, 'div', CLASS='legend')) + + def depart_legend(self, node): + self.body.append('</div>\n') + + def visit_line(self, node): + self.body.append(self.starttag(node, 'div', suffix='', CLASS='line')) + if not len(node): + self.body.append('<br />') + + def depart_line(self, node): + self.body.append('</div>\n') + + def visit_line_block(self, node): + self.body.append(self.starttag(node, 'div', CLASS='line-block')) + + def depart_line_block(self, node): + self.body.append('</div>\n') + + def visit_list_item(self, node): + self.body.append(self.starttag(node, 'li', '')) + + def depart_list_item(self, node): + self.body.append('</li>\n') + + # inline literal + def visit_literal(self, node): + # special case: "code" role + classes = node['classes'] + if 'code' in classes: + # filter 'code' from class arguments + classes.pop(classes.index('code')) + self.body.append(self.starttag(node, 'code', '')) + return + self.body.append( + self.starttag(node, 'span', '', CLASS='docutils literal')) + text = node.astext() + if not isinstance(node.parent, nodes.literal_block): + text = text.replace('\n', ' ') + # Protect text like ``--an-option`` and the regular expression + # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping + for token in self.words_and_spaces.findall(text): + if token.strip() and self.in_word_wrap_point.search(token): + self.body.append('<span class="pre">%s</span>' + % self.encode(token)) + else: + self.body.append(self.encode(token)) + self.body.append('</span>') + raise nodes.SkipNode # content already processed + + def depart_literal(self, node): + # skipped unless literal element is from "code" role: + self.body.append('</code>') + + def visit_literal_block(self, node): + self.body.append(self.starttag(node, 'pre', '', CLASS='literal-block')) + if 'code' in node['classes']: + self.body.append('<code>') + + def depart_literal_block(self, node): + if 'code' in node['classes']: + self.body.append('</code>') + self.body.append('</pre>\n') + + # Mathematics: + # As there is no native HTML math support, we provide alternatives + # for the math-output: LaTeX and MathJax simply wrap the content, + # HTML and MathML also convert the math_code. + # HTML element: + math_tags = { # format: (inline, block, [class arguments]) + 'html': ('span', 'div', ['formula']), + 'latex': ('tt', 'pre', ['math']), + 'mathjax': ('span', 'div', ['math']), + 'mathml': ('', 'div', []), + 'problematic': ('span', 'pre', ['math', 'problematic']), + } + + def visit_math(self, node): + # Also called from `visit_math_block()`: + is_block = isinstance(node, nodes.math_block) + format = self.math_output + math_code = node.astext().translate(unichar2tex.uni2tex_table) + + # preamble code and conversion + if format == 'html': + if self.math_options and not self.math_header: + self.math_header = [ + self.stylesheet_call(utils.find_file_in_dirs( + s, self.settings.stylesheet_dirs), adjust_path=True) + for s in self.math_options.split(',')] + math2html.DocumentParameters.displaymode = is_block + # TODO: fix display mode in matrices and fractions + math_code = wrap_math_code(math_code, is_block) + math_code = math2html.math2html(math_code) + elif format == 'latex': + math_code = self.encode(math_code) + elif format == 'mathjax': + if not self.math_header: + if self.math_options: + self.mathjax_url = self.math_options + else: + self.document.reporter.warning( + 'No MathJax URL specified, using local fallback ' + '(see config.html).', base_node=node) + # append MathJax configuration + # (input LaTeX with AMS, output common HTML): + if '?' not in self.mathjax_url: + self.mathjax_url += '?config=TeX-AMS_CHTML' + self.math_header = [self.mathjax_script % self.mathjax_url] + if is_block: + math_code = wrap_math_code(math_code, is_block) + else: + math_code = rf'\({math_code}\)' + math_code = self.encode(math_code) + elif format == 'mathml': + if 'XHTML 1' in self.doctype: + self.doctype = self.doctype_mathml + self.content_type = self.content_type_mathml + if self.math_options: + converter = getattr(tex2mathml_extern, self.math_options) + else: + converter = latex2mathml.tex2mathml + try: + math_code = converter(math_code, as_block=is_block) + except (MathError, OSError) as err: + details = getattr(err, 'details', []) + self.messages.append(self.document.reporter.warning( + err, *details, base_node=node)) + math_code = self.encode(node.astext()) + if self.settings.report_level <= 2: + format = 'problematic' + else: + format = 'latex' + if isinstance(err, OSError): + # report missing converter only once + self.math_output = format + + # append to document body + tag = self.math_tags[format][is_block] + suffix = '\n' if is_block else '' + if tag: + self.body.append(self.starttag(node, tag, suffix=suffix, + classes=self.math_tags[format][2])) + self.body.extend([math_code, suffix]) + if tag: + self.body.append(f'</{tag}>{suffix}') + # Content already processed: + raise nodes.SkipChildren + + def depart_math(self, node): + pass + + def visit_math_block(self, node): + self.visit_math(node) + + def depart_math_block(self, node): + self.report_messages(node) + + # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 + # HTML5/polyglot recommends using both + def visit_meta(self, node): + self.meta.append(self.emptytag(node, 'meta', + **node.non_default_attributes())) + + def depart_meta(self, node): + pass + + def visit_option(self, node): + self.body.append(self.starttag(node, 'span', '', CLASS='option')) + + def depart_option(self, node): + self.body.append('</span>') + if isinstance(node.next_node(descend=False, siblings=True), + nodes.option): + self.body.append(', ') + + def visit_option_argument(self, node): + self.body.append(node.get('delimiter', ' ')) + self.body.append(self.starttag(node, 'var', '')) + + def depart_option_argument(self, node): + self.body.append('</var>') + + def visit_option_group(self, node): + self.body.append(self.starttag(node, 'dt', '')) + self.body.append('<kbd>') + + def depart_option_group(self, node): + self.body.append('</kbd></dt>\n') + + def visit_option_list(self, node): + self.body.append( + self.starttag(node, 'dl', CLASS='option-list')) + + def depart_option_list(self, node): + self.body.append('</dl>\n') + + def visit_option_list_item(self, node): + pass + + def depart_option_list_item(self, node): + pass + + def visit_option_string(self, node): + pass + + def depart_option_string(self, node): + pass + + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization') + + def depart_organization(self, node): + self.depart_docinfo_item() + + # Do not omit <p> tags + # -------------------- + # + # The HTML4CSS1 writer does this to "produce + # visually compact lists (less vertical whitespace)". This writer + # relies on CSS rules for visual compactness. + # + # * In XHTML 1.1, e.g., a <blockquote> element may not contain + # character data, so you cannot drop the <p> tags. + # * Keeping simple paragraphs in the field_body enables a CSS + # rule to start the field-body on a new line if the label is too long + # * it makes the code simpler. + # + # TODO: omit paragraph tags in simple table cells? + + def visit_paragraph(self, node): + self.body.append(self.starttag(node, 'p', '')) + + def depart_paragraph(self, node): + self.body.append('</p>') + if not (isinstance(node.parent, (nodes.list_item, nodes.entry)) + and (len(node.parent) == 1)): + self.body.append('\n') + self.report_messages(node) + + def visit_problematic(self, node): + if node.hasattr('refid'): + self.body.append('<a href="#%s">' % node['refid']) + self.context.append('</a>') + else: + self.context.append('') + self.body.append(self.starttag(node, 'span', '', CLASS='problematic')) + + def depart_problematic(self, node): + self.body.append('</span>') + self.body.append(self.context.pop()) + + def visit_raw(self, node): + if 'html' in node.get('format', '').split(): + if isinstance(node.parent, nodes.TextElement): + tagname = 'span' + else: + tagname = 'div' + if node['classes']: + self.body.append(self.starttag(node, tagname, suffix='')) + self.body.append(node.astext()) + if node['classes']: + self.body.append('</%s>' % tagname) + # Keep non-HTML raw text out of output: + raise nodes.SkipNode + + def visit_reference(self, node): + atts = {'classes': ['reference']} + suffix = '' + if 'refuri' in node: + atts['href'] = node['refuri'] + if (self.settings.cloak_email_addresses + and atts['href'].startswith('mailto:')): + atts['href'] = self.cloak_mailto(atts['href']) + self.in_mailto = True + atts['classes'].append('external') + else: + assert 'refid' in node, \ + 'References must have "refuri" or "refid" attribute.' + atts['href'] = '#' + node['refid'] + atts['classes'].append('internal') + if len(node) == 1 and isinstance(node[0], nodes.image): + atts['classes'].append('image-reference') + if not isinstance(node.parent, nodes.TextElement): + suffix = '\n' + self.body.append(self.starttag(node, 'a', suffix, **atts)) + + def depart_reference(self, node): + self.body.append('</a>') + if not isinstance(node.parent, nodes.TextElement): + self.body.append('\n') + self.in_mailto = False + + def visit_revision(self, node): + self.visit_docinfo_item(node, 'revision', meta=False) + + def depart_revision(self, node): + self.depart_docinfo_item() + + def visit_row(self, node): + self.body.append(self.starttag(node, 'tr', '')) + node.column = 0 + + def depart_row(self, node): + self.body.append('</tr>\n') + + def visit_rubric(self, node): + self.body.append(self.starttag(node, 'p', '', CLASS='rubric')) + + def depart_rubric(self, node): + self.body.append('</p>\n') + + def visit_section(self, node): + self.section_level += 1 + self.body.append( + self.starttag(node, 'div', CLASS='section')) + + def depart_section(self, node): + self.section_level -= 1 + self.body.append('</div>\n') + + # TODO: use the new HTML5 element <aside> + def visit_sidebar(self, node): + self.body.append( + self.starttag(node, 'div', CLASS='sidebar')) + self.in_sidebar = True + + def depart_sidebar(self, node): + self.body.append('</div>\n') + self.in_sidebar = False + + def visit_status(self, node): + self.visit_docinfo_item(node, 'status', meta=False) + + def depart_status(self, node): + self.depart_docinfo_item() + + def visit_strong(self, node): + self.body.append(self.starttag(node, 'strong', '')) + + def depart_strong(self, node): + self.body.append('</strong>') + + def visit_subscript(self, node): + self.body.append(self.starttag(node, 'sub', '')) + + def depart_subscript(self, node): + self.body.append('</sub>') + + def visit_substitution_definition(self, node): + """Internal only.""" + raise nodes.SkipNode + + def visit_substitution_reference(self, node): + self.unimplemented_visit(node) + + # h1–h6 elements must not be used to markup subheadings, subtitles, + # alternative titles and taglines unless intended to be the heading for a + # new section or subsection. + # -- http://www.w3.org/TR/html51/sections.html#headings-and-sections + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.sidebar): + classes = ['sidebar-subtitle'] + elif isinstance(node.parent, nodes.document): + classes = ['subtitle'] + self.in_document_title = len(self.body) + 1 + elif isinstance(node.parent, nodes.section): + classes = ['section-subtitle'] + self.body.append(self.starttag(node, 'p', '', classes=classes)) + + def depart_subtitle(self, node): + self.body.append('</p>\n') + if isinstance(node.parent, nodes.document): + self.subtitle = self.body[self.in_document_title:-1] + self.in_document_title = 0 + self.body_pre_docinfo.extend(self.body) + self.html_subtitle.extend(self.body) + del self.body[:] + + def visit_superscript(self, node): + self.body.append(self.starttag(node, 'sup', '')) + + def depart_superscript(self, node): + self.body.append('</sup>') + + def visit_system_message(self, node): + self.body.append(self.starttag(node, 'aside', CLASS='system-message')) + self.body.append('<p class="system-message-title">') + backref_text = '' + if len(node['backrefs']): + backrefs = node['backrefs'] + if len(backrefs) == 1: + backref_text = ('; <em><a href="#%s">backlink</a></em>' + % backrefs[0]) + else: + i = 1 + backlinks = [] + for backref in backrefs: + backlinks.append('<a href="#%s">%s</a>' % (backref, i)) + i += 1 + backref_text = ('; <em>backlinks: %s</em>' + % ', '.join(backlinks)) + if node.hasattr('line'): + line = ', line %s' % node['line'] + else: + line = '' + self.body.append('System Message: %s/%s ' + '(<span class="docutils literal">%s</span>%s)%s</p>\n' + % (node['type'], node['level'], + self.encode(node['source']), line, backref_text)) + + def depart_system_message(self, node): + self.body.append('</aside>\n') + + def visit_table(self, node): + atts = {'classes': self.settings.table_style.replace(',', ' ').split()} + if 'align' in node: + atts['classes'].append('align-%s' % node['align']) + if 'width' in node: + atts['style'] = 'width: %s;' % node['width'] + tag = self.starttag(node, 'table', **atts) + self.body.append(tag) + + def depart_table(self, node): + self.body.append('</table>\n') + self.report_messages(node) + + def visit_target(self, node): + if ('refuri' not in node + and 'refid' not in node + and 'refname' not in node): + self.body.append(self.starttag(node, 'span', '', CLASS='target')) + self.context.append('</span>') + else: + self.context.append('') + + def depart_target(self, node): + self.body.append(self.context.pop()) + + # no hard-coded vertical alignment in table body + def visit_tbody(self, node): + self.body.append(self.starttag(node, 'tbody')) + + def depart_tbody(self, node): + self.body.append('</tbody>\n') + + def visit_term(self, node): + if 'details' in node.parent.parent['classes']: + self.body.append(self.starttag(node, 'summary', suffix='')) + else: + # The parent node (definition_list_item) is omitted in HTML. + self.body.append(self.starttag(node, 'dt', suffix='', + classes=node.parent['classes'], + ids=node.parent['ids'])) + + def depart_term(self, node): + # Nest (optional) classifier(s) in the <dt> element + if node.next_node(nodes.classifier, descend=False, siblings=True): + return # skip (depart_classifier() calls this function again) + if 'details' in node.parent.parent['classes']: + self.body.append('</summary>\n') + else: + self.body.append('</dt>\n') + + def visit_tgroup(self, node): + self.colspecs = [] + node.stubs = [] + + def depart_tgroup(self, node): + pass + + def visit_thead(self, node): + self.body.append(self.starttag(node, 'thead')) + + def depart_thead(self, node): + self.body.append('</thead>\n') + + def section_title_tags(self, node): + atts = {} + h_level = self.section_level + self.initial_header_level - 1 + # Only 6 heading levels have dedicated HTML tags. + tagname = 'h%i' % min(h_level, 6) + if h_level > 6: + atts['aria-level'] = h_level + start_tag = self.starttag(node, tagname, '', **atts) + if node.hasattr('refid'): + atts = {} + atts['class'] = 'toc-backref' + atts['role'] = 'doc-backlink' # HTML5 only + atts['href'] = '#' + node['refid'] + start_tag += self.starttag(nodes.reference(), 'a', '', **atts) + close_tag = '</a></%s>\n' % tagname + else: + close_tag = '</%s>\n' % tagname + return start_tag, close_tag + + def visit_title(self, node): + close_tag = '</p>\n' + if isinstance(node.parent, nodes.topic): + # TODO: use role="heading" or <h1>? (HTML5 only) + self.body.append( + self.starttag(node, 'p', '', CLASS='topic-title')) + if (self.settings.toc_backlinks + and 'contents' in node.parent['classes']): + self.body.append('<a class="reference internal" href="#top">') + close_tag = '</a></p>\n' + elif isinstance(node.parent, nodes.sidebar): + # TODO: use role="heading" or <h1>? (HTML5 only) + self.body.append( + self.starttag(node, 'p', '', CLASS='sidebar-title')) + elif isinstance(node.parent, nodes.Admonition): + self.body.append( + self.starttag(node, 'p', '', CLASS='admonition-title')) + elif isinstance(node.parent, nodes.table): + self.body.append(self.starttag(node, 'caption', '')) + close_tag = '</caption>\n' + elif isinstance(node.parent, nodes.document): + self.body.append(self.starttag(node, 'h1', '', CLASS='title')) + close_tag = '</h1>\n' + self.in_document_title = len(self.body) + else: + assert isinstance(node.parent, nodes.section) + # Get correct heading and evt. backlink tags + start_tag, close_tag = self.section_title_tags(node) + self.body.append(start_tag) + self.context.append(close_tag) + + def depart_title(self, node): + self.body.append(self.context.pop()) + if self.in_document_title: + self.title = self.body[self.in_document_title:-1] + self.in_document_title = 0 + self.body_pre_docinfo.extend(self.body) + self.html_title.extend(self.body) + del self.body[:] + + def visit_title_reference(self, node): + self.body.append(self.starttag(node, 'cite', '')) + + def depart_title_reference(self, node): + self.body.append('</cite>') + + def visit_topic(self, node): + self.body.append(self.starttag(node, 'div', CLASS='topic')) + + def depart_topic(self, node): + self.body.append('</div>\n') + + def visit_transition(self, node): + self.body.append(self.emptytag(node, 'hr', CLASS='docutils')) + + def depart_transition(self, node): + pass + + def visit_version(self, node): + self.visit_docinfo_item(node, 'version', meta=False) + + def depart_version(self, node): + self.depart_docinfo_item() + + def unimplemented_visit(self, node): + raise NotImplementedError('visiting unimplemented node type: %s' + % node.__class__.__name__) + + +class SimpleListChecker(nodes.GenericNodeVisitor): + + """ + Raise `nodes.NodeFound` if non-simple list item is encountered. + + Here "simple" means a list item containing nothing other than a single + paragraph, a simple list, or a paragraph followed by a simple list. + + This version also checks for simple field lists and docinfo. + """ + + def default_visit(self, node): + raise nodes.NodeFound + + def visit_list_item(self, node): + children = [child for child in node.children + if not isinstance(child, nodes.Invisible)] + if (children and isinstance(children[0], nodes.paragraph) + and (isinstance(children[-1], nodes.bullet_list) + or isinstance(children[-1], nodes.enumerated_list) + or isinstance(children[-1], nodes.field_list))): + children.pop() + if len(children) <= 1: + return + else: + raise nodes.NodeFound + + def pass_node(self, node): + pass + + def ignore_node(self, node): + # ignore nodes that are never complex (can contain only inline nodes) + raise nodes.SkipNode + + # Paragraphs and text + visit_Text = ignore_node + visit_paragraph = ignore_node + + # Lists + visit_bullet_list = pass_node + visit_enumerated_list = pass_node + visit_docinfo = pass_node + + # Docinfo nodes: + visit_author = ignore_node + visit_authors = visit_list_item + visit_address = visit_list_item + visit_contact = pass_node + visit_copyright = ignore_node + visit_date = ignore_node + visit_organization = ignore_node + visit_status = ignore_node + visit_version = visit_list_item + + # Definition list: + visit_definition_list = pass_node + visit_definition_list_item = pass_node + visit_term = ignore_node + visit_classifier = pass_node + visit_definition = visit_list_item + + # Field list: + visit_field_list = pass_node + visit_field = pass_node + # the field body corresponds to a list item + visit_field_body = visit_list_item + visit_field_name = ignore_node + + # Invisible nodes should be ignored. + visit_comment = ignore_node + visit_substitution_definition = ignore_node + visit_target = ignore_node + visit_pending = ignore_node diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/docutils_xml.py b/.venv/lib/python3.12/site-packages/docutils/writers/docutils_xml.py new file mode 100644 index 00000000..f4169295 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/docutils_xml.py @@ -0,0 +1,187 @@ +# $Id: docutils_xml.py 9502 2023-12-14 22:39:08Z milde $ +# Author: David Goodger, Paul Tremblay, Guenter Milde +# Maintainer: docutils-develop@lists.sourceforge.net +# Copyright: This module has been placed in the public domain. + +""" +Simple document tree Writer, writes Docutils XML according to +https://docutils.sourceforge.io/docs/ref/docutils.dtd. +""" + +__docformat__ = 'reStructuredText' + +from io import StringIO +import xml.sax.saxutils + +import docutils +from docutils import frontend, nodes, writers, utils + + +class RawXmlError(docutils.ApplicationError): + pass + + +class Writer(writers.Writer): + + supported = ('xml',) + """Formats this writer supports.""" + + settings_spec = ( + '"Docutils XML" Writer Options', + None, + (('Generate XML with newlines before and after tags.', + ['--newlines'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Generate XML with indents and newlines.', + ['--indents'], # TODO use integer value for number of spaces? + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Omit the XML declaration. Use with caution.', + ['--no-xml-declaration'], + {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false', + 'validator': frontend.validate_boolean}), + ('Omit the DOCTYPE declaration.', + ['--no-doctype'], + {'dest': 'doctype_declaration', 'default': 1, + 'action': 'store_false', 'validator': frontend.validate_boolean}),)) + + settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'} + + config_section = 'docutils_xml writer' + config_section_dependencies = ('writers',) + + output = None + """Final translated form of `document`.""" + + def __init__(self): + writers.Writer.__init__(self) + self.translator_class = XMLTranslator + + def translate(self): + self.visitor = visitor = self.translator_class(self.document) + self.document.walkabout(visitor) + self.output = ''.join(visitor.output) + + +class XMLTranslator(nodes.GenericNodeVisitor): + + # TODO: add stylesheet options similar to HTML and LaTeX writers? + # xml_stylesheet = '<?xml-stylesheet type="text/xsl" href="%s"?>\n' + doctype = ( + '<!DOCTYPE document PUBLIC' + ' "+//IDN docutils.sourceforge.net//DTD Docutils Generic//EN//XML"' + ' "http://docutils.sourceforge.net/docs/ref/docutils.dtd">\n') + generator = '<!-- Generated by Docutils %s -->\n' + + xmlparser = xml.sax.make_parser() + """SAX parser instance to check/extract raw XML.""" + xmlparser.setFeature( + "http://xml.org/sax/features/external-general-entities", True) + + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + + # Reporter + self.warn = self.document.reporter.warning + self.error = self.document.reporter.error + + # Settings + self.settings = settings = document.settings + self.indent = self.newline = '' + if settings.newlines: + self.newline = '\n' + if settings.indents: + self.newline = '\n' + self.indent = ' ' # TODO make this configurable? + self.level = 0 # indentation level + self.in_simple = 0 # level of nesting inside mixed-content elements + self.fixed_text = 0 # level of nesting inside FixedText elements + + # Output + self.output = [] + if settings.xml_declaration: + self.output.append(utils.xml_declaration(settings.output_encoding)) + if settings.doctype_declaration: + self.output.append(self.doctype) + self.output.append(self.generator % docutils.__version__) + + # initialize XML parser + self.the_handle = TestXml() + self.xmlparser.setContentHandler(self.the_handle) + + # generic visit and depart methods + # -------------------------------- + + simple_nodes = (nodes.TextElement, nodes.meta, + nodes.image, nodes.colspec, nodes.transition) + + def default_visit(self, node): + """Default node visit method.""" + if not self.in_simple: + self.output.append(self.indent*self.level) + self.output.append(node.starttag(xml.sax.saxutils.quoteattr)) + self.level += 1 + # `nodes.literal` is not an instance of FixedTextElement by design, + # see docs/ref/rst/restructuredtext.html#inline-literals + if isinstance(node, (nodes.FixedTextElement, nodes.literal)): + self.fixed_text += 1 + if isinstance(node, self.simple_nodes): + self.in_simple += 1 + if not self.in_simple: + self.output.append(self.newline) + + def default_departure(self, node): + """Default node depart method.""" + self.level -= 1 + if not self.in_simple: + self.output.append(self.indent*self.level) + self.output.append(node.endtag()) + if isinstance(node, (nodes.FixedTextElement, nodes.literal)): + self.fixed_text -= 1 + if isinstance(node, self.simple_nodes): + self.in_simple -= 1 + if not self.in_simple: + self.output.append(self.newline) + + # specific visit and depart methods + # --------------------------------- + + def visit_Text(self, node): + text = xml.sax.saxutils.escape(node.astext()) + # indent text if we are not in a FixedText element: + if not self.fixed_text: + text = text.replace('\n', '\n'+self.indent*self.level) + self.output.append(text) + + def depart_Text(self, node): + pass + + def visit_raw(self, node): + if 'xml' not in node.get('format', '').split(): + # skip other raw content? + # raise nodes.SkipNode + self.default_visit(node) + return + # wrap in <raw> element + self.default_visit(node) # or not? + xml_string = node.astext() + self.output.append(xml_string) + self.default_departure(node) # or not? + # Check validity of raw XML: + try: + self.xmlparser.parse(StringIO(xml_string)) + except xml.sax._exceptions.SAXParseException: + col_num = self.the_handle.locator.getColumnNumber() + line_num = self.the_handle.locator.getLineNumber() + srcline = node.line + if not isinstance(node.parent, nodes.TextElement): + srcline += 2 # directive content start line + msg = 'Invalid raw XML in column %d, line offset %d:\n%s' % ( + col_num, line_num, node.astext()) + self.warn(msg, source=node.source, line=srcline+line_num-1) + raise nodes.SkipNode # content already processed + + +class TestXml(xml.sax.handler.ContentHandler): + + def setDocumentLocator(self, locator): + self.locator = locator diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/__init__.py new file mode 100644 index 00000000..799d30e4 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/__init__.py @@ -0,0 +1,955 @@ +# $Id: __init__.py 9558 2024-03-11 17:48:52Z milde $ +# Author: David Goodger +# Maintainer: docutils-develop@lists.sourceforge.net +# Copyright: This module has been placed in the public domain. + +""" +Simple HyperText Markup Language document tree Writer. + +The output conforms to the XHTML version 1.0 Transitional DTD +(*almost* strict). The output contains a minimum of formatting +information. The cascading style sheet "html4css1.css" is required +for proper viewing with a modern graphical browser. +""" + +__docformat__ = 'reStructuredText' + +import os.path +import re + +from docutils import frontend, nodes, writers +from docutils.writers import _html_base +from docutils.writers._html_base import PIL + + +class Writer(writers._html_base.Writer): + + supported = ('html', 'html4', 'html4css1', 'xhtml', 'xhtml10') + """Formats this writer supports.""" + + default_stylesheets = ['html4css1.css'] + default_stylesheet_dirs = ['.', + os.path.abspath(os.path.dirname(__file__)), + os.path.abspath(os.path.join( + os.path.dirname(os.path.dirname(__file__)), + 'html5_polyglot')) # for math.css + ] + default_template = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'template.txt') + + # use a copy of the parent spec with some modifications + settings_spec = frontend.filter_settings_spec( + writers._html_base.Writer.settings_spec, + template=( + 'Template file. (UTF-8 encoded, default: "%s")' % default_template, + ['--template'], + {'default': default_template, 'metavar': '<file>'}), + stylesheet_path=( + 'Comma separated list of stylesheet paths. ' + 'Relative paths are expanded if a matching file is found in ' + 'the --stylesheet-dirs. With --link-stylesheet, ' + 'the path is rewritten relative to the output HTML file. ' + '(default: "%s")' % ','.join(default_stylesheets), + ['--stylesheet-path'], + {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', + 'validator': frontend.validate_comma_separated_list, + 'default': default_stylesheets}), + stylesheet_dirs=( + 'Comma-separated list of directories where stylesheets are found. ' + 'Used by --stylesheet-path when expanding relative path ' + 'arguments. (default: "%s")' % ','.join(default_stylesheet_dirs), + ['--stylesheet-dirs'], + {'metavar': '<dir[,dir,...]>', + 'validator': frontend.validate_comma_separated_list, + 'default': default_stylesheet_dirs}), + initial_header_level=( + 'Specify the initial header level. Does not affect document ' + 'title & subtitle (see --no-doc-title). (default: 1 for "<h1>")', + ['--initial-header-level'], + {'choices': '1 2 3 4 5 6'.split(), 'default': '1', + 'metavar': '<level>'}), + xml_declaration=( + 'Prepend an XML declaration (default). ', + ['--xml-declaration'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ) + settings_spec = settings_spec + ( + 'HTML4 Writer Options', + '', + (('Specify the maximum width (in characters) for one-column field ' + 'names. Longer field names will span an entire row of the table ' + 'used to render the field list. Default is 14 characters. ' + 'Use 0 for "no limit".', + ['--field-name-limit'], + {'default': 14, 'metavar': '<level>', + 'validator': frontend.validate_nonnegative_int}), + ('Specify the maximum width (in characters) for options in option ' + 'lists. Longer options will span an entire row of the table used ' + 'to render the option list. Default is 14 characters. ' + 'Use 0 for "no limit".', + ['--option-limit'], + {'default': 14, 'metavar': '<level>', + 'validator': frontend.validate_nonnegative_int}), + ) + ) + + config_section = 'html4css1 writer' + + def __init__(self): + self.parts = {} + self.translator_class = HTMLTranslator + + +class HTMLTranslator(writers._html_base.HTMLTranslator): + """ + The html4css1 writer has been optimized to produce visually compact + lists (less vertical whitespace). HTML's mixed content models + allow list items to contain "<li><p>body elements</p></li>" or + "<li>just text</li>" or even "<li>text<p>and body + elements</p>combined</li>", each with different effects. It would + be best to stick with strict body elements in list items, but they + affect vertical spacing in older browsers (although they really + shouldn't). + The html5_polyglot writer solves this using CSS2. + + Here is an outline of the optimization: + + - Check for and omit <p> tags in "simple" lists: list items + contain either a single paragraph, a nested simple list, or a + paragraph followed by a nested simple list. This means that + this list can be compact: + + - Item 1. + - Item 2. + + But this list cannot be compact: + + - Item 1. + + This second paragraph forces space between list items. + + - Item 2. + + - In non-list contexts, omit <p> tags on a paragraph if that + paragraph is the only child of its parent (footnotes & citations + are allowed a label first). + + - Regardless of the above, in definitions, table cells, field bodies, + option descriptions, and list items, mark the first child with + 'class="first"' and the last child with 'class="last"'. The stylesheet + sets the margins (top & bottom respectively) to 0 for these elements. + + The ``no_compact_lists`` setting (``--no-compact-lists`` command-line + option) disables list whitespace optimization. + """ + + # The following definitions are required for display in browsers limited + # to CSS1 or backwards compatible behaviour of the writer: + + doctype = ( + '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' + ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n') + + content_type = ('<meta http-equiv="Content-Type"' + ' content="text/html; charset=%s" />\n') + content_type_mathml = ('<meta http-equiv="Content-Type"' + ' content="application/xhtml+xml; charset=%s" />\n') + + # encode also non-breaking space + special_characters = _html_base.HTMLTranslator.special_characters.copy() + special_characters[0xa0] = ' ' + + # use character reference for dash (not valid in HTML5) + attribution_formats = {'dash': ('—', ''), + 'parentheses': ('(', ')'), + 'parens': ('(', ')'), + 'none': ('', '')} + + # ersatz for first/last pseudo-classes missing in CSS1 + def set_first_last(self, node): + self.set_class_on_child(node, 'first', 0) + self.set_class_on_child(node, 'last', -1) + + # add newline after opening tag + def visit_address(self, node): + self.visit_docinfo_item(node, 'address', meta=False) + self.body.append(self.starttag(node, 'pre', CLASS='address')) + + def depart_address(self, node): + self.body.append('\n</pre>\n') + self.depart_docinfo_item() + + # ersatz for first/last pseudo-classes + def visit_admonition(self, node): + node['classes'].insert(0, 'admonition') + self.body.append(self.starttag(node, 'div')) + self.set_first_last(node) + + def depart_admonition(self, node=None): + self.body.append('</div>\n') + + # author, authors: use <br> instead of paragraphs + def visit_author(self, node): + if isinstance(node.parent, nodes.authors): + if self.author_in_authors: + self.body.append('\n<br />') + else: + self.visit_docinfo_item(node, 'author') + + def depart_author(self, node): + if isinstance(node.parent, nodes.authors): + self.author_in_authors = True + else: + self.depart_docinfo_item() + + def visit_authors(self, node): + self.visit_docinfo_item(node, 'authors') + self.author_in_authors = False # initialize + + def depart_authors(self, node): + self.depart_docinfo_item() + + # use "width" argument instead of "style: 'width'": + def visit_colspec(self, node): + self.colspecs.append(node) + # "stubs" list is an attribute of the tgroup element: + node.parent.stubs.append(node.attributes.get('stub')) + + def depart_colspec(self, node): + # write out <colgroup> when all colspecs are processed + if isinstance(node.next_node(descend=False, siblings=True), + nodes.colspec): + return + if ('colwidths-auto' in node.parent.parent['classes'] + or ('colwidths-auto' in self.settings.table_style + and 'colwidths-given' not in node.parent.parent['classes'])): + return + total_width = sum(node['colwidth'] for node in self.colspecs) + self.body.append(self.starttag(node, 'colgroup')) + for node in self.colspecs: + colwidth = int(node['colwidth'] * 100.0 / total_width + 0.5) + self.body.append(self.emptytag(node, 'col', + width='%i%%' % colwidth)) + self.body.append('</colgroup>\n') + + # Compact lists: + # exclude definition lists and field lists (non-compact by default) + + def is_compactable(self, node): + return ('compact' in node['classes'] + or (self.settings.compact_lists + and 'open' not in node['classes'] + and (self.compact_simple + or 'contents' in node.parent['classes'] + # TODO: self.in_contents + or self.check_simple_list(node)))) + + # citations: Use table for bibliographic references. + def visit_citation(self, node): + self.body.append(self.starttag(node, 'table', + CLASS='docutils citation', + frame="void", rules="none")) + self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' + '<tbody valign="top">\n' + '<tr>') + self.footnote_backrefs(node) + + def depart_citation(self, node): + self.body.append('</td></tr>\n' + '</tbody>\n</table>\n') + + def visit_citation_reference(self, node): + href = '#' + if 'refid' in node: + href += node['refid'] + elif 'refname' in node: + href += self.document.nameids[node['refname']] + self.body.append(self.starttag(node, 'a', suffix='[', href=href, + classes=['citation-reference'])) + + def depart_citation_reference(self, node): + self.body.append(']</a>') + + # insert classifier-delimiter (not required with CSS2) + def visit_classifier(self, node): + self.body.append(' <span class="classifier-delimiter">:</span> ') + self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) + + def depart_classifier(self, node): + self.body.append('</span>') + self.depart_term(node) # close the <dt> after last classifier + + # ersatz for first/last pseudo-classes + def visit_compound(self, node): + self.body.append(self.starttag(node, 'div', CLASS='compound')) + if len(node) > 1: + node[0]['classes'].append('compound-first') + node[-1]['classes'].append('compound-last') + for child in node[1:-1]: + child['classes'].append('compound-middle') + + def depart_compound(self, node): + self.body.append('</div>\n') + + # ersatz for first/last pseudo-classes, no special handling of "details" + def visit_definition(self, node): + self.body.append(self.starttag(node, 'dd', '')) + self.set_first_last(node) + + def depart_definition(self, node): + self.body.append('</dd>\n') + + # don't add "simple" class value, no special handling of "details" + def visit_definition_list(self, node): + self.body.append(self.starttag(node, 'dl', CLASS='docutils')) + + def depart_definition_list(self, node): + self.body.append('</dl>\n') + + # no special handling of "details" + def visit_definition_list_item(self, node): + pass + + def depart_definition_list_item(self, node): + pass + + # use a table for description lists + def visit_description(self, node): + self.body.append(self.starttag(node, 'td', '')) + self.set_first_last(node) + + def depart_description(self, node): + self.body.append('</td>') + + # use table for docinfo + def visit_docinfo(self, node): + self.context.append(len(self.body)) + self.body.append(self.starttag(node, 'table', + CLASS='docinfo', + frame="void", rules="none")) + self.body.append('<col class="docinfo-name" />\n' + '<col class="docinfo-content" />\n' + '<tbody valign="top">\n') + self.in_docinfo = True + + def depart_docinfo(self, node): + self.body.append('</tbody>\n</table>\n') + self.in_docinfo = False + start = self.context.pop() + self.docinfo = self.body[start:] + self.body = [] + + def visit_docinfo_item(self, node, name, meta=True): + if meta: + meta_tag = '<meta name="%s" content="%s" />\n' \ + % (name, self.attval(node.astext())) + self.meta.append(meta_tag) + self.body.append(self.starttag(node, 'tr', '')) + self.body.append('<th class="docinfo-name">%s:</th>\n<td>' + % self.language.labels[name]) + if len(node): + if isinstance(node[0], nodes.Element): + node[0]['classes'].append('first') + if isinstance(node[-1], nodes.Element): + node[-1]['classes'].append('last') + + def depart_docinfo_item(self): + self.body.append('</td></tr>\n') + + # add newline after opening tag + def visit_doctest_block(self, node): + self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) + + def depart_doctest_block(self, node): + self.body.append('\n</pre>\n') + + # insert an NBSP into empty cells, ersatz for first/last + def visit_entry(self, node): + writers._html_base.HTMLTranslator.visit_entry(self, node) + if len(node) == 0: # empty cell + self.body.append(' ') + self.set_first_last(node) + + def depart_entry(self, node): + self.body.append(self.context.pop()) + + # ersatz for first/last pseudo-classes + def visit_enumerated_list(self, node): + """ + The 'start' attribute does not conform to HTML 4.01's strict.dtd, but + cannot be emulated in CSS1 (HTML 5 reincludes it). + """ + atts = {} + if 'start' in node: + atts['start'] = node['start'] + if 'enumtype' in node: + atts['class'] = node['enumtype'] + # @@@ To do: prefix, suffix. How? Change prefix/suffix to a + # single "format" attribute? Use CSS2? + old_compact_simple = self.compact_simple + self.context.append((self.compact_simple, self.compact_p)) + self.compact_p = None + self.compact_simple = self.is_compactable(node) + if self.compact_simple and not old_compact_simple: + atts['class'] = (atts.get('class', '') + ' simple').strip() + self.body.append(self.starttag(node, 'ol', **atts)) + + def depart_enumerated_list(self, node): + self.compact_simple, self.compact_p = self.context.pop() + self.body.append('</ol>\n') + + # use table for field-list: + def visit_field(self, node): + self.body.append(self.starttag(node, 'tr', '', CLASS='field')) + + def depart_field(self, node): + self.body.append('</tr>\n') + + def visit_field_body(self, node): + self.body.append(self.starttag(node, 'td', '', CLASS='field-body')) + self.set_class_on_child(node, 'first', 0) + field = node.parent + if (self.compact_field_list + or isinstance(field.parent, nodes.docinfo) + or field.parent.index(field) == len(field.parent) - 1): + # If we are in a compact list, the docinfo, or if this is + # the last field of the field list, do not add vertical + # space after last element. + self.set_class_on_child(node, 'last', -1) + + def depart_field_body(self, node): + self.body.append('</td>\n') + + def visit_field_list(self, node): + self.context.append((self.compact_field_list, self.compact_p)) + self.compact_p = None + if 'compact' in node['classes']: + self.compact_field_list = True + elif (self.settings.compact_field_lists + and 'open' not in node['classes']): + self.compact_field_list = True + if self.compact_field_list: + for field in node: + field_body = field[-1] + assert isinstance(field_body, nodes.field_body) + children = [n for n in field_body + if not isinstance(n, nodes.Invisible)] + if not (len(children) == 0 + or len(children) == 1 + and isinstance(children[0], + (nodes.paragraph, nodes.line_block))): + self.compact_field_list = False + break + self.body.append(self.starttag(node, 'table', frame='void', + rules='none', + CLASS='docutils field-list')) + self.body.append('<col class="field-name" />\n' + '<col class="field-body" />\n' + '<tbody valign="top">\n') + + def depart_field_list(self, node): + self.body.append('</tbody>\n</table>\n') + self.compact_field_list, self.compact_p = self.context.pop() + + def visit_field_name(self, node): + atts = {} + if self.in_docinfo: + atts['class'] = 'docinfo-name' + else: + atts['class'] = 'field-name' + if (self.settings.field_name_limit + and len(node.astext()) > self.settings.field_name_limit): + atts['colspan'] = 2 + self.context.append('</tr>\n' + + self.starttag(node.parent, 'tr', '', + CLASS='field') + + '<td> </td>') + else: + self.context.append('') + self.body.append(self.starttag(node, 'th', '', **atts)) + + def depart_field_name(self, node): + self.body.append(':</th>') + self.body.append(self.context.pop()) + + # use table for footnote text + def visit_footnote(self, node): + self.body.append(self.starttag(node, 'table', + CLASS='docutils footnote', + frame="void", rules="none")) + self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' + '<tbody valign="top">\n' + '<tr>') + self.footnote_backrefs(node) + + def footnote_backrefs(self, node): + backlinks = [] + backrefs = node['backrefs'] + if self.settings.footnote_backlinks and backrefs: + if len(backrefs) == 1: + self.context.append('') + self.context.append('</a>') + self.context.append('<a class="fn-backref" href="#%s">' + % backrefs[0]) + else: + for (i, backref) in enumerate(backrefs, 1): + backlinks.append('<a class="fn-backref" href="#%s">%s</a>' + % (backref, i)) + self.context.append('<em>(%s)</em> ' % ', '.join(backlinks)) + self.context += ['', ''] + else: + self.context.append('') + self.context += ['', ''] + # If the node does not only consist of a label. + if len(node) > 1: + # If there are preceding backlinks, we do not set class + # 'first', because we need to retain the top-margin. + if not backlinks: + node[1]['classes'].append('first') + node[-1]['classes'].append('last') + + def depart_footnote(self, node): + self.body.append('</td></tr>\n' + '</tbody>\n</table>\n') + + # insert markers in text (pseudo-classes are not supported in CSS1): + def visit_footnote_reference(self, node): + href = '#' + node['refid'] + format = self.settings.footnote_references + if format == 'brackets': + suffix = '[' + self.context.append(']') + else: + assert format == 'superscript' + suffix = '<sup>' + self.context.append('</sup>') + self.body.append(self.starttag(node, 'a', suffix, + CLASS='footnote-reference', href=href)) + + def depart_footnote_reference(self, node): + self.body.append(self.context.pop() + '</a>') + + # just pass on generated text + def visit_generated(self, node): + pass + + # Backwards-compatibility implementation: + # * Do not use <video>, + # * don't embed images, + # * use <object> instead of <img> for SVG. + # (SVG not supported by IE up to version 8, + # html4css1 strives for IE6 compatibility.) + object_image_types = {'.svg': 'image/svg+xml', + '.swf': 'application/x-shockwave-flash', + '.mp4': 'video/mp4', + '.webm': 'video/webm', + '.ogg': 'video/ogg', + } + + def visit_image(self, node): + atts = {} + uri = node['uri'] + ext = os.path.splitext(uri)[1].lower() + if ext in self.object_image_types: + atts['data'] = uri + atts['type'] = self.object_image_types[ext] + else: + atts['src'] = uri + atts['alt'] = node.get('alt', uri) + # image size + if 'width' in node: + atts['width'] = node['width'] + if 'height' in node: + atts['height'] = node['height'] + if 'scale' in node: + if (PIL and ('width' not in node or 'height' not in node) + and self.settings.file_insertion_enabled): + imagepath = self.uri2imagepath(uri) + try: + with PIL.Image.open(imagepath) as img: + img_size = img.size + except (OSError, UnicodeEncodeError): + pass # TODO: warn/info? + else: + self.settings.record_dependencies.add( + imagepath.replace('\\', '/')) + if 'width' not in atts: + atts['width'] = '%dpx' % img_size[0] + if 'height' not in atts: + atts['height'] = '%dpx' % img_size[1] + for att_name in 'width', 'height': + if att_name in atts: + match = re.match(r'([0-9.]+)(\S*)$', atts[att_name]) + assert match + atts[att_name] = '%s%s' % ( + float(match.group(1)) * (float(node['scale']) / 100), + match.group(2)) + style = [] + for att_name in 'width', 'height': + if att_name in atts: + if re.match(r'^[0-9.]+$', atts[att_name]): + # Interpret unitless values as pixels. + atts[att_name] += 'px' + style.append('%s: %s;' % (att_name, atts[att_name])) + del atts[att_name] + if style: + atts['style'] = ' '.join(style) + # No newlines around inline images. + if (not isinstance(node.parent, nodes.TextElement) + or isinstance(node.parent, nodes.reference) + and not isinstance(node.parent.parent, nodes.TextElement)): + suffix = '\n' + else: + suffix = '' + if 'align' in node: + atts['class'] = 'align-%s' % node['align'] + if ext in self.object_image_types: + # do NOT use an empty tag: incorrect rendering in browsers + self.body.append(self.starttag(node, 'object', '', **atts) + + node.get('alt', uri) + '</object>' + suffix) + else: + self.body.append(self.emptytag(node, 'img', suffix, **atts)) + + def depart_image(self, node): + pass + + # use table for footnote text, + # context added in footnote_backrefs. + def visit_label(self, node): + self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), + CLASS='label')) + + def depart_label(self, node): + self.body.append(f']{self.context.pop()}</td><td>{self.context.pop()}') + + # ersatz for first/last pseudo-classes + def visit_list_item(self, node): + self.body.append(self.starttag(node, 'li', '')) + if len(node): + node[0]['classes'].append('first') + + def depart_list_item(self, node): + self.body.append('</li>\n') + + # use <tt> (not supported by HTML5), + # cater for limited styling options in CSS1 using hard-coded NBSPs + def visit_literal(self, node): + # special case: "code" role + classes = node['classes'] + if 'code' in classes: + # filter 'code' from class arguments + node['classes'] = [cls for cls in classes if cls != 'code'] + self.body.append(self.starttag(node, 'code', '')) + return + self.body.append( + self.starttag(node, 'tt', '', CLASS='docutils literal')) + text = node.astext() + for token in self.words_and_spaces.findall(text): + if token.strip(): + # Protect text like "--an-option" and the regular expression + # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping + if self.in_word_wrap_point.search(token): + self.body.append('<span class="pre">%s</span>' + % self.encode(token)) + else: + self.body.append(self.encode(token)) + elif token in ('\n', ' '): + # Allow breaks at whitespace: + self.body.append(token) + else: + # Protect runs of multiple spaces; the last space can wrap: + self.body.append(' ' * (len(token) - 1) + ' ') + self.body.append('</tt>') + # Content already processed: + raise nodes.SkipNode + + def depart_literal(self, node): + # skipped unless literal element is from "code" role: + self.body.append('</code>') + + # add newline after wrapper tags, don't use <code> for code + def visit_literal_block(self, node): + self.body.append(self.starttag(node, 'pre', CLASS='literal-block')) + + def depart_literal_block(self, node): + self.body.append('\n</pre>\n') + + # use table for option list + def visit_option_group(self, node): + atts = {} + if (self.settings.option_limit + and len(node.astext()) > self.settings.option_limit): + atts['colspan'] = 2 + self.context.append('</tr>\n<tr><td> </td>') + else: + self.context.append('') + self.body.append( + self.starttag(node, 'td', CLASS='option-group', **atts)) + self.body.append('<kbd>') + self.context.append(0) # count number of options + + def depart_option_group(self, node): + self.context.pop() + self.body.append('</kbd></td>\n') + self.body.append(self.context.pop()) + + def visit_option_list(self, node): + self.body.append( + self.starttag(node, 'table', CLASS='docutils option-list', + frame="void", rules="none")) + self.body.append('<col class="option" />\n' + '<col class="description" />\n' + '<tbody valign="top">\n') + + def depart_option_list(self, node): + self.body.append('</tbody>\n</table>\n') + + def visit_option_list_item(self, node): + self.body.append(self.starttag(node, 'tr', '')) + + def depart_option_list_item(self, node): + self.body.append('</tr>\n') + + # Omit <p> tags to produce visually compact lists (less vertical + # whitespace) as CSS styling requires CSS2. + def should_be_compact_paragraph(self, node): + """ + Determine if the <p> tags around paragraph ``node`` can be omitted. + """ + if (isinstance(node.parent, nodes.document) + or isinstance(node.parent, nodes.compound)): + # Never compact paragraphs in document or compound. + return False + for key, value in node.attlist(): + if (node.is_not_default(key) + and not (key == 'classes' + and value in ([], ['first'], + ['last'], ['first', 'last']))): + # Attribute which needs to survive. + return False + first = isinstance(node.parent[0], nodes.label) # skip label + for child in node.parent.children[first:]: + # only first paragraph can be compact + if isinstance(child, nodes.Invisible): + continue + if child is node: + break + return False + parent_length = len([n for n in node.parent if not isinstance( + n, (nodes.Invisible, nodes.label))]) + if (self.compact_simple + or self.compact_field_list + or self.compact_p and parent_length == 1): + return True + return False + + def visit_paragraph(self, node): + if self.should_be_compact_paragraph(node): + self.context.append('') + else: + self.body.append(self.starttag(node, 'p', '')) + self.context.append('</p>\n') + + def depart_paragraph(self, node): + self.body.append(self.context.pop()) + self.report_messages(node) + + # ersatz for first/last pseudo-classes + def visit_sidebar(self, node): + self.body.append( + self.starttag(node, 'div', CLASS='sidebar')) + self.set_first_last(node) + self.in_sidebar = True + + def depart_sidebar(self, node): + self.body.append('</div>\n') + self.in_sidebar = False + + # <sub> not allowed in <pre> + def visit_subscript(self, node): + if isinstance(node.parent, nodes.literal_block): + self.body.append(self.starttag(node, 'span', '', + CLASS='subscript')) + else: + self.body.append(self.starttag(node, 'sub', '')) + + def depart_subscript(self, node): + if isinstance(node.parent, nodes.literal_block): + self.body.append('</span>') + else: + self.body.append('</sub>') + + # Use <h*> for subtitles (deprecated in HTML 5) + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.sidebar): + self.body.append(self.starttag(node, 'p', '', + CLASS='sidebar-subtitle')) + self.context.append('</p>\n') + elif isinstance(node.parent, nodes.document): + self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle')) + self.context.append('</h2>\n') + self.in_document_title = len(self.body) + elif isinstance(node.parent, nodes.section): + tag = 'h%s' % (self.section_level + self.initial_header_level - 1) + self.body.append( + self.starttag(node, tag, '', CLASS='section-subtitle') + + self.starttag({}, 'span', '', CLASS='section-subtitle')) + self.context.append('</span></%s>\n' % tag) + + def depart_subtitle(self, node): + self.body.append(self.context.pop()) + if self.in_document_title: + self.subtitle = self.body[self.in_document_title:-1] + self.in_document_title = 0 + self.body_pre_docinfo.extend(self.body) + self.html_subtitle.extend(self.body) + del self.body[:] + + # <sup> not allowed in <pre> in HTML 4 + def visit_superscript(self, node): + if isinstance(node.parent, nodes.literal_block): + self.body.append(self.starttag(node, 'span', '', + CLASS='superscript')) + else: + self.body.append(self.starttag(node, 'sup', '')) + + def depart_superscript(self, node): + if isinstance(node.parent, nodes.literal_block): + self.body.append('</span>') + else: + self.body.append('</sup>') + + # <tt> element deprecated in HTML 5 + def visit_system_message(self, node): + self.body.append(self.starttag(node, 'div', CLASS='system-message')) + self.body.append('<p class="system-message-title">') + backref_text = '' + if len(node['backrefs']): + backrefs = node['backrefs'] + if len(backrefs) == 1: + backref_text = ('; <em><a href="#%s">backlink</a></em>' + % backrefs[0]) + else: + i = 1 + backlinks = [] + for backref in backrefs: + backlinks.append('<a href="#%s">%s</a>' % (backref, i)) + i += 1 + backref_text = ('; <em>backlinks: %s</em>' + % ', '.join(backlinks)) + if node.hasattr('line'): + line = ', line %s' % node['line'] + else: + line = '' + self.body.append('System Message: %s/%s ' + '(<tt class="docutils">%s</tt>%s)%s</p>\n' + % (node['type'], node['level'], + self.encode(node['source']), line, backref_text)) + + def depart_system_message(self, node): + self.body.append('</div>\n') + + # "hard coded" border setting + def visit_table(self, node): + self.context.append(self.compact_p) + self.compact_p = True + atts = {'border': 1} + classes = ['docutils', self.settings.table_style] + if 'align' in node: + classes.append('align-%s' % node['align']) + if 'width' in node: + atts['style'] = 'width: %s' % node['width'] + self.body.append( + self.starttag(node, 'table', CLASS=' '.join(classes), **atts)) + + def depart_table(self, node): + self.compact_p = self.context.pop() + self.body.append('</table>\n') + + # hard-coded vertical alignment + def visit_tbody(self, node): + self.body.append(self.starttag(node, 'tbody', valign='top')) + + def depart_tbody(self, node): + self.body.append('</tbody>\n') + + # no special handling of "details" in definition list + def visit_term(self, node): + self.body.append(self.starttag(node, 'dt', '', + classes=node.parent['classes'], + ids=node.parent['ids'])) + + def depart_term(self, node): + # Nest (optional) classifier(s) in the <dt> element + if node.next_node(nodes.classifier, descend=False, siblings=True): + return # skip (depart_classifier() calls this function again) + self.body.append('</dt>\n') + + # hard-coded vertical alignment + def visit_thead(self, node): + self.body.append(self.starttag(node, 'thead', valign='bottom')) + + def depart_thead(self, node): + self.body.append('</thead>\n') + + # auxiliary method, called by visit_title() + # "with-subtitle" class, no ARIA roles + def section_title_tags(self, node): + classes = [] + h_level = self.section_level + self.initial_header_level - 1 + if (len(node.parent) >= 2 + and isinstance(node.parent[1], nodes.subtitle)): + classes.append('with-subtitle') + if h_level > 6: + classes.append('h%i' % h_level) + tagname = 'h%i' % min(h_level, 6) + start_tag = self.starttag(node, tagname, '', classes=classes) + if node.hasattr('refid'): + atts = {} + atts['class'] = 'toc-backref' + atts['href'] = '#' + node['refid'] + start_tag += self.starttag({}, 'a', '', **atts) + close_tag = '</a></%s>\n' % tagname + else: + close_tag = '</%s>\n' % tagname + return start_tag, close_tag + + +class SimpleListChecker(writers._html_base.SimpleListChecker): + + """ + Raise `nodes.NodeFound` if non-simple list item is encountered. + + Here "simple" means a list item containing nothing other than a single + paragraph, a simple list, or a paragraph followed by a simple list. + """ + + def visit_list_item(self, node): + children = [] + for child in node.children: + if not isinstance(child, nodes.Invisible): + children.append(child) + if (children and isinstance(children[0], nodes.paragraph) + and (isinstance(children[-1], nodes.bullet_list) + or isinstance(children[-1], nodes.enumerated_list))): + children.pop() + if len(children) <= 1: + return + else: + raise nodes.NodeFound + + # def visit_bullet_list(self, node): + # pass + + # def visit_enumerated_list(self, node): + # pass + + def visit_paragraph(self, node): + raise nodes.SkipNode + + def visit_definition_list(self, node): + raise nodes.NodeFound + + def visit_docinfo(self, node): + raise nodes.NodeFound diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/html4css1.css b/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/html4css1.css new file mode 100644 index 00000000..1d0d3e7c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/html4css1.css @@ -0,0 +1,350 @@ +/* +:Author: David Goodger (goodger@python.org) +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ +:Copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. + +See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to +customize this style sheet. +*/ + +/* used to remove borders from tables and images */ +.borderless, table.borderless td, table.borderless th { + border: 0 } + +table.borderless td, table.borderless th { + /* Override padding for "table.docutils td" with "! important". + The right padding separates the table cells. */ + padding: 0 0.5em 0 0 ! important } + +.first { + /* Override more specific margin styles with "! important". */ + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +dl.docutils dd { + margin-bottom: 0.5em } + +object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { + overflow: hidden; +} + +/* Uncomment (and remove this text!) to get bold-faced definition list terms +dl.docutils dt { + font-weight: bold } +*/ + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title, .code .error { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin: 0 0 0.5em 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1.title { + text-align: center } + +h2.subtitle { + text-align: center } + +hr.docutils { + width: 75% } + +img.align-left, .figure.align-left, object.align-left, table.align-left { + clear: left ; + float: left ; + margin-right: 1em } + +img.align-right, .figure.align-right, object.align-right, table.align-right { + clear: right ; + float: right ; + margin-left: 1em } + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left } + +.align-center { + clear: both ; + text-align: center } + +.align-right { + text-align: right } + +/* reset inner alignment in figures */ +div.align-right { + text-align: inherit } + +/* div.align-center * { */ +/* text-align: left } */ + +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font: inherit } + +pre.literal-block, pre.doctest-block, pre.math, pre.code { + margin-left: 2em ; + margin-right: 2em } + +pre.code .ln { color: gray; } /* line numbers */ +pre.code, code { background-color: #eeeeee } +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.pre { + white-space: pre } + +span.problematic, pre.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +table.docutils th.field-name, table.docinfo th.docinfo-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +/* "booktabs" style (no vertical lines) */ +table.docutils.booktabs { + border: 0px; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.docutils.booktabs * { + border: 0px; +} +table.docutils.booktabs th { + border-bottom: thin solid; + text-align: left; +} + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/template.txt b/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/template.txt new file mode 100644 index 00000000..2591bce3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html4css1/template.txt @@ -0,0 +1,8 @@ +%(head_prefix)s +%(head)s +%(stylesheet)s +%(body_prefix)s +%(body_pre_docinfo)s +%(docinfo)s +%(body)s +%(body_suffix)s diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/__init__.py new file mode 100644 index 00000000..c9bdf66c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/__init__.py @@ -0,0 +1,393 @@ +# $Id: __init__.py 9539 2024-02-17 10:36:51Z milde $ +# :Author: Günter Milde <milde@users.sf.net> +# Based on the html4css1 writer by David Goodger. +# :Maintainer: docutils-develop@lists.sourceforge.net +# :Copyright: © 2005, 2009, 2015 Günter Milde, +# portions from html4css1 © David Goodger. +# :License: Released under the terms of the `2-Clause BSD license`_, in short: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause + +# Use "best practice" as recommended by the W3C: +# http://www.w3.org/2009/cheatsheet/ + +""" +Plain HyperText Markup Language document tree Writer. + +The output conforms to the `HTML 5` specification. + +The cascading style sheet "minimal.css" is required for proper viewing, +the style sheet "plain.css" improves reading experience. +""" +__docformat__ = 'reStructuredText' + +from pathlib import Path + +from docutils import frontend, nodes +from docutils.writers import _html_base + + +class Writer(_html_base.Writer): + + supported = ('html5', 'xhtml', 'html') + """Formats this writer supports.""" + + default_stylesheets = ['minimal.css', 'plain.css'] + default_stylesheet_dirs = ['.', str(Path(__file__).parent)] + default_template = Path(__file__).parent / 'template.txt' + + # use a copy of the parent spec with some modifications + settings_spec = frontend.filter_settings_spec( + _html_base.Writer.settings_spec, + template=( + f'Template file. (UTF-8 encoded, default: "{default_template}")', + ['--template'], + {'default': default_template, 'metavar': '<file>'}), + stylesheet_path=( + 'Comma separated list of stylesheet paths. ' + 'Relative paths are expanded if a matching file is found in ' + 'the --stylesheet-dirs. With --link-stylesheet, ' + 'the path is rewritten relative to the output HTML file. ' + '(default: "%s")' % ','.join(default_stylesheets), + ['--stylesheet-path'], + {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', + 'validator': frontend.validate_comma_separated_list, + 'default': default_stylesheets}), + stylesheet_dirs=( + 'Comma-separated list of directories where stylesheets are found. ' + 'Used by --stylesheet-path when expanding relative path ' + 'arguments. (default: "%s")' % ','.join(default_stylesheet_dirs), + ['--stylesheet-dirs'], + {'metavar': '<dir[,dir,...]>', + 'validator': frontend.validate_comma_separated_list, + 'default': default_stylesheet_dirs}), + initial_header_level=( + 'Specify the initial header level. Does not affect document ' + 'title & subtitle (see --no-doc-title). (default: 2 for "<h2>")', + ['--initial-header-level'], + {'choices': '1 2 3 4 5 6'.split(), 'default': '2', + 'metavar': '<level>'}), + no_xml_declaration=( + 'Omit the XML declaration (default).', + ['--no-xml-declaration'], + {'dest': 'xml_declaration', 'action': 'store_false'}), + ) + settings_spec = settings_spec + ( + 'HTML5 Writer Options', + '', + ((frontend.SUPPRESS_HELP, # Obsoleted by "--image-loading" + ['--embed-images'], + {'action': 'store_true', + 'validator': frontend.validate_boolean}), + (frontend.SUPPRESS_HELP, # Obsoleted by "--image-loading" + ['--link-images'], + {'dest': 'embed_images', 'action': 'store_false'}), + ('Suggest at which point images should be loaded: ' + '"embed", "link" (default), or "lazy".', + ['--image-loading'], + {'choices': ('embed', 'link', 'lazy'), + # 'default': 'link' # default set in _html_base.py + }), + ('Append a self-link to section headings.', + ['--section-self-link'], + {'default': False, 'action': 'store_true'}), + ('Do not append a self-link to section headings. (default)', + ['--no-section-self-link'], + {'dest': 'section_self_link', 'action': 'store_false'}), + ) + ) + + config_section = 'html5 writer' + + def __init__(self): + self.parts = {} + self.translator_class = HTMLTranslator + + +class HTMLTranslator(_html_base.HTMLTranslator): + """ + This writer generates `polyglot markup`: HTML5 that is also valid XML. + + Safe subclassing: when overriding, treat ``visit_*`` and ``depart_*`` + methods as a unit to prevent breaks due to internal changes. See the + docstring of docutils.writers._html_base.HTMLTranslator for details + and examples. + """ + + # self.starttag() arguments for the main document + documenttag_args = {'tagname': 'main'} + + # add meta tag to fix rendering in mobile browsers + def __init__(self, document): + super().__init__(document) + self.meta.append('<meta name="viewport" ' + 'content="width=device-width, initial-scale=1" />\n') + + # <acronym> tag obsolete in HTML5. Use the <abbr> tag instead. + def visit_acronym(self, node): + # @@@ implementation incomplete ("title" attribute) + self.body.append(self.starttag(node, 'abbr', '')) + + def depart_acronym(self, node): + self.body.append('</abbr>') + + # no standard meta tag name in HTML5, use separate "author" meta tags + # https://www.w3.org/TR/html5/document-metadata.html#standard-metadata-names + def visit_authors(self, node): + self.visit_docinfo_item(node, 'authors', meta=False) + for subnode in node: + self.meta.append('<meta name="author" content=' + f'"{self.attval(subnode.astext())}" />\n') + + def depart_authors(self, node): + self.depart_docinfo_item() + + # use the <figcaption> semantic tag. + def visit_caption(self, node): + if isinstance(node.parent, nodes.figure): + self.body.append('<figcaption>\n') + self.body.append(self.starttag(node, 'p', '')) + + def depart_caption(self, node): + self.body.append('</p>\n') + # <figcaption> is closed in depart_figure(), as legend may follow. + + # use HTML block-level tags if matching class value found + supported_block_tags = {'ins', 'del'} + + def visit_container(self, node): + # If there is exactly one of the "supported block tags" in + # the list of class values, use it as tag name: + classes = node['classes'] + tags = [cls for cls in classes + if cls in self.supported_block_tags] + if len(tags) == 1: + node.html5tagname = tags[0] + classes.remove(tags[0]) + else: + node.html5tagname = 'div' + self.body.append(self.starttag(node, node.html5tagname, + CLASS='docutils container')) + + def depart_container(self, node): + self.body.append(f'</{node.html5tagname}>\n') + del node.html5tagname + + # no standard meta tag name in HTML5, use dcterms.rights + # see https://wiki.whatwg.org/wiki/MetaExtensions + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright', meta=False) + self.meta.append('<meta name="dcterms.rights" ' + f'content="{self.attval(node.astext())}" />\n') + + def depart_copyright(self, node): + self.depart_docinfo_item() + + # no standard meta tag name in HTML5, use dcterms.date + def visit_date(self, node): + self.visit_docinfo_item(node, 'date', meta=False) + self.meta.append('<meta name="dcterms.date" ' + f'content="{self.attval(node.astext())}" />\n') + + def depart_date(self, node): + self.depart_docinfo_item() + + # use new HTML5 <figure> and <figcaption> elements + def visit_figure(self, node): + atts = {} + if node.get('width'): + atts['style'] = f"width: {node['width']}" + if node.get('align'): + atts['class'] = f"align-{node['align']}" + self.body.append(self.starttag(node, 'figure', **atts)) + + def depart_figure(self, node): + if len(node) > 1: + self.body.append('</figcaption>\n') + self.body.append('</figure>\n') + + # use HTML5 <footer> element + def visit_footer(self, node): + self.context.append(len(self.body)) + + def depart_footer(self, node): + start = self.context.pop() + footer = [self.starttag(node, 'footer')] + footer.extend(self.body[start:]) + footer.append('</footer>\n') + self.footer.extend(footer) + self.body_suffix[:0] = footer + del self.body[start:] + + # use HTML5 <header> element + def visit_header(self, node): + self.context.append(len(self.body)) + + def depart_header(self, node): + start = self.context.pop() + header = [self.starttag(node, 'header')] + header.extend(self.body[start:]) + header.append('</header>\n') + self.body_prefix.extend(header) + self.header.extend(header) + del self.body[start:] + + # use HTML text-level tags if matching class value found + supported_inline_tags = {'code', 'kbd', 'dfn', 'samp', 'var', + 'bdi', 'del', 'ins', 'mark', 'small', + 'b', 'i', 'q', 's', 'u'} + + # Use `supported_inline_tags` if found in class values + def visit_inline(self, node): + classes = node['classes'] + node.html5tagname = 'span' + # Special handling for "code" directive content + if (isinstance(node.parent, nodes.literal_block) + and 'code' in node.parent.get('classes') + or isinstance(node.parent, nodes.literal) + and getattr(node.parent, 'html5tagname', None) == 'code'): + if classes == ['ln']: + # line numbers are not part of the "fragment of computer code" + if self.body[-1] == '<code>': + del self.body[-1] + else: + self.body.append('</code>') + node.html5tagname = 'small' + else: + tags = [cls for cls in self.supported_inline_tags + if cls in classes] + if len(tags): + node.html5tagname = tags[0] + classes.remove(node.html5tagname) + self.body.append(self.starttag(node, node.html5tagname, '')) + + def depart_inline(self, node): + self.body.append(f'</{node.html5tagname}>') + if (node.html5tagname == 'small' and node.get('classes') == ['ln'] + and isinstance(node.parent, nodes.literal_block)): + self.body.append(f'<code data-lineno="{node.astext()}">') + del node.html5tagname + + # place inside HTML5 <figcaption> element (together with caption) + def visit_legend(self, node): + if not isinstance(node.parent[1], nodes.caption): + self.body.append('<figcaption>\n') + self.body.append(self.starttag(node, 'div', CLASS='legend')) + + def depart_legend(self, node): + self.body.append('</div>\n') + # <figcaption> closed in visit_figure() + + # use HTML5 text-level tags if matching class value found + def visit_literal(self, node): + classes = node['classes'] + html5tagname = 'span' + tags = [cls for cls in self.supported_inline_tags + if cls in classes] + if len(tags): + html5tagname = tags[0] + classes.remove(html5tagname) + if html5tagname == 'code': + node.html5tagname = html5tagname + self.body.append(self.starttag(node, html5tagname, '')) + return + self.body.append( + self.starttag(node, html5tagname, '', CLASS='docutils literal')) + text = node.astext() + # remove hard line breaks (except if in a parsed-literal block) + if not isinstance(node.parent, nodes.literal_block): + text = text.replace('\n', ' ') + # Protect text like ``--an-option`` and the regular expression + # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping + for token in self.words_and_spaces.findall(text): + if token.strip() and self.in_word_wrap_point.search(token): + self.body.append( + f'<span class="pre">{self.encode(token)}</span>') + else: + self.body.append(self.encode(token)) + self.body.append(f'</{html5tagname}>') + # Content already processed: + raise nodes.SkipNode + + def depart_literal(self, node): + # skipped unless literal element is from "code" role: + self.depart_inline(node) + + # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 + # HTML5/polyglot recommends using both + def visit_meta(self, node): + if node.hasattr('lang'): + node['xml:lang'] = node['lang'] + self.meta.append(self.emptytag(node, 'meta', + **node.non_default_attributes())) + + def depart_meta(self, node): + pass + + # no standard meta tag name in HTML5 + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization', meta=False) + + def depart_organization(self, node): + self.depart_docinfo_item() + + # use the new HTML5 element <section> + def visit_section(self, node): + self.section_level += 1 + self.body.append( + self.starttag(node, 'section')) + + def depart_section(self, node): + self.section_level -= 1 + self.body.append('</section>\n') + + # use the new HTML5 element <aside> + def visit_sidebar(self, node): + self.body.append( + self.starttag(node, 'aside', CLASS='sidebar')) + self.in_sidebar = True + + def depart_sidebar(self, node): + self.body.append('</aside>\n') + self.in_sidebar = False + + # Use new HTML5 element <aside> or <nav> + # Add class value to <body>, if there is a ToC in the document + # (see responsive.css how this is used for a navigation sidebar). + def visit_topic(self, node): + atts = {'classes': ['topic']} + if 'contents' in node['classes']: + node.html5tagname = 'nav' + del atts['classes'] + if isinstance(node.parent, nodes.document): + atts['role'] = 'doc-toc' + self.body_prefix[0] = '</head>\n<body class="with-toc">\n' + elif 'abstract' in node['classes']: + node.html5tagname = 'div' + atts['role'] = 'doc-abstract' + elif 'dedication' in node['classes']: + node.html5tagname = 'div' + atts['role'] = 'doc-dedication' + else: + node.html5tagname = 'aside' + self.body.append(self.starttag(node, node.html5tagname, **atts)) + + def depart_topic(self, node): + self.body.append(f'</{node.html5tagname}>\n') + del node.html5tagname + + # append self-link + def section_title_tags(self, node): + start_tag, close_tag = super().section_title_tags(node) + ids = node.parent['ids'] + if (ids and getattr(self.settings, 'section_self_link', None) + and not isinstance(node.parent, nodes.document)): + self_link = ('<a class="self-link" title="link to this section"' + f' href="#{ids[0]}"></a>') + close_tag = close_tag.replace('</h', self_link + '</h') + return start_tag, close_tag diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/italic-field-names.css b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/italic-field-names.css new file mode 100644 index 00000000..75908529 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/italic-field-names.css @@ -0,0 +1,26 @@ +/* italic-field-name.css: */ +/* Alternative style for Docutils field-lists */ + +/* :Copyright: © 2023 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ + +/* In many contexts, a **bold** field name is too heavy styling. */ +/* Use *italic* instead:: */ + +dl.field-list > dt { + font-weight: normal; + font-style: italic; +} +dl.field-list > dt > .colon { + font-style: normal; + padding-left: 0.05ex; +} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/math.css b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/math.css new file mode 100644 index 00000000..eb1ba72e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/math.css @@ -0,0 +1,332 @@ +/* +* math2html: convert LaTeX equations to HTML output. +* +* Copyright (C) 2009,2010 Alex Fernández +* 2021 Günter Milde +* +* Released under the terms of the `2-Clause BSD license'_, in short: +* Copying and distribution of this file, with or without modification, +* are permitted in any medium without royalty provided the copyright +* notice and this notice are preserved. +* This file is offered as-is, without any warranty. +* +* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause +* +* Based on eLyXer: convert LyX source files to HTML output. +* http://elyxer.nongnu.org/ +* +* +* CSS file for LaTeX formulas. +* +* References: http://www.zipcon.net/~swhite/docs/math/math.html +* http://www.cs.tut.fi/~jkorpela/math/ +*/ + +/* Formulas */ +.formula { + text-align: center; + margin: 1.2em 0; + line-height: 1.4; +} +span.formula { + white-space: nowrap; +} +div.formula { + padding: 0.5ex; + margin-left: auto; + margin-right: auto; +} + +/* Basic features */ +a.eqnumber { + display: inline-block; + float: right; + clear: right; + font-weight: bold; +} +span.unknown { + color: #800000; +} +span.ignored, span.arraydef { + display: none; +} +.phantom { + visibility: hidden; +} +.formula i { + letter-spacing: 0.1ex; +} + +/* Alignment */ +.align-l { + text-align: left; +} +.align-r { + text-align: right; +} +.align-c { + text-align: center; +} + +/* Structures */ +span.hspace { + display: inline-block; +} +span.overline, span.bar { + text-decoration: overline; +} +.fraction, .fullfraction, .textfraction { + display: inline-block; + vertical-align: middle; + text-align: center; +} +span.formula .fraction, +.textfraction, +span.smallmatrix { + font-size: 80%; + line-height: 1; +} +span.numerator { + display: block; + line-height: 1; +} +span.denominator { + display: block; + line-height: 1; + padding: 0ex; + border-top: thin solid; +} +.formula sub, .formula sup { + font-size: 80%; +} +sup.numerator, sup.unit { + vertical-align: 80%; +} +sub.denominator, sub.unit { + vertical-align: -20%; +} +span.smallsymbol { + font-size: 75%; + line-height: 75%; +} +span.boldsymbol { + font-weight: bold; +} +span.sqrt { + display: inline-block; + vertical-align: middle; + padding: 0.1ex; +} +sup.root { + position: relative; + left: 1.4ex; +} +span.radical { + display: inline-block; + padding: 0ex; + /* font-size: 160%; for DejaVu, not required with STIX */ + line-height: 100%; + vertical-align: top; + vertical-align: middle; +} + +span.root { + display: inline-block; + border-top: thin solid; + padding: 0ex; + vertical-align: middle; +} +div.formula .bigoperator, +.displaystyle .bigoperator, +.displaystyle .bigoperator { + line-height: 120%; + font-size: 140%; + padding-right: 0.2ex; +} +span.fraction .bigoperator, +span.scriptstyle .bigoperator { + line-height: inherit; + font-size: inherit; + padding-right: 0; +} +span.bigdelimiter { + display: inline-block; +} +span.bigdelimiter.size1 { + transform: scale(1, 1.2); + line-height: 1.2; +} +span.bigdelimiter.size2 { + transform: scale(1, 1.62); + line-height: 1.62%; + +} +span.bigdelimiter.size3 { + transform: scale(1, 2.05); + line-height: 2.05%; +} +span.bigdelimiter.size4 { + transform: scale(1, 2.47); + line-height: 2.47%; +} +/* vertically stacked sub and superscript */ +span.scripts { + display: inline-table; + vertical-align: middle; + padding-right: 0.2ex; +} +.script { + display: table-row; + text-align: left; + line-height: 150%; +} +span.limits { + display: inline-table; + vertical-align: middle; +} +.limit { + display: table-row; + line-height: 99%; +} +sup.limit, sub.limit { + line-height: 100%; +} +span.embellished, +span.embellished > .base { + display: inline-block; +} +span.embellished > sup, +span.embellished > sub { + display: inline-block; + font-size: 100%; + position: relative; + bottom: 0.3em; + width: 0px; +} +span.embellished > sub { + top: 0.4em; +} + +/* Environments */ +span.array, span.bracketcases, span.binomial, span.environment { + display: inline-table; + text-align: center; + vertical-align: middle; +} +span.arrayrow, span.binomrow { + display: table-row; + padding: 0; + border: 0; +} +span.arraycell, span.bracket, span.case, span.binomcell, span.environmentcell { + display: table-cell; + padding: 0ex 0.2ex; + line-height: 1; /* 99%; */ + border: 0ex; +} +.environment.align > .arrayrow > .arraycell.align-l { + padding-right: 2em; +} + +/* Inline binomials */ +span.binom { + display: inline-block; + vertical-align: middle; + text-align: center; + font-size: 80%; +} +span.binomstack { + display: block; + padding: 0em; +} + +/* Over- and underbraces */ +span.overbrace { + border-top: 2pt solid; +} +span.underbrace { + border-bottom: 2pt solid; +} + +/* Stackrel */ +span.stackrel { + display: inline-block; + text-align: center; +} +span.upstackrel { + display: block; + padding: 0em; + font-size: 80%; + line-height: 64%; + position: relative; + top: 0.15em; + +} +span.downstackrel { + display: block; + vertical-align: bottom; + padding: 0em; +} + +/* Fonts */ +.formula { + font-family: STIX, "DejaVu Serif", "DejaVu Math TeX Gyre", serif; +} +span.radical, /* ensure correct size of square-root sign */ +span.integral { /* upright integral signs for better alignment of indices */ + font-family: "STIXIntegralsUp", STIX; + /* font-size: 115%; match apparent size with DejaVu */ +} +span.bracket { + /* some "STIX" and "DejaVu Math TeX Gyre" bracket pieces don't fit */ + font-family: "DejaVu Serif", serif; +} +span.mathsf, span.textsf { + font-family: sans-serif; +} +span.mathrm, span.textrm { + font-family: STIX, "DejaVu Serif", "DejaVu Math TeX Gyre", serif; +} +span.mathtt, span.texttt { + font-family: monospace; +} +span.text, span.textnormal, +span.mathsf, span.mathtt, span.mathrm { + font-style: normal; +} +span.fraktur { + font-family: "Lucida Blackletter", eufm10, blackletter; +} +span.blackboard { + font-family: Blackboard, msbm10, serif; +} +span.scriptfont { + font-family: "Monotype Corsiva", "Apple Chancery", "URW Chancery L", cursive; + font-style: italic; +} +span.mathscr { + font-family: MathJax_Script, rsfs10, cursive; + font-style: italic; +} +span.textsc { + font-variant: small-caps; +} +span.textsl { + font-style: oblique; +} + +/* Colors */ +span.colorbox { + display: inline-block; + padding: 5px; +} +span.fbox { + display: inline-block; + border: thin solid black; + padding: 2px; +} +span.boxed, span.framebox { + display: inline-block; + border: thin solid black; + padding: 5px; +} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/minimal.css b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/minimal.css new file mode 100644 index 00000000..66f0658d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/minimal.css @@ -0,0 +1,293 @@ +/* Minimal style sheet for the HTML output of Docutils. */ +/* */ +/* :Author: Günter Milde, based on html4css1.css by David Goodger */ +/* :Id: $Id: minimal.css 9545 2024-02-17 10:37:56Z milde $ */ +/* :Copyright: © 2015, 2021 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ + +/* This CSS3 stylesheet defines rules for Docutils elements without */ +/* HTML equivalent. It is required to make the document semantics visible. */ +/* */ +/* .. _validates: http://jigsaw.w3.org/css-validator/validator$link */ + +/* titles */ +p.topic-title, +p.admonition-title, +p.system-message-title { + font-weight: bold; +} +p.sidebar-title, +p.rubric { + font-weight: bold; + font-size: larger; +} +p.rubric { + color: maroon; +} +p.subtitle, +p.section-subtitle, +p.sidebar-subtitle { + font-weight: bold; + margin-top: -0.5em; +} +h1 + p.subtitle { + font-size: 1.6em; +} +a.toc-backref { + color: inherit; + text-decoration: none; +} + +/* Warnings, Errors */ +.system-messages h2, +.system-message-title, +pre.problematic, +span.problematic { + color: red; +} + +/* Inline Literals */ +.docutils.literal { + font-family: monospace; + white-space: pre-wrap; +} +/* do not wrap at hyphens and similar: */ +.literal > span.pre { white-space: nowrap; } + +/* keep line-breaks (\n) visible */ +.pre-wrap { white-space: pre-wrap; } + +/* Lists */ + +/* compact and simple lists: no margin between items */ +.simple li, .simple ul, .simple ol, +.compact li, .compact ul, .compact ol, +.simple > li p, dl.simple > dd, +.compact > li p, dl.compact > dd { + margin-top: 0; + margin-bottom: 0; +} +/* Nested Paragraphs */ +p:first-child { margin-top: 0; } +p:last-child { margin-bottom: 0; } +details > p:last-child { margin-bottom: 1em; } + +/* Table of Contents */ +.contents ul.auto-toc { /* section numbers present */ + list-style-type: none; +} + +/* Enumerated Lists */ +ol.arabic { list-style: decimal } +ol.loweralpha { list-style: lower-alpha } +ol.upperalpha { list-style: upper-alpha } +ol.lowerroman { list-style: lower-roman } +ol.upperroman { list-style: upper-roman } + +/* Definition Lists and Derivatives */ +dt .classifier { font-style: italic } +dt .classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} +/* Field Lists and similar */ +/* bold field name, content starts on the same line */ +dl.field-list, +dl.option-list, +dl.docinfo { + display: flow-root; +} +dl.field-list > dt, +dl.option-list > dt, +dl.docinfo > dt { + font-weight: bold; + clear: left; + float: left; + margin: 0; + padding: 0; + padding-right: 0.25em; +} +/* Offset for field content (corresponds to the --field-name-limit option) */ +dl.field-list > dd, +dl.option-list > dd, +dl.docinfo > dd { + margin-left: 9em; /* ca. 14 chars in the test examples, fit all Docinfo fields */ +} +/* start nested lists on new line */ +dd > dl:first-child, +dd > ul:first-child, +dd > ol:first-child { + clear: left; +} +/* start field-body on a new line after long field names */ +dl.field-list > dd > *:first-child, +dl.option-list > dd > *:first-child +{ + display: inline-block; + width: 100%; + margin: 0; +} + +/* Bibliographic Fields (docinfo) */ +dl.docinfo pre.address { + font: inherit; + margin: 0.5em 0; +} +dl.docinfo > dd.authors > p { margin: 0; } + +/* Option Lists */ +dl.option-list > dt { font-weight: normal; } +span.option { white-space: nowrap; } + +/* Footnotes and Citations */ + +.footnote, .citation { margin: 1em 0; } /* default paragraph skip (Firefox) */ +/* hanging indent */ +.citation { padding-left: 2em; } +.footnote { padding-left: 1.7em; } +.footnote.superscript { padding-left: 1.0em; } +.citation > .label { margin-left: -2em; } +.footnote > .label { margin-left: -1.7em; } +.footnote.superscript > .label { margin-left: -1.0em; } + +.footnote > .label + *, +.citation > .label + * { + display: inline-block; + margin-top: 0; + vertical-align: top; +} +.footnote > .backrefs + *, +.citation > .backrefs + * { + margin-top: 0; +} +.footnote > .label + p, .footnote > .backrefs + p, +.citation > .label + p, .citation > .backrefs + p { + display: inline; + vertical-align: inherit; +} + +.backrefs { user-select: none; } +.backrefs > a { font-style: italic; } + +/* superscript footnotes */ +a[role="doc-noteref"].superscript, +.footnote.superscript > .label, +.footnote.superscript > .backrefs { + vertical-align: super; + font-size: smaller; + line-height: 1; +} +a[role="doc-noteref"].superscript > .fn-bracket, +.footnote.superscript > .label > .fn-bracket { + /* hide brackets in display but leave for copy/paste */ + display: inline-block; + width: 0; + overflow: hidden; +} +[role="doc-noteref"].superscript + [role="doc-noteref"].superscript { + padding-left: 0.15em; /* separate consecutive footnote references */ + /* TODO: unfortunately, "+" also selects with text between the references. */ +} + +/* Alignment */ +.align-left { + text-align: left; + margin-right: auto; +} +.align-center { + text-align: center; + margin-left: auto; + margin-right: auto; +} +.align-right { + text-align: right; + margin-left: auto; +} +.align-top { vertical-align: top; } +.align-middle { vertical-align: middle; } +.align-bottom { vertical-align: bottom; } + +/* reset inner alignment in figures and tables */ +figure.align-left, figure.align-right, +table.align-left, table.align-center, table.align-right { + text-align: inherit; +} + +/* Text Blocks */ +.topic { margin: 1em 2em; } +.sidebar, +.admonition, +.system-message { + margin: 1em 2em; + border: thin solid; + padding: 0.5em 1em; +} +div.line-block { display: block; } +div.line-block div.line-block, pre { margin-left: 2em; } + +/* Code line numbers: dropped when copying text from the page */ +pre.code .ln { display: none; } +pre.code code:before { + content: attr(data-lineno); /* …, none) fallback not supported by any browser */ + color: gray; +} + +/* Tables */ +table { + border-collapse: collapse; +} +td, th { + border: thin solid silver; + padding: 0 1ex; +} +.borderless td, .borderless th { + border: 0; + padding: 0; + padding-right: 0.5em /* separate table cells */ +} + +table > caption, figcaption { + text-align: left; + margin-top: 0.2em; + margin-bottom: 0.2em; +} +table.captionbelow { + caption-side: bottom; +} + +/* MathML (see "math.css" for --math-output=HTML) */ +math .boldsymbol { font-weight: bold; } +math.boxed, math .boxed {padding: 0.25em; border: thin solid; } +/* style table similar to AMS "align" or "aligned" environment: */ +mtable.cases > mtr > mtd { text-align: left; } +mtable.ams-align > mtr > mtd { padding-left: 0; padding-right: 0; } +mtable.ams-align > mtr > mtd:nth-child(2n) { text-align: left; } +mtable.ams-align > mtr > mtd:nth-child(2n+1) { text-align: right; } +mtable.ams-align > mtr > mtd:nth-child(2n+3) { padding-left: 2em; } +.mathscr mi, mi.mathscr { + font-family: STIX, XITSMathJax_Script, rsfs10, + "Asana Math", Garamond, cursive; +} + +/* Document Header and Footer */ +header { border-bottom: 1px solid black; } +footer { border-top: 1px solid black; } + +/* Images are block-level by default in Docutils */ +/* New HTML5 block elements: set display for older browsers */ +img, svg, header, footer, main, aside, nav, section, figure, video, details { + display: block; +} +svg { width: auto; height: auto; } /* enable scaling of SVG images */ +/* inline images */ +p img, p svg, p video { display: inline; } diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/plain.css b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/plain.css new file mode 100644 index 00000000..f0f089bb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/plain.css @@ -0,0 +1,307 @@ +/* CSS31_ style sheet for the output of Docutils HTML writers. */ +/* Rules for easy reading and pre-defined style variants. */ +/* */ +/* :Author: Günter Milde, based on html4css1.css by David Goodger */ +/* :Id: $Id: plain.css 9615 2024-04-06 13:28:15Z milde $ */ +/* :Copyright: © 2015 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ +/* .. _CSS3: https://www.w3.org/Style/CSS/ */ + + +/* Document Structure */ +/* ****************** */ + +/* "page layout" */ +body { + margin: 0; + background-color: #dbdbdb; + --field-indent: 9em; /* default indent of fields in field lists */ +} +main, footer, header { + line-height:1.6; + /* avoid long lines --> better reading */ + /* optimum is 45…75 characters/line <http://webtypography.net/2.1.2> */ + /* OTOH: lines should not be too short because of missing hyphenation, */ + max-width: 50rem; + padding: 1px 2%; /* 1px on top avoids grey bar above title (mozilla) */ + margin: auto; +} +main { + counter-reset: table figure; + background-color: white; +} +footer, header { + font-size: smaller; + padding: 0.5em 2%; + border: none; +} + +/* Table of Contents */ +ul.auto-toc > li > p { + padding-left: 1em; + text-indent: -1em; +} +nav.contents ul { + padding-left: 1em; +} +main > nav.contents ul ul ul ul:not(.auto-toc) { + list-style-type: '\2B29\ '; +} +main > nav.contents ul ul ul ul ul:not(.auto-toc) { + list-style-type: '\2B1D\ '; +} + +/* Transitions */ +hr.docutils { + width: 80%; + margin-top: 1em; + margin-bottom: 1em; + clear: both; +} + +/* Paragraphs */ + +/* vertical space (parskip) */ +p, ol, ul, dl, li, +.footnote, .citation, +div > math, +table { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +h1, h2, h3, h4, h5, h6, +dd, details > p:last-child { + margin-bottom: 0.5em; +} + +/* Lists */ +/* ===== */ + +/* Definition Lists */ +/* Indent lists nested in definition lists */ +dd > ul:only-child, dd > ol:only-child { padding-left: 1em; } + +/* Description Lists */ +/* styled like in most dictionaries, encyclopedias etc. */ +dl.description { + display: flow-root; +} +dl.description > dt { + font-weight: bold; + clear: left; + float: left; + margin: 0; + padding: 0; + padding-right: 0.3em; +} +dl.description > dd:after { + display: table; + content: ""; + clear: left; /* clearfix for empty descriptions */ +} + +/* Field Lists */ + +dl.field-list > dd, +dl.docinfo > dd { + margin-left: var(--field-indent); /* adapted in media queries or HTML */ +} + +/* example for custom field-name width */ +dl.field-list.narrow > dd { + --field-indent: 5em; +} +/* run-in: start field-body on same line after long field names */ +dl.field-list.run-in > dd p { + display: block; +} + +/* Bibliographic Fields */ + +/* generally, bibliographic fields use dl.docinfo */ +/* but dedication and abstract are placed into divs */ +div.abstract p.topic-title { + text-align: center; +} +div.dedication { + margin: 2em 5em; + text-align: center; + font-style: italic; +} +div.dedication p.topic-title { + font-style: normal; +} + +/* disclosures */ +details { padding-left: 1em; } +summary { margin-left: -1em; } + +/* Text Blocks */ +/* =========== */ + +/* Literal Blocks */ +pre.literal-block, pre.doctest-block, +pre.math, pre.code { + font-family: monospace; +} + +/* Block Quotes and Topics */ +bockquote { margin: 1em 2em; } +blockquote p.attribution, +.topic p.attribution { + text-align: right; + margin-left: 20%; +} + +/* Tables */ +/* ====== */ + +/* th { vertical-align: bottom; } */ + +table tr { text-align: left; } + +/* "booktabs" style (no vertical lines) */ +table.booktabs { + border: 0; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.booktabs * { + border: 0; +} +table.booktabs th { + border-bottom: thin solid; +} + +/* numbered tables (counter defined in div.document) */ +table.numbered > caption:before { + counter-increment: table; + content: "Table " counter(table) ": "; + font-weight: bold; +} + +/* Explicit Markup Blocks */ +/* ====================== */ + +/* Footnotes and Citations */ +/* ----------------------- */ + +/* line on the left */ +.footnote-list { + border-left: solid thin; + padding-left: 0.25em; +} + +/* Directives */ +/* ---------- */ + +/* Body Elements */ +/* ~~~~~~~~~~~~~ */ + +/* Images and Figures */ + +/* let content flow to the side of aligned images and figures */ +figure.align-left, +img.align-left, +svg.align-left, +video.align-left, +div.align-left, +object.align-left { + clear: left; + float: left; + margin-right: 1em; +} +figure.align-right, +img.align-right, +svg.align-right, +video.align-right, +div.align-right, +object.align-right { + clear: right; + float: right; + margin-left: 1em; +} +/* Stop floating sidebars, images and figures */ +h1, h2, h3, h4, footer, header { clear: both; } + +/* Numbered figures */ +figure.numbered > figcaption > p:before { + counter-increment: figure; + content: "Figure " counter(figure) ": "; + font-weight: bold; +} + +/* Admonitions and System Messages */ +.caution p.admonition-title, +.attention p.admonition-title, +.danger p.admonition-title, +.error p.admonition-title, +.warning p.admonition-title, +div.error { + color: red; +} + +/* Sidebar */ +/* Move right. In a layout with fixed margins, */ +/* it can be moved into the margin. */ +aside.sidebar { + width: 30%; + max-width: 26em; + float: right; + clear: right; + margin-left: 1em; + margin-right: -1%; + background-color: #fffffa; +} + + +/* Code */ +pre.code { padding: 0.7ex } +pre.code, code { background-color: #eeeeee } +/* basic highlighting: for a complete scheme, see */ +/* https://docutils.sourceforge.io/sandbox/stylesheets/ */ +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + + +/* Epigraph */ +/* Highlights */ +/* Pull-Quote */ +/* Compound Paragraph */ +/* Container */ + +/* Inline Markup */ +/* ============= */ + +sup, sub { line-height: 0.8; } /* do not add leading for lines with sup/sub */ + +/* Inline Literals */ +/* possible values: normal, nowrap, pre, pre-wrap, pre-line */ +/* span.docutils.literal { white-space: pre-wrap; } */ + +/* Hyperlink References */ +a { text-decoration: none; } + +/* External Targets */ +/* span.target.external */ +/* Internal Targets */ +/* span.target.internal */ +/* Footnote References */ +/* a[role="doc-noteref"] */ +/* Citation References */ +/* a.citation-reference */ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/responsive.css b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/responsive.css new file mode 100644 index 00000000..234fa90b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/responsive.css @@ -0,0 +1,486 @@ +/* CSS3_ style sheet for the output of Docutils HTML5 writer. */ +/* Generic responsive design for all screen sizes. */ +/* */ +/* :Author: Günter Milde */ +/* */ +/* :Id: $Id: responsive.css 9615 2024-04-06 13:28:15Z milde $ */ +/* :Copyright: © 2021 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ +/* .. _CSS3: https://www.w3.org/Style/CSS/ */ + +/* Note: */ +/* This style sheet is provisional: */ +/* the API is not settled and may change with any minor Docutils version. */ + + + +/* General Settings */ +/* ================ */ + + +* { box-sizing: border-box; } + +body { + background-color: #fafaf6; + margin: auto; + --field-indent: 6.6em; /* indent of fields in field lists */ + --sidebar-margin-right: 0; /* adapted in media queries below */ +} +main { + counter-reset: figure table; +} +body > * { + background-color: white; + line-height: 1.6; + padding: 0.5rem calc(29% - 7.2rem); /* go from 5% to 15% (8.15em/54em) */ + margin: auto; + max-width: 100rem; +} +sup, sub { /* avoid additional inter-line space for lines with sup/sub */ + line-height: 1; +} + +/* Vertical Space (Parskip) */ +p, ol, ul, dl, li, +.topic, +.footnote, .citation, +div > math, +table { + margin-top: 0.5em; + margin-bottom: 0.5em; +} +h1, h2, h3, h4, h5, h6, +dl > dd, details > p:last-child { + margin-bottom: 0.5em; +} + +/* Indented Blocks */ +blockquote, figure, .topic { + margin: 1em 2%; + padding-left: 1em; +} +div.line-block div.line-block, +pre, dd, dl.option-list { + margin-left: calc(2% + 1em); +} + +/* Object styling */ +/* ============== */ + +footer, header { + font-size: small; +} + +/* Frontmatter */ +div.dedication { + padding: 0; + margin: 1.4em 0; + font-style: italic; + font-size: large; +} +.dedication p.topic-title { + display: none; +} + +blockquote p.attribution, +.topic p.attribution { + text-align: right; +} + +/* Table of Contents */ +nav.contents ul { + padding-left: 1em; +} +ul.auto-toc > li > p { /* hanging indent */ + padding-left: 1em; + text-indent: -1em; +} +main > nav.contents ul:not(.auto-toc) { + list-style-type: square; +} +main > nav.contents ul ul:not(.auto-toc) { + list-style-type: disc; +} +main > nav.contents ul ul ul:not(.auto-toc) { + list-style-type: '\2B29\ '; +} +main > nav.contents ul ul ul ul:not(.auto-toc) { + list-style-type: '\2B1D\ '; +} +main > nav.contents ul ul ul ul ul:not(.auto-toc) { + list-style-type: '\2B2A\ '; +} +nav.contents ul > li::marker { + color: grey; +} + +/* Transitions */ +hr { + margin: 1em 10%; +} + +/* Lists */ + +dl.field-list.narrow, dl.docinfo, dl.option-list { + --field-indent: 2.4em; +} + +ul, ol { + padding-left: 1.1em; /* indent by bullet width (Firefox, DejaVu fonts) */ +} +dl.field-list > dd, +dl.docinfo > dd { + margin-left: var(--field-indent); /* adapted in media queries or HTML */ +} +dl.option-list > dd { + margin-left: 20%; +} +/* run-in: start field-body on same line after long field names */ +dl.field-list.run-in > dd p { + display: block; +} +/* "description style" like in most dictionaries, encyclopedias etc. */ +dl.description { + display: flow-root; +} +dl.description > dt { + clear: left; + float: left; + margin: 0; + padding: 0; + padding-right: 0.3em; + font-weight: bold; +} +dl.description > dd:after { + display: table; + content: ""; + clear: left; /* clearfix for empty descriptions */ +} +/* start lists nested in description/field lists on new line */ +dd > dl:first-child, +dd > ul:first-child, +dd > ol:first-child { + clear: left; +} + +/* disclosures */ +details { padding-left: 1em; } +summary { margin-left: -1em; } + +/* Footnotes and Citations */ +.footnote { + font-size: small; +} + +/* Images, Figures, and Tables */ +figcaption, +table > caption { + /* font-size: small; */ + font-style: italic; +} +figcaption > .legend { + font-size: small; + font-style: initial; +} +figure.numbered > figcaption > p:before { + counter-increment: figure; + content: "Figure " counter(figure) ": "; + font-weight: bold; + font-style: initial; +} + +table tr { + text-align: left; + vertical-align: baseline; +} +table.booktabs { /* "booktabs" style (no vertical lines) */ + border-top: 2px solid; + border-bottom: 2px solid; +} +table.booktabs * { + border: 0; +} +table.booktabs th { + border-bottom: thin solid; +} +table.numbered > caption:before { + counter-increment: table; + content: "Table " counter(table) ": "; + font-weight: bold; + font-style: initial; +} + +/* Admonitions and System Messages */ +.admonition, +div.system-message { + border: thin solid silver; + margin: 1em 2%; + padding: 0.5em 1em; +} +.caution p.admonition-title, +.attention p.admonition-title, +.danger p.admonition-title, +.warning p.admonition-title, +div.error { + color: maroon; +} +div.system-message > p > span.literal { + overflow-wrap: break-word; +} + +/* Literal and Code */ +pre.literal-block, pre.doctest{ + padding: 0.2em; + overflow-x: auto; +} +.literal-block, .doctest, span.literal { + background-color: #f6f9f8; +} +.system-message span.literal { + background-color: inherit; +} + +/* basic highlighting: for a complete scheme, see */ +/* https://docutils.sourceforge.io/sandbox/stylesheets/ */ +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +/* Hyperlink References */ +a { + text-decoration: none; /* for chromium */ + /* Wrap links at any place, if this is the only way to prevent overflow */ + overflow-wrap: break-word; +} +.contents a, a.toc-backref, a.citation-reference { + overflow-wrap: inherit; +} +/* Undecorated Links (see also minimal.css) */ +/* a.citation-reference, */ +.citation a.fn-backref { + color: inherit; +} +a:hover { + text-decoration: underline; +} +*:hover > a.toc-backref:after, +.topic-title:hover > a:after { + content: " \2191"; /* ↑ UPWARDS ARROW */ + color: grey; +} +*:hover > a.self-link:after { + content: "\1F517"; /* LINK SYMBOL */ + color: grey; + font-size: smaller; + margin-left: 0.2em; +} +/* highlight specific targets of the current URL */ +section:target > h2, section:target > h3, section:target > h4, +section:target > h5, section:target > h6, +span:target + h2, span:target + h3, span:target + h4, +span:target + h5, span:target + h6, +dt:target, span:target, +.contents :target, +.contents:target > .topic-title, +[role="doc-biblioentry"]:target > .label, +[role="doc-biblioref"]:target, +[role="note"]:target, /* Docutils 0.18 ... 0.19 */ +[role="doc-footnote"]:target, /* Docutils >= 0.20 */ +[role="doc-noteref"]:target { + background-color: #d2e6ec; +} + +/* Block Alignment */ +/* Let content flow to the side of aligned images and figures */ + +/* no floats around this elements */ +footer, header, hr, +h1, h2, h3 { + clear: both; +} + +img.align-left, +svg.align-left, +video.align-left, +figure.align-left, +div.align-left, +table.align-left { + margin-left: 0; + padding-left: 0; + margin-right: 0.5em; + clear: left; + float: left; +} +img.align-right, +svg.align-right, +video.align-right, +figure.align-right, +div.align-right, +table.align-right { + margin-left: 0.5em; + margin-right: 0; + clear: right; + float: right; +} + +/* Margin Elements */ +/* see below for screen size dependent rules */ +.sidebar, +.marginal, +.admonition.marginal { + max-width: 40%; + border: none; + background-color: #efefea; + margin: 0.5em var(--sidebar-margin-right) 0.5em 1em; + padding: 0.5em; + padding-left: 0.7em; + clear: right; + float: right; + font-size: small; +} +.sidebar { + width: 40%; +} + +/* Adaptive page layout */ +/* ==================== */ + +@media (max-width: 30em) { + /* Smaller margins and no floating elements for small screens */ + /* (main text less than 40 characters/line) */ + body > * { + padding: 0.5rem 5%; + line-height: 1.4 + } + .sidebar, + .marginal, + .admonition.marginal { + width: auto; + max-width: 100%; + float: none; + } + dl.option-list, + pre { + margin-left: 0; + } + body { + --field-indent: 4em; + } + pre, pre * { + font-size: 0.9em; + /* overflow: auto; */ + } +} + +@media (min-width: 54em) { + /* Move ToC to the left */ + /* Main text width before: 70% ≙ 35em ≙ 75…95 chrs (Dejavu/Times) */ + /* after: ≳ 30em ≙ 54…70 chrs (Dejavu/Times) */ + body.with-toc { + padding-left: 8%; + } + body.with-toc > * { + margin-left: 0; + padding-left: 22rem; /* fallback for webkit */ + padding-left: min(22%, 22rem); + padding-right: 7%; + } + main > nav.contents { /* global ToC */ + position: fixed; + top: 0; + left: 0; + width: min(25%, 25em); + height: 100vh; + margin: 0; + background-color: #fafaf6; + padding: 1em 2% 0 2%; + overflow: auto; + } + main > nav.contents > * { + padding-left: 0; + line-height: 1.4; + } + main > nav.contents a { + color: inherit; + } +} + +@media (min-width: 70em) { + body { + --field-indent: 9em; + } +} + +@media (min-width: 77em) { + /* Move marginalia to 6rem from right border */ + /* .sidebar, */ + /* .marginal, */ + /* .admonition.marginal { */ + /* margin-right: calc(6rem - 15%); */ + /* } */ + /* BUG: margin is calculated for break point width */ + /* workaround: variable + many breakpoints */ + body > * { + padding-left: 18%; + padding-right: 28%; /* fallback for webkit */ + padding-right: min(28%, 28rem); + --sidebar-margin-right: -20rem; + } + /* limit main text to ~ 50em ≙ 85…100 characters DejaVu rsp. …120 Times */ + body.with-toc > * { + padding-left: min(22%, 22rem); + padding-right: calc(78% - 50rem); /* fallback for webkit */ + padding-right: min(78% - 50rem, 28rem); + --sidebar-margin-right: 0; + } +} + +@media (min-width: 85em) { + body.with-toc > * { + --sidebar-margin-right: -9rem; + } +} + +@media (min-width: 90em) { + /* move marginalia into the margin */ + body > * { + padding-left: min(22%, 22rem); + --sidebar-margin-right: -23rem; + } + body.with-toc > * { + --sidebar-margin-right: -14rem; + } +} + +@media (min-width: 99em) { + /* move marginalia out of main text area */ + body.with-toc > * { + --sidebar-margin-right: -20rem; + } + body > *, body.with-toc > * { /* for webkit */ + padding-left: 22rem; + padding-right: 28rem; + } + .admonition.marginal, + .marginal { + width: 40%; /* make marginal figures, ... "full width" */ + } +} + +@media (min-width: 104em) { + body.with-toc > * { + --sidebar-margin-right: -23rem; + } +} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/template.txt b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/template.txt new file mode 100644 index 00000000..2591bce3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/template.txt @@ -0,0 +1,8 @@ +%(head_prefix)s +%(head)s +%(stylesheet)s +%(body_prefix)s +%(body_pre_docinfo)s +%(docinfo)s +%(body)s +%(body_suffix)s diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/tuftig.css b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/tuftig.css new file mode 100644 index 00000000..cdedfded --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/html5_polyglot/tuftig.css @@ -0,0 +1,566 @@ +/* CSS3_ style sheet for the output of Docutils HTML writers. */ +/* Rules inspired by Edward Tufte's layout design. */ +/* */ +/* :Author: Günter Milde */ +/* based on tufte.css_ by Dave Liepmann */ +/* and the tufte-latex_ package. */ +/* */ +/* :Id: $Id: tuftig.css 9503 2023-12-16 22:37:59Z milde $ */ +/* :Copyright: © 2020 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ +/* .. _CSS3: https://www.w3.org/Style/CSS/ */ +/* .. _tufte.css: https://edwardtufte.github.io/tufte-css/ */ +/* .. _tufte-latex_: https://www.ctan.org/pkg/tufte-latex */ + + +/* General Settings */ +/* ================ */ + +body { + font-family: Georgia, serif; + background-color: #fafaf6; + font-size: 1.2em; + line-height: 1.4; + margin: auto; +} +main { + counter-reset: figure table; +} +main, header, footer { + padding: 0.5em 5%; + background-color: #fefef8; + max-width: 100rem; +} + +/* Spacing */ + +/* vertical space (parskip) */ +p, ol, ul, dl, li, +h1, h2, h3, h4, h5, h6, +div.line-block, +.topic, +.footnote, .citation, +table { + margin-top: 0.5em; + margin-bottom: 0.5em; +} +dl > dd { + margin-bottom: 0.5em; +} +/* exceptions */ +p:first-child { + margin-top: 0; +} +p:last-child { + margin-bottom: 0; +} + +/* Indented Blocks */ +blockquote, +.topic { + /* background-color: Honeydew; */ + margin: 0.5em 2%; + padding-left: 1em; +} +div.line-block div.line-block, +dl.option-list, +figure > img, +pre.literal-block, pre.math, +pre.doctest-block, pre.code { + /* background-color: LightCyan; */ + margin-left: calc(2% + 1em); +} + +/* Object styling */ +/* ============== */ + +footer, header { + font-size: smaller; +} + +/* Titles and Headings */ + +h2, h3, h4, p.subtitle, p.section-subtitle, +p.topic-title, p.sidebar-title, p.sidebar-subtitle { + font-weight: normal; + font-style: italic; + text-align: left; +} +.sectnum { + font-style: normal; +} + +h1.title { + text-align: left; + margin-top: 2.4em; + margin-bottom: 2em; + font-size: 2.4em; +} +h1 + p.subtitle { + margin-top: -2em; + margin-bottom: 2em; + font-size: 2.0em; +} +section { + margin-top: 2em; +} +h2, .contents > p.topic-title { + font-size: 2.2em; +} +h2 + p.section-subtitle { + font-size: 1.6em; +} +h3 { + font-size: 1.2em; +} +h3 + p.section-subtitle { + font-size: 1.1em; +} +h4 { + font-size: 1em; +} +p.section-subtitle { + font-size: 1em; +} + +/* Dedication and Abstract */ +div.dedication { + padding: 0; + margin-left: 0; + font-style: italic; + font-size: 1.2em; +} +/* div.abstract p.topic-title, */ +div.dedication p.topic-title { + display: none; +} + +/* Attribution */ +blockquote p.attribution, +.topic p.attribution { + text-align: right; +} + +/* Table of Contents */ +nav.contents { + padding: 0; + font-style: italic; +} +ul.auto-toc > li > p { + padding-left: 1em; + text-indent: -1em; +} +nav.contents ul { + padding-left: 1em; +} + + +/* Transitions */ +hr { + border: 0; + border-top: 1px solid #ccc; + margin: 1em 10%; +} + +/* Lists */ +/* Less indent per level */ +ul, ol { + padding-left: 1.1em; +} +dd { + margin-left: 1.5em; +} +dd > dl:first-child, +dd > ul:first-child, +dd > ol:first-child { + /* lists nested in definition/description/field lists */ + clear: left; +} + +dl.field-list > dd, +dl.docinfo > dd, +dl.option-list > dd { + margin-left: 4em; +} +/* example for custom field-name width */ +dl.field-list.narrow > dd { + margin-left: 3em; +} +/* run-in: start field-body on same line after long field names */ +dl.field-list.run-in > dd p { + display: block; +} +/* italic field name */ +dl.description > dt, +dl.field-list > dt, +dl.docinfo > dt { + font-weight: normal; + font-style: italic; +} + +/* "description style" like in most dictionaries, encyclopedias etc. */ +dl.description > dt { + clear: left; + float: left; + margin: 0; + padding: 0; + padding-right: 0.5em; +} +dl.description > dd:after { + display: block; + content: ""; + clear: both; +} + +/* Citation list (style as description list) */ +.citation-list, +.footnote-list { + display: contents; +} +.citation { + padding-left: 1.5em; +} +.citation .label { + margin-left: -1.5em; +} + +/* Images and Figures */ +/* Caption to the left (if there is space) or below: */ +figure { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + margin: 0.5em 2%; + padding-left: 1em; +} +figure > img, +figure.fullwidth > img { + margin: 0 0.5em 0.5em 0; + padding: 0; +} +figcaption { + font-size: 0.8em; +} +.fullwidth > figcaption { + font-size: inherit; +} +figure.numbered > figcaption > p:before { + counter-increment: figure; + content: "Figure " counter(figure) ": "; +} + +/* Tables */ +table tr { + text-align: left; +} +/* th { vertical-align: bottom; } */ +/* "booktabs" style (no vertical lines) */ +table.booktabs { + border-top: 2px solid; + border-bottom: 2px solid; +} +table.booktabs * { + border: 0; +} +table.booktabs th { + border-bottom: thin solid; +} +table.numbered > caption:before { + counter-increment: table; + content: "Table " counter(table) ": "; +} + +/* Admonitions and System Messages */ +.admonition, .system-message { + border-style: solid; + border-color: silver; + border-width: thin; + margin: 1em 0; + padding: 0.5em; +} +.caution p.admonition-title, +.attention p.admonition-title, +.danger p.admonition-title, +.warning p.admonition-title, +div.error { + color: maroon; +} + +/* Literal and Code */ +pre.literal-block, pre.doctest-block, +pre.math, pre.code { + /* font-family: Consolas, "Liberation Mono", Menlo, monospace; */ + /* font-size: 0.9em; */ + overflow: auto; +} +/* basic highlighting: for a complete scheme, see */ +/* https://docutils.sourceforge.io/sandbox/stylesheets/ */ +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +.sans { + font-family: "Gill Sans", "Gill Sans MT", Calibri, "Lucida Sans", "Noto Sans", sans-serif; + letter-spacing: .02em; +} + +/* Hyperlink References */ +/* underline that clears descenders */ +a { + color: inherit; +} +a:link { + text-decoration: underline; + /* text-decoration-skip-ink: auto; nonstandard selector */ +} +/* undecorated links */ +.contents a:link, a.toc-backref:link, a.image-reference:link, +a[role="doc-noteref"]:link, a[role="doc-backlink"]:link, .backrefs a:link, +a.citation-reference:link, +a[href^="#system-message"] { + text-decoration: none; +} +a:link:hover { + text-decoration: underline; +} + +/* Block Alignment */ +/* Let content flow to the side of aligned images and figures */ +/* (does not work if the image/figure is a grid element). */ + +/* no floats around this elements */ +footer, header, +hr.docutils, +h1, h2, h3, .contents > p.topic-title, +.fullwidth { + clear: both; +} + +img.align-left, +svg.align-left, +video.align-left, +figure.align-left, +div.align-left, +table.align-left { + margin-left: 0; + padding-left: 0; + padding-right: 0.5em; + clear: left; + float: left; +} +figure.align-left > img { + margin-left: 0; + padding-left: 0; +} + +img.align-right, +svg.align-right, +video.align-right, +div.align-right { + padding-left: 0.5em; + clear: right; + float: right; +} +figure.align-right { + clear: right; + float: right; +} +figure.align-right > img { + justify-self: right; + padding: 0; +} +table.align-right { + margin-right: 2.5%; +} + +figure.align-center { + align-content: center; + justify-content: center; +} +figure.align-center > img { + padding-left: 0; + justify-self: center; +} + +/* Margin Elements */ +/* see below for screen size dependent rules */ +aside.sidebar, +.marginal, +.admonition.marginal, +.topic.marginal { + background-color: #efefea; + box-sizing: border-box; + margin-left: 2%; + margin-right: 0; + padding: 0.5em; + font-size: 0.8em; +} +aside.sidebar { + background-color: inherit; +} +figure.marginal > figcaption { + font-size: 1em; +} +.footnote { + font-size: smaller; + overflow: auto; +} + +/* Adaptive page layout */ + +/* no floating for very small Screens */ +/* (main text up to ca. 40 characters/line) */ +@media (min-width: 35em) { + main, header, footer { + padding: 0.5em calc(15% - 3rem); + line-height: 1.6 + } + aside.sidebar, + .marginal, + .admonition.marginal, + .topic.marginal { + max-width: 45%; + float: right; + clear: right; + } + dl.field-list > dd, + dl.docinfo > dd { + margin-left: 6em; + } + dl.option-list > dd { + margin-left: 6em; + } +} + +/* 2 column layout with wide margin */ +@media (min-width: 65em) { + /* use the same grid for main, all sections, and figures */ + main, section { + display: grid; + grid-template-columns: [content] minmax(0, 6fr) + [margin] 3fr [end]; + grid-column-gap: calc(3em + 1%); + } + main > section, section > section { + grid-column: 1 / end; + } + main, header, footer { + padding-right: 5%; /* less padding right of margin-column */ + } + section > figure { + display: contents; /* to place caption in the margin */ + } + /* Main text elements */ + main > *, section > *, + figure > img, + .footnote.align-left, /* override the placement in the margin */ + .citation.align-left { + grid-column: content; + } + .citation.align-left { + font-size: 1em; + padding-left: 1.5em; + } + .citation.align-left .label { + margin-left: -1.5em; + } + figure > img { /* indent */ + margin: 0.5em 2%; + padding-left: 1em; + } + + /* Margin Elements */ + /* Sidebar, Footnotes, Citations, Captions */ + aside.sidebar, + .citation, + .footnote, + figcaption, + /* table > caption, does not work :(*/ + .marginal, + .admonition.marginal, + .topic.marginal { + /* color: red; */ + grid-column: margin; + width: auto; + max-width: 55em; + margin: 0.5em 0; + border: none; + padding: 0; + font-size: 0.8em; + text-align: initial; /* overwrite align-* */ + background-color: inherit; + } + .admonition.marginal { + padding: 0.5em; + } + figure.marginal { + display: block; + margin: 0.5em 0; + } + .citation, + .footnote { + padding-left: 0; + } + .citation .label, + .footnote .label { + margin-left: 0; + } + + /* Fullwidth Elements */ + h1.title, p.subtitle, + dl.docinfo, + div.abstract, + div.dedication, + nav.contents, + aside.system-message, + pre, + .fullwidth, + .fullwidth img, + .fullwidth figcaption { + /* background-color: Linen; */ + grid-column: content / end; + margin-right: calc(10% - 3rem); + max-width: 55em; + } +} + +/* 3 column layout */ + +@media (min-width: 100em) { + main, header, footer { + padding-left: 30%; + } + main > nav.contents { + position: fixed; + top: 0; + left: 0; + box-sizing: border-box; + width: 25%; + height: 100vh; + margin: 0; + background-color: #fafaf6; + padding: 5.5em 2%; + overflow: auto; + } + main > nav.contents > * { + padding-left: 0; + } +} + +/* wrap URLs */ +/* a:link { */ +/* white-space: normal; */ +/* hyphens: none; */ +/* } */ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/__init__.py new file mode 100644 index 00000000..d1960a79 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/__init__.py @@ -0,0 +1,3323 @@ +# $Id: __init__.py 9581 2024-03-17 23:31:04Z milde $ +# Author: Engelbert Gruber, Günter Milde +# Maintainer: docutils-develop@lists.sourceforge.net +# Copyright: This module has been placed in the public domain. + +"""LaTeX2e document tree Writer.""" + +__docformat__ = 'reStructuredText' + +# code contributions from several people included, thanks to all. +# some named: David Abrahams, Julien Letessier, Lele Gaifax, and others. +# +# convention deactivate code by two # i.e. ##. + +from pathlib import Path +import re +import string +from urllib.request import url2pathname +import warnings +try: + import roman +except ImportError: + import docutils.utils.roman as roman + +from docutils import frontend, nodes, languages, writers, utils +from docutils.transforms import writer_aux +from docutils.utils.math import pick_math_environment, unichar2tex + +LATEX_WRITER_DIR = Path(__file__).parent + + +class Writer(writers.Writer): + + supported = ('latex', 'latex2e') + """Formats this writer supports.""" + + default_template = 'default.tex' + default_template_path = LATEX_WRITER_DIR + default_preamble = ('% PDF Standard Fonts\n' + '\\usepackage{mathptmx} % Times\n' + '\\usepackage[scaled=.90]{helvet}\n' + '\\usepackage{courier}') + table_style_values = [ # TODO: align-left, align-center, align-right, ?? + 'booktabs', 'borderless', 'colwidths-auto', + 'nolines', 'standard'] + + settings_spec = ( + 'LaTeX-Specific Options', + None, + (('Specify LaTeX documentclass. Default: "article".', + ['--documentclass'], + {'default': 'article', }), + ('Specify document options. Multiple options can be given, ' + 'separated by commas. Default: "a4paper".', + ['--documentoptions'], + {'default': 'a4paper', }), + ('Format for footnote references: one of "superscript" or ' + '"brackets". Default: "superscript".', + ['--footnote-references'], + {'choices': ['superscript', 'brackets'], 'default': 'superscript', + 'metavar': '<format>', + 'overrides': 'trim_footnote_reference_space'}), + ('Use \\cite command for citations. (future default)', + ['--use-latex-citations'], + {'default': None, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Use figure floats for citations ' + '(might get mixed with real figures). (provisional default)', + ['--figure-citations'], + {'dest': 'use_latex_citations', 'action': 'store_false', + 'validator': frontend.validate_boolean}), + ('Format for block quote attributions: one of "dash" (em-dash ' + 'prefix), "parentheses"/"parens", or "none". Default: "dash".', + ['--attribution'], + {'choices': ['dash', 'parentheses', 'parens', 'none'], + 'default': 'dash', 'metavar': '<format>'}), + ('Specify LaTeX packages/stylesheets. ' + 'A style is referenced with "\\usepackage" if extension is ' + '".sty" or omitted and with "\\input" else. ' + ' Overrides previous --stylesheet and --stylesheet-path settings.', + ['--stylesheet'], + {'default': '', 'metavar': '<file[,file,...]>', + 'overrides': 'stylesheet_path', + 'validator': frontend.validate_comma_separated_list}), + ('Comma separated list of LaTeX packages/stylesheets. ' + 'Relative paths are expanded if a matching file is found in ' + 'the --stylesheet-dirs. With --link-stylesheet, ' + 'the path is rewritten relative to the output *.tex file. ', + ['--stylesheet-path'], + {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', + 'validator': frontend.validate_comma_separated_list}), + ('Link to the stylesheet(s) in the output file. (default)', + ['--link-stylesheet'], + {'dest': 'embed_stylesheet', 'action': 'store_false'}), + ('Embed the stylesheet(s) in the output file. ' + 'Stylesheets must be accessible during processing. ', + ['--embed-stylesheet'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Comma-separated list of directories where stylesheets are found. ' + 'Used by --stylesheet-path when expanding relative path arguments. ' + 'Default: ".".', + ['--stylesheet-dirs'], + {'metavar': '<dir[,dir,...]>', + 'validator': frontend.validate_comma_separated_list, + 'default': ['.']}), + ('Customization by LaTeX code in the preamble. ' + 'Default: select PDF standard fonts (Times, Helvetica, Courier).', + ['--latex-preamble'], + {'default': default_preamble}), + ('Specify the template file. Default: "%s".' % default_template, + ['--template'], + {'default': default_template, 'metavar': '<file>'}), + ('Table of contents by LaTeX. (default)', + ['--use-latex-toc'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Table of contents by Docutils (without page numbers).', + ['--use-docutils-toc'], + {'dest': 'use_latex_toc', 'action': 'store_false', + 'validator': frontend.validate_boolean}), + ('Add parts on top of the section hierarchy.', + ['--use-part-section'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Attach author and date to the document info table. (default)', + ['--use-docutils-docinfo'], + {'dest': 'use_latex_docinfo', 'action': 'store_false', + 'validator': frontend.validate_boolean}), + ('Attach author and date to the document title.', + ['--use-latex-docinfo'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ("Typeset abstract as topic. (default)", + ['--topic-abstract'], + {'dest': 'use_latex_abstract', 'action': 'store_false', + 'validator': frontend.validate_boolean}), + ("Use LaTeX abstract environment for the document's abstract.", + ['--use-latex-abstract'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Color of any hyperlinks embedded in text. ' + 'Default: "blue" (use "false" to disable).', + ['--hyperlink-color'], {'default': 'blue'}), + ('Additional options to the "hyperref" package.', + ['--hyperref-options'], {'default': ''}), + ('Enable compound enumerators for nested enumerated lists ' + '(e.g. "1.2.a.ii").', + ['--compound-enumerators'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Disable compound enumerators for nested enumerated lists. ' + '(default)', + ['--no-compound-enumerators'], + {'action': 'store_false', 'dest': 'compound_enumerators'}), + ('Enable section ("." subsection ...) prefixes for compound ' + 'enumerators. This has no effect without --compound-enumerators.', + ['--section-prefix-for-enumerators'], + {'default': None, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Disable section prefixes for compound enumerators. (default)', + ['--no-section-prefix-for-enumerators'], + {'action': 'store_false', 'dest': 'section_prefix_for_enumerators'}), + ('Set the separator between section number and enumerator ' + 'for compound enumerated lists. Default: "-".', + ['--section-enumerator-separator'], + {'default': '-', 'metavar': '<char>'}), + ('When possible, use the specified environment for literal-blocks. ' + 'Default: "" (fall back to "alltt").', + ['--literal-block-env'], + {'default': ''}), + ('Deprecated alias for "--literal-block-env=verbatim".', + ['--use-verbatim-when-possible'], + {'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Table style. "standard" with horizontal and vertical lines, ' + '"booktabs" (LaTeX booktabs style) only horizontal lines ' + 'above and below the table and below the header, or "borderless". ' + 'Default: "standard"', + ['--table-style'], + {'default': ['standard'], + 'metavar': '<format>', + 'action': 'append', + 'validator': frontend.validate_comma_separated_list, + 'choices': table_style_values}), + ('LaTeX graphicx package option. ' + 'Possible values are "dvipdfmx", "dvips", "dvisvgm", ' + '"luatex", "pdftex", and "xetex".' + 'Default: "".', + ['--graphicx-option'], + {'default': ''}), + ('LaTeX font encoding. ' + 'Possible values are "", "T1" (default), "OT1", "LGR,T1" or ' + 'any other combination of options to the `fontenc` package. ', + ['--font-encoding'], + {'default': 'T1'}), + ('Per default the latex-writer puts the reference title into ' + 'hyperreferences. Specify "ref*" or "pageref*" to get the section ' + 'number or the page number.', + ['--reference-label'], + {'default': ''}), + ('Specify style and database(s) for bibtex, for example ' + '"--use-bibtex=unsrt,mydb1,mydb2". Provisional!', + ['--use-bibtex'], + {'default': '', + 'metavar': '<style,bibfile[,bibfile,...]>', + 'validator': frontend.validate_comma_separated_list}), + ('Use legacy functions with class value list for ' + '\\DUtitle and \\DUadmonition.', + ['--legacy-class-functions'], + {'default': False, + 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Use \\DUrole and "DUclass" wrappers for class values. ' + 'Place admonition content in an environment. (default)', + ['--new-class-functions'], + {'dest': 'legacy_class_functions', + 'action': 'store_false', + 'validator': frontend.validate_boolean}), + ('Use legacy algorithm to determine table column widths. ' + '(provisional default)', + ['--legacy-column-widths'], + {'default': None, + 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Use new algorithm to determine table column widths. ' + '(future default)', + ['--new-column-widths'], + {'dest': 'legacy_column_widths', + 'action': 'store_false', + 'validator': frontend.validate_boolean}), + # TODO: implement "latex footnotes" alternative + ('Footnotes with numbers/symbols by Docutils. (default) ' + '(The alternative, --latex-footnotes, is not implemented yet.)', + ['--docutils-footnotes'], + {'default': True, + 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ), + ) + + relative_path_settings = ('template',) + settings_defaults = {'sectnum_depth': 0} # updated by SectNum transform + config_section = 'latex2e writer' + config_section_dependencies = ('writers', 'latex writers') + + head_parts = ('head_prefix', 'requirements', 'latex_preamble', + 'stylesheet', 'fallbacks', 'pdfsetup', 'titledata') + visitor_attributes = head_parts + ('title', 'subtitle', + 'body_pre_docinfo', 'docinfo', + 'dedication', 'abstract', 'body') + + output = None + """Final translated form of `document`.""" + + def __init__(self): + writers.Writer.__init__(self) + self.translator_class = LaTeXTranslator + + def get_transforms(self): + # Override parent method to add latex-specific transforms + return super().get_transforms() + [ + # Convert specific admonitions to generic one + writer_aux.Admonitions, + # TODO: footnote collection transform + ] + + def translate(self): + visitor = self.translator_class(self.document) + self.document.walkabout(visitor) + # copy parts + for part in self.visitor_attributes: + setattr(self, part, getattr(visitor, part)) + # get template string from file + templatepath = Path(self.document.settings.template) + if not templatepath.exists(): + templatepath = self.default_template_path / templatepath.name + template = templatepath.read_text(encoding='utf-8') + # fill template + self.assemble_parts() # create dictionary of parts + self.output = string.Template(template).substitute(self.parts) + + def assemble_parts(self): + """Assemble the `self.parts` dictionary of output fragments.""" + writers.Writer.assemble_parts(self) + for part in self.visitor_attributes: + lines = getattr(self, part) + if part in self.head_parts: + if lines: + lines.append('') # to get a trailing newline + self.parts[part] = '\n'.join(lines) + else: + # body contains inline elements, so join without newline + self.parts[part] = ''.join(lines) + + +class Babel: + """Language specifics for LaTeX.""" + + # TeX (babel) language names: + # ! not all of these are supported by Docutils! + # + # based on LyX' languages file with adaptions to `BCP 47`_ + # (https://www.rfc-editor.org/rfc/bcp/bcp47.txt) and + # http://www.tug.org/TUGboat/Articles/tb29-3/tb93miklavec.pdf + # * the key without subtags is the default + # * case is ignored + # cf. https://docutils.sourceforge.io/docs/howto/i18n.html + # https://www.w3.org/International/articles/language-tags/ + # and http://www.iana.org/assignments/language-subtag-registry + language_codes = { + # code TeX/Babel-name comment + 'af': 'afrikaans', + 'ar': 'arabic', + # 'be': 'belarusian', + 'bg': 'bulgarian', + 'br': 'breton', + 'ca': 'catalan', + # 'cop': 'coptic', + 'cs': 'czech', + 'cy': 'welsh', + 'da': 'danish', + 'de': 'ngerman', # new spelling (de_1996) + 'de-1901': 'german', # old spelling + 'de-AT': 'naustrian', + 'de-AT-1901': 'austrian', + 'dsb': 'lowersorbian', + 'el': 'greek', # monotonic (el-monoton) + 'el-polyton': 'polutonikogreek', + 'en': 'english', # TeX' default language + 'en-AU': 'australian', + 'en-CA': 'canadian', + 'en-GB': 'british', + 'en-NZ': 'newzealand', + 'en-US': 'american', + 'eo': 'esperanto', + 'es': 'spanish', + 'et': 'estonian', + 'eu': 'basque', + # 'fa': 'farsi', + 'fi': 'finnish', + 'fr': 'french', + 'fr-CA': 'canadien', + 'ga': 'irish', # Irish Gaelic + # 'grc': # Ancient Greek + 'grc-ibycus': 'ibycus', # Ibycus encoding + 'gl': 'galician', + 'he': 'hebrew', + 'hr': 'croatian', + 'hsb': 'uppersorbian', + 'hu': 'magyar', + 'ia': 'interlingua', + 'id': 'bahasai', # Bahasa (Indonesian) + 'is': 'icelandic', + 'it': 'italian', + 'ja': 'japanese', + 'kk': 'kazakh', + 'la': 'latin', + 'lt': 'lithuanian', + 'lv': 'latvian', + 'mn': 'mongolian', # Mongolian, Cyrillic (mn-cyrl) + 'ms': 'bahasam', # Bahasa (Malay) + 'nb': 'norsk', # Norwegian Bokmal + 'nl': 'dutch', + 'nn': 'nynorsk', # Norwegian Nynorsk + 'no': 'norsk', # Norwegian (Bokmal) + 'pl': 'polish', + 'pt': 'portuges', + 'pt-BR': 'brazil', + 'ro': 'romanian', + 'ru': 'russian', + 'se': 'samin', # North Sami + 'sh-Cyrl': 'serbianc', # Serbo-Croatian, Cyrillic + 'sh-Latn': 'serbian', # Serbo-Croatian, Latin (cf. 'hr') + 'sk': 'slovak', + 'sl': 'slovene', + 'sq': 'albanian', + 'sr': 'serbianc', # Serbian, Cyrillic (contributed) + 'sr-Latn': 'serbian', # Serbian, Latin script + 'sv': 'swedish', + # 'th': 'thai', + 'tr': 'turkish', + 'uk': 'ukrainian', + 'vi': 'vietnam', + # zh-Latn: Chinese Pinyin + } + # normalize (downcase) keys + language_codes = {k.lower(): v for k, v in language_codes.items()} + + warn_msg = 'Language "%s" not supported by LaTeX (babel)' + + # "Active characters" are shortcuts that start a LaTeX macro and may need + # escaping for literals use. Characters that prevent literal use (e.g. + # starting accent macros like "a -> ä) will be deactivated if one of the + # defining languages is used in the document. + # Special cases: + # ~ (tilde) -- used in estonian, basque, galician, and old versions of + # spanish -- cannot be deactivated as it denotes a no-break space macro, + # " (straight quote) -- used in albanian, austrian, basque + # brazil, bulgarian, catalan, czech, danish, dutch, estonian, + # finnish, galician, german, icelandic, italian, latin, naustrian, + # ngerman, norsk, nynorsk, polish, portuges, russian, serbian, slovak, + # slovene, spanish, swedish, ukrainian, and uppersorbian -- + # is escaped as ``\textquotedbl``. + active_chars = { + # TeX/Babel-name: active characters to deactivate + # 'breton': ':;!?' # ensure whitespace + # 'esperanto': '^', + # 'estonian': '~"`', + # 'french': ':;!?' # ensure whitespace + 'galician': '.<>', # also '~"' + # 'magyar': '`', # for special hyphenation cases + 'spanish': '.<>', # old versions also '~' + # 'turkish': ':!=' # ensure whitespace + } + + def __init__(self, language_code, reporter=None): + self.reporter = reporter + self.language = self.language_name(language_code) + self.otherlanguages = {} + + def __call__(self): + """Return the babel call with correct options and settings""" + languages = sorted(self.otherlanguages.keys()) + languages.append(self.language or 'english') + self.setup = [r'\usepackage[%s]{babel}' % ','.join(languages)] + # Deactivate "active characters" + shorthands = [] + for c in ''.join(self.active_chars.get(lng, '') for lng in languages): + if c not in shorthands: + shorthands.append(c) + if shorthands: + self.setup.append(r'\AtBeginDocument{\shorthandoff{%s}}' + % ''.join(shorthands)) + # Including '~' in shorthandoff prevents its use as no-break space + if 'galician' in languages: + self.setup.append(r'\deactivatetilden % restore ~ in Galician') + if 'estonian' in languages: + self.setup.extend([r'\makeatletter', + r' \addto\extrasestonian{\bbl@deactivate{~}}', + r'\makeatother']) + if 'basque' in languages: + self.setup.extend([r'\makeatletter', + r' \addto\extrasbasque{\bbl@deactivate{~}}', + r'\makeatother']) + if (languages[-1] == 'english' + and 'french' in self.otherlanguages.keys()): + self.setup += ['% Prevent side-effects if French hyphenation ' + 'patterns are not loaded:', + r'\frenchbsetup{StandardLayout}', + r'\AtBeginDocument{\selectlanguage{%s}' + r'\noextrasfrench}' % self.language] + return '\n'.join(self.setup) + + def language_name(self, language_code): + """Return TeX language name for `language_code`""" + for tag in utils.normalize_language_tag(language_code): + try: + return self.language_codes[tag] + except KeyError: + pass + if self.reporter is not None: + self.reporter.warning(self.warn_msg % language_code) + return '' + + def get_language(self): + # Obsolete, kept for backwards compatibility with Sphinx + return self.language + + +# Building blocks for the latex preamble +# -------------------------------------- + +class SortableDict(dict): + """Dictionary with additional sorting methods + + Tip: use key starting with with '_' for sorting before small letters + and with '~' for sorting after small letters. + """ + def sortedkeys(self): + """Return sorted list of keys""" + return sorted(self.keys()) + + def sortedvalues(self): + """Return list of values sorted by keys""" + return [self[key] for key in self.sortedkeys()] + + +# PreambleCmds +# ````````````` +# A container for LaTeX code snippets that can be +# inserted into the preamble if required in the document. +# +# .. The package 'makecmds' would enable shorter definitions using the +# \providelength and \provideenvironment commands. +# However, it is pretty non-standard (texlive-latex-extra). + +class PreambleCmds: + """Building blocks for the latex preamble.""" + + +# Requirements and Setup + +PreambleCmds.color = r"""\usepackage{color}""" + +PreambleCmds.float = r"""\usepackage{float} % extended float configuration +\floatplacement{figure}{H} % place figures here definitely""" + +PreambleCmds.linking = r"""%% hyperlinks: +\ifthenelse{\isundefined{\hypersetup}}{ + \usepackage[%s]{hyperref} + \usepackage{bookmark} + \urlstyle{same} %% normal text font (alternatives: tt, rm, sf) +}{}""" + +PreambleCmds.minitoc = r"""%% local table of contents +\usepackage{minitoc}""" + +PreambleCmds.table = r"""\usepackage{longtable,ltcaption,array} +\setlength{\extrarowheight}{2pt} +\newlength{\DUtablewidth} % internal use in tables""" + +PreambleCmds.table_columnwidth = ( + r'\newcommand{\DUcolumnwidth}[1]' + r'{\dimexpr#1\DUtablewidth-2\tabcolsep\relax}') + +PreambleCmds.textcomp = r"""\usepackage{textcomp} % text symbol macros""" +# TODO? Options [force,almostfull] prevent spurious error messages, +# see de.comp.text.tex/2005-12/msg01855 + +# backwards compatibility definitions + +PreambleCmds.abstract_legacy = r""" +% abstract title +\providecommand*{\DUtitleabstract}[1]{\centerline{\textbf{#1}}}""" + +# see https://sourceforge.net/p/docutils/bugs/339/ +PreambleCmds.admonition_legacy = r""" +% admonition (specially marked topic) +\providecommand{\DUadmonition}[2][class-arg]{% + % try \DUadmonition#1{#2}: + \ifcsname DUadmonition#1\endcsname% + \csname DUadmonition#1\endcsname{#2}% + \else + \begin{center} + \fbox{\parbox{0.9\linewidth}{#2}} + \end{center} + \fi +}""" + +PreambleCmds.error_legacy = r""" +% error admonition title +\providecommand*{\DUtitleerror}[1]{\DUtitle{\color{red}#1}}""" + +PreambleCmds.title_legacy = r""" +% title for topics, admonitions, unsupported section levels, and sidebar +\providecommand*{\DUtitle}[2][class-arg]{% + % call \DUtitle#1{#2} if it exists: + \ifcsname DUtitle#1\endcsname% + \csname DUtitle#1\endcsname{#2}% + \else + \smallskip\noindent\textbf{#2}\smallskip% + \fi +}""" + +PreambleCmds.toc_list = r""" +\providecommand*{\DUCLASScontents}{% + \renewenvironment{itemize}% + {\begin{list}{}{\setlength{\partopsep}{0pt} + \setlength{\parsep}{0pt}} + }% + {\end{list}}% +}""" + +PreambleCmds.ttem = r""" +% character width in monospaced font +\newlength{\ttemwidth} +\settowidth{\ttemwidth}{\ttfamily M}""" + +## PreambleCmds.caption = r"""% configure caption layout +## \usepackage{caption} +## \captionsetup{singlelinecheck=false}% no exceptions for one-liners""" + + +# Definitions from docutils.sty:: + +def _read_block(fp): + block = [next(fp)] # first line (empty) + for line in fp: + if not line.strip(): + break + block.append(line) + return ''.join(block).rstrip() + + +with open(LATEX_WRITER_DIR/'docutils.sty', encoding='utf-8') as fp: + for line in fp: + line = line.strip('% \n') + if not line.endswith('::'): + continue + block_name = line.rstrip(':') + if not block_name: + continue + definitions = _read_block(fp) + if block_name in ('color', 'float', 'table', 'textcomp'): + definitions = definitions.strip() + # print('Block: `%s`'% block_name) + # print(definitions) + setattr(PreambleCmds, block_name, definitions) + + +# LaTeX encoding maps +# ------------------- +# :: + +class CharMaps: + """LaTeX representations for active and Unicode characters.""" + + # characters that need escaping even in `alltt` environments: + alltt = { + ord('\\'): '\\textbackslash{}', + ord('{'): '\\{', + ord('}'): '\\}', + } + # characters that normally need escaping: + special = { + ord('#'): '\\#', + ord('$'): '\\$', + ord('%'): '\\%', + ord('&'): '\\&', + ord('~'): '\\textasciitilde{}', + ord('_'): '\\_', + ord('^'): '\\textasciicircum{}', + # straight double quotes are 'active' in many languages + ord('"'): '\\textquotedbl{}', + # Square brackets are ordinary chars and cannot be escaped with '\', + # so we put them in a group '{[}'. (Alternative: ensure that all + # macros with optional arguments are terminated with {} and text + # inside any optional argument is put in a group ``[{text}]``). + # Commands with optional args inside an optional arg must be put in a + # group, e.g. ``\item[{\hyperref[label]{text}}]``. + ord('['): '{[}', + ord(']'): '{]}', + # the soft hyphen is unknown in 8-bit text + # and not properly handled by XeTeX + 0x00AD: '\\-', # SOFT HYPHEN + } + # Unicode chars that are not recognized by LaTeX's utf8 encoding + unsupported_unicode = { + # TODO: ensure white space also at the beginning of a line? + # 0x00A0: '\\leavevmode\\nobreak\\vadjust{}~' + 0x2000: '\\enskip', # EN QUAD + 0x2001: '\\quad', # EM QUAD + 0x2002: '\\enskip', # EN SPACE + 0x2003: '\\quad', # EM SPACE + 0x2008: '\\,', # PUNCTUATION SPACE + 0x200b: '\\hspace{0pt}', # ZERO WIDTH SPACE + 0x202F: '\\,', # NARROW NO-BREAK SPACE + # 0x02d8: '\\\u{ }', # BREVE + 0x2011: '\\hbox{-}', # NON-BREAKING HYPHEN + 0x212b: '\\AA', # ANGSTROM SIGN + 0x21d4: '\\ensuremath{\\Leftrightarrow}', # LEFT RIGHT DOUBLE ARROW + 0x2260: '\\ensuremath{\\neq}', # NOT EQUAL TO + 0x2261: '\\ensuremath{\\equiv}', # IDENTICAL TO + 0x2264: '\\ensuremath{\\le}', # LESS-THAN OR EQUAL TO + 0x2265: '\\ensuremath{\\ge}', # GREATER-THAN OR EQUAL TO + # Docutils footnote symbols: + 0x2660: '\\ensuremath{\\spadesuit}', + 0x2663: '\\ensuremath{\\clubsuit}', + 0xfb00: 'ff', # LATIN SMALL LIGATURE FF + 0xfb01: 'fi', # LATIN SMALL LIGATURE FI + 0xfb02: 'fl', # LATIN SMALL LIGATURE FL + 0xfb03: 'ffi', # LATIN SMALL LIGATURE FFI + 0xfb04: 'ffl', # LATIN SMALL LIGATURE FFL + } + # Unicode chars that are recognized by LaTeX's utf8 encoding + utf8_supported_unicode = { + 0x00A0: '~', # NO-BREAK SPACE + 0x00AB: '\\guillemotleft{}', # LEFT-POINTING DOUBLE ANGLE QUOTATION + 0x00bb: '\\guillemotright{}', # RIGHT-POINTING DOUBLE ANGLE QUOTATION + 0x200C: '\\textcompwordmark{}', # ZERO WIDTH NON-JOINER + 0x2013: '\\textendash{}', + 0x2014: '\\textemdash{}', + 0x2018: '\\textquoteleft{}', + 0x2019: '\\textquoteright{}', + 0x201A: '\\quotesinglbase{}', # SINGLE LOW-9 QUOTATION MARK + 0x201C: '\\textquotedblleft{}', + 0x201D: '\\textquotedblright{}', + 0x201E: '\\quotedblbase{}', # DOUBLE LOW-9 QUOTATION MARK + 0x2030: '\\textperthousand{}', # PER MILLE SIGN + 0x2031: '\\textpertenthousand{}', # PER TEN THOUSAND SIGN + 0x2039: '\\guilsinglleft{}', + 0x203A: '\\guilsinglright{}', + 0x2423: '\\textvisiblespace{}', # OPEN BOX + 0x2020: '\\dag{}', + 0x2021: '\\ddag{}', + 0x2026: '\\dots{}', + 0x2122: '\\texttrademark{}', + } + # recognized with 'utf8', if textcomp is loaded + textcomp = { + # Latin-1 Supplement + 0x00a2: '\\textcent{}', # ¢ CENT SIGN + 0x00a4: '\\textcurrency{}', # ¤ CURRENCY SYMBOL + 0x00a5: '\\textyen{}', # ¥ YEN SIGN + 0x00a6: '\\textbrokenbar{}', # ¦ BROKEN BAR + 0x00a7: '\\textsection{}', # § SECTION SIGN + 0x00a8: '\\textasciidieresis{}', # ¨ DIAERESIS + 0x00a9: '\\textcopyright{}', # © COPYRIGHT SIGN + 0x00aa: '\\textordfeminine{}', # ª FEMININE ORDINAL INDICATOR + 0x00ac: '\\textlnot{}', # ¬ NOT SIGN + 0x00ae: '\\textregistered{}', # ® REGISTERED SIGN + 0x00af: '\\textasciimacron{}', # ¯ MACRON + 0x00b0: '\\textdegree{}', # ° DEGREE SIGN + 0x00b1: '\\textpm{}', # ± PLUS-MINUS SIGN + 0x00b2: '\\texttwosuperior{}', # ² SUPERSCRIPT TWO + 0x00b3: '\\textthreesuperior{}', # ³ SUPERSCRIPT THREE + 0x00b4: '\\textasciiacute{}', # ´ ACUTE ACCENT + 0x00b5: '\\textmu{}', # µ MICRO SIGN + 0x00b6: '\\textparagraph{}', # ¶ PILCROW SIGN # != \textpilcrow + 0x00b9: '\\textonesuperior{}', # ¹ SUPERSCRIPT ONE + 0x00ba: '\\textordmasculine{}', # º MASCULINE ORDINAL INDICATOR + 0x00bc: '\\textonequarter{}', # 1/4 FRACTION + 0x00bd: '\\textonehalf{}', # 1/2 FRACTION + 0x00be: '\\textthreequarters{}', # 3/4 FRACTION + 0x00d7: '\\texttimes{}', # × MULTIPLICATION SIGN + 0x00f7: '\\textdiv{}', # ÷ DIVISION SIGN + # others + 0x0192: '\\textflorin{}', # LATIN SMALL LETTER F WITH HOOK + 0x02b9: '\\textasciiacute{}', # MODIFIER LETTER PRIME + 0x02ba: '\\textacutedbl{}', # MODIFIER LETTER DOUBLE PRIME + 0x2016: '\\textbardbl{}', # DOUBLE VERTICAL LINE + 0x2022: '\\textbullet{}', # BULLET + 0x2032: '\\textasciiacute{}', # PRIME + 0x2033: '\\textacutedbl{}', # DOUBLE PRIME + 0x2035: '\\textasciigrave{}', # REVERSED PRIME + 0x2036: '\\textgravedbl{}', # REVERSED DOUBLE PRIME + 0x203b: '\\textreferencemark{}', # REFERENCE MARK + 0x203d: '\\textinterrobang{}', # INTERROBANG + 0x2044: '\\textfractionsolidus{}', # FRACTION SLASH + 0x2045: '\\textlquill{}', # LEFT SQUARE BRACKET WITH QUILL + 0x2046: '\\textrquill{}', # RIGHT SQUARE BRACKET WITH QUILL + 0x2052: '\\textdiscount{}', # COMMERCIAL MINUS SIGN + 0x20a1: '\\textcolonmonetary{}', # COLON SIGN + 0x20a3: '\\textfrenchfranc{}', # FRENCH FRANC SIGN + 0x20a4: '\\textlira{}', # LIRA SIGN + 0x20a6: '\\textnaira{}', # NAIRA SIGN + 0x20a9: '\\textwon{}', # WON SIGN + 0x20ab: '\\textdong{}', # DONG SIGN + 0x20ac: '\\texteuro{}', # EURO SIGN + 0x20b1: '\\textpeso{}', # PESO SIGN + 0x20b2: '\\textguarani{}', # GUARANI SIGN + 0x2103: '\\textcelsius{}', # DEGREE CELSIUS + 0x2116: '\\textnumero{}', # NUMERO SIGN + 0x2117: '\\textcircledP{}', # SOUND RECORDING COPYRIGHT + 0x211e: '\\textrecipe{}', # PRESCRIPTION TAKE + 0x2120: '\\textservicemark{}', # SERVICE MARK + 0x2122: '\\texttrademark{}', # TRADE MARK SIGN + 0x2126: '\\textohm{}', # OHM SIGN + 0x2127: '\\textmho{}', # INVERTED OHM SIGN + 0x212e: '\\textestimated{}', # ESTIMATED SYMBOL + 0x2190: '\\textleftarrow{}', # LEFTWARDS ARROW + 0x2191: '\\textuparrow{}', # UPWARDS ARROW + 0x2192: '\\textrightarrow{}', # RIGHTWARDS ARROW + 0x2193: '\\textdownarrow{}', # DOWNWARDS ARROW + 0x2212: '\\textminus{}', # MINUS SIGN + 0x2217: '\\textasteriskcentered{}', # ASTERISK OPERATOR + 0x221a: '\\textsurd{}', # SQUARE ROOT + 0x2422: '\\textblank{}', # BLANK SYMBOL + 0x25e6: '\\textopenbullet{}', # WHITE BULLET + 0x25ef: '\\textbigcircle{}', # LARGE CIRCLE + 0x266a: '\\textmusicalnote{}', # EIGHTH NOTE + 0x26ad: '\\textmarried{}', # MARRIAGE SYMBOL + 0x26ae: '\\textdivorced{}', # DIVORCE SYMBOL + 0x27e8: '\\textlangle{}', # MATHEMATICAL LEFT ANGLE BRACKET + 0x27e9: '\\textrangle{}', # MATHEMATICAL RIGHT ANGLE BRACKET + } + # Unicode chars that require a feature/package to render + pifont = { + 0x2665: '\\ding{170}', # black heartsuit + 0x2666: '\\ding{169}', # black diamondsuit + 0x2713: '\\ding{51}', # check mark + 0x2717: '\\ding{55}', # check mark + } + # TODO: greek alphabet ... ? + # see also LaTeX codec + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252124 + # and unimap.py from TeXML + + +class DocumentClass: + """Details of a LaTeX document class.""" + + def __init__(self, document_class, with_part=False): + self.document_class = document_class + self._with_part = with_part + self.sections = ['section', 'subsection', 'subsubsection', + 'paragraph', 'subparagraph'] + if self.document_class in ('book', 'memoir', 'report', + 'scrbook', 'scrreprt'): + self.sections.insert(0, 'chapter') + if self._with_part: + self.sections.insert(0, 'part') + + def section(self, level): + """Return the LaTeX section name for section `level`. + + The name depends on the specific document class. + Level is 1,2,3..., as level 0 is the title. + """ + if level <= len(self.sections): + return self.sections[level-1] + # unsupported levels + return 'DUtitle' + + def latex_section_depth(self, depth): + """ + Return LaTeX equivalent of Docutils section level `depth`. + + Given the value of the ``:depth:`` option of the "contents" or + "sectnum" directive, return the corresponding value for the + LaTeX ``tocdepth`` or ``secnumdepth`` counters. + """ + depth = min(depth, len(self.sections)) # limit to supported levels + if 'chapter' in self.sections: + depth -= 1 + if self.sections[0] == 'part': + depth -= 1 + return depth + + +class Table: + """Manage a table while traversing. + + Table style might be + + :standard: horizontal and vertical lines + :booktabs: only horizontal lines (requires "booktabs" LaTeX package) + :borderless: no borders around table cells + :nolines: alias for borderless + + :colwidths-auto: column widths determined by LaTeX + """ + def __init__(self, translator, latex_type): + self._translator = translator + self._latex_type = latex_type + self.legacy_column_widths = False + + self.close() + self._colwidths = [] + self._rowspan = [] + self._in_thead = 0 + + def open(self): + self._open = True + self._col_specs = [] + self.caption = [] + self._attrs = {} + self._in_head = False # maybe context with search + + def close(self): + self._open = False + self._col_specs = None + self.caption = [] + self._attrs = {} + self.stubs = [] + self.colwidths_auto = False + + def is_open(self): + return self._open + + def set_table_style(self, node, settings): + self.legacy_column_widths = settings.legacy_column_widths + if 'align' in node: + self.set('align', node['align']) + # TODO: elif 'align' in classes/settings.table-style: + # self.set('align', ...) + borders = [cls.replace('nolines', 'borderless') + for cls in (['standard'] + + settings.table_style + + node['classes']) + if cls in ('standard', 'booktabs', 'borderless', 'nolines')] + self.borders = borders[-1] + self.colwidths_auto = (('colwidths-auto' in node['classes'] + or 'colwidths-auto' in settings.table_style) + and 'colwidths-given' not in node['classes'] + and 'width' not in node) + + def get_latex_type(self): + if self._latex_type == 'longtable' and not self.caption: + # do not advance the "table" counter (requires "ltcaption" package) + return 'longtable*' + return self._latex_type + + def set(self, attr, value): + self._attrs[attr] = value + + def get(self, attr): + if attr in self._attrs: + return self._attrs[attr] + return None + + def get_vertical_bar(self): + if self.borders == 'standard': + return '|' + return '' + + def get_opening(self, width=r'\linewidth'): + align_map = {'left': '[l]', + 'center': '[c]', + 'right': '[r]', + None: ''} + align = align_map.get(self.get('align')) + latex_type = self.get_latex_type() + if align and latex_type not in ("longtable", "longtable*"): + opening = [r'\noindent\makebox[\linewidth]%s{%%' % (align,), + r'\begin{%s}' % (latex_type,)] + else: + opening = [r'\begin{%s}%s' % (latex_type, align)] + if not self.colwidths_auto: + if self.borders == 'standard' and not self.legacy_column_widths: + opening.insert(-1, r'\setlength{\DUtablewidth}' + r'{\dimexpr%s-%i\arrayrulewidth\relax}%%' + % (width, len(self._col_specs)+1)) + else: + opening.insert(-1, r'\setlength{\DUtablewidth}{%s}%%' % width) + return '\n'.join(opening) + + def get_closing(self): + closing = [] + if self.borders == 'booktabs': + closing.append(r'\bottomrule') + # elif self.borders == 'standard': + # closing.append(r'\hline') + closing.append(r'\end{%s}' % self.get_latex_type()) + if (self.get('align') + and self.get_latex_type() not in ("longtable", "longtable*")): + closing.append('}') + return '\n'.join(closing) + + def visit_colspec(self, node): + self._col_specs.append(node) + # "stubs" list is an attribute of the tgroup element: + self.stubs.append(node.attributes.get('stub')) + + def get_colspecs(self, node): + """Return column specification for longtable. + """ + bar = self.get_vertical_bar() + self._rowspan = [0] * len(self._col_specs) + if self.colwidths_auto: + self._colwidths = [] + latex_colspecs = ['l'] * len(self._col_specs) + elif self.legacy_column_widths: + # use old algorithm for backwards compatibility + width = 80 # assumed standard line length + factor = 0.93 # do not make it full linewidth + # first see if we get too wide. + total_width = sum(node['colwidth']+1 for node in self._col_specs) + if total_width > width: + factor *= width / total_width + self._colwidths = [(factor * (node['colwidth']+1)/width) + + 0.005 for node in self._col_specs] + latex_colspecs = ['p{%.3f\\DUtablewidth}' % colwidth + for colwidth in self._colwidths] + else: + # No of characters corresponding to table width = 100% + # Characters/line with LaTeX article, A4, Times, default margins + # depends on character: M: 40, A: 50, x: 70, i: 120. + norm_length = 40 + # Allowance to prevent unpadded columns like + # === == + # ABC DE + # === == + # getting too narrow: + if 'colwidths-given' not in node.parent.parent['classes']: + allowance = 1 + else: + allowance = 0 # "widths" option specified, use exact ratio + self._colwidths = [(node['colwidth']+allowance)/norm_length + for node in self._col_specs] + total_width = sum(self._colwidths) + # Limit to 100%, force 100% if table width is specified: + if total_width > 1 or 'width' in node.parent.parent.attributes: + self._colwidths = [colwidth/total_width + for colwidth in self._colwidths] + latex_colspecs = ['p{\\DUcolumnwidth{%.3f}}' % colwidth + for colwidth in self._colwidths] + return bar + bar.join(latex_colspecs) + bar + + def get_column_width(self): + """Return columnwidth for current cell (not multicell).""" + try: + if self.legacy_column_widths: + return '%.2f\\DUtablewidth'%self._colwidths[self._cell_in_row] + return '\\DUcolumnwidth{%.2f}'%self._colwidths[self._cell_in_row] + except IndexError: + return '*' + + def get_multicolumn_width(self, start, len_): + """Return sum of columnwidths for multicell.""" + try: + multicol_width = sum(self._colwidths[start + co] + for co in range(len_)) + if self.legacy_column_widths: + return 'p{%.2f\\DUtablewidth}' % multicol_width + return 'p{\\DUcolumnwidth{%.3f}}' % multicol_width + except IndexError: + return 'l' + + def get_caption(self): + """Deprecated. Will be removed in Docutils 0.22.""" + warnings.warn('`writers.latex2e.Table.get_caption()` is obsolete' + ' and will be removed in Docutils 0.22.', + DeprecationWarning, stacklevel=2) + + if not self.caption: + return '' + caption = ''.join(self.caption) + if 1 == self._translator.thead_depth(): + return r'\caption{%s}\\' '\n' % caption + return r'\caption[]{%s (... continued)}\\' '\n' % caption + + def need_recurse(self): + if self._latex_type == 'longtable': + return 1 == self._translator.thead_depth() + return 0 + + def visit_thead(self): + self._in_thead += 1 + if self.borders == 'standard': + return ['\\hline\n'] + elif self.borders == 'booktabs': + return ['\\toprule\n'] + return [] + + def depart_thead(self): + a = [] + ## if self.borders == 'standard': + ## a.append('\\hline\n') + if self.borders == 'booktabs': + a.append('\\midrule\n') + if self._latex_type == 'longtable': + if 1 == self._translator.thead_depth(): + a.append('\\endfirsthead\n') + else: + n_c = len(self._col_specs) + a.append('\\endhead\n') + # footer on all but last page (if it fits): + twidth = sum(node['colwidth']+2 for node in self._col_specs) + if twidth > 30 or (twidth > 12 and not self.colwidths_auto): + a.append(r'\multicolumn{%d}{%s}' + % (n_c, self.get_multicolumn_width(0, n_c)) + + r'{\raggedleft\ldots continued on next page}\\' + + '\n') + a.append('\\endfoot\n\\endlastfoot\n') + # for longtable one could add firsthead, foot and lastfoot + self._in_thead -= 1 + return a + + def visit_row(self): + self._cell_in_row = 0 + + def depart_row(self): + res = [' \\\\\n'] + self._cell_in_row = None # remove cell counter + for i in range(len(self._rowspan)): + if self._rowspan[i] > 0: + self._rowspan[i] -= 1 + + if self.borders == 'standard': + rowspans = [i+1 for i in range(len(self._rowspan)) + if self._rowspan[i] <= 0] + if len(rowspans) == len(self._rowspan): + res.append('\\hline\n') + else: + cline = '' + rowspans.reverse() + # TODO merge clines + while True: + try: + c_start = rowspans.pop() + except IndexError: + break + cline += '\\cline{%d-%d}\n' % (c_start, c_start) + res.append(cline) + return res + + def set_rowspan(self, cell, value): + try: + self._rowspan[cell] = value + except IndexError: + pass + + def get_rowspan(self, cell): + try: + return self._rowspan[cell] + except IndexError: + return 0 + + def get_entry_number(self): + return self._cell_in_row + + def visit_entry(self): + self._cell_in_row += 1 + + def is_stub_column(self): + if len(self.stubs) >= self._cell_in_row: + return self.stubs[self._cell_in_row] + return False + + +class LaTeXTranslator(nodes.NodeVisitor): + """ + Generate code for 8-bit LaTeX from a Docutils document tree. + + See the docstring of docutils.writers._html_base.HTMLTranslator for + notes on and examples of safe subclassing. + """ + + # When options are given to the documentclass, latex will pass them + # to other packages, as done with babel. + # Dummy settings might be taken from document settings + + # Generate code for typesetting with 8-bit latex/pdflatex vs. + # xelatex/lualatex engine. Overwritten by the XeTeX writer + is_xetex = False + + # Config setting defaults + # ----------------------- + + # TODO: use mixins for different implementations. + # list environment for docinfo. else tabularx + ## use_optionlist_for_docinfo = False # TODO: NOT YET IN USE + + # Use compound enumerations (1.A.1.) + compound_enumerators = False + + # If using compound enumerations, include section information. + section_prefix_for_enumerators = False + + # This is the character that separates the section ("." subsection ...) + # prefix from the regular list enumerator. + section_enumerator_separator = '-' + + # Auxiliary variables + # ------------------- + + has_latex_toc = False # is there a toc in the doc? (needed by minitoc) + section_level = 0 + + # Flags to encode(): + # inside citation reference labels underscores dont need to be escaped + inside_citation_reference_label = False + verbatim = False # do not encode + insert_non_breaking_blanks = False # replace blanks by "~" + insert_newline = False # add latex newline commands + literal = False # literal text (block or inline) + alltt = False # inside `alltt` environment + + def __init__(self, document, babel_class=Babel): + super().__init__(document) + # Reporter + # ~~~~~~~~ + self.warn = self.document.reporter.warning + self.error = self.document.reporter.error + + # Settings + # ~~~~~~~~ + self.settings = settings = document.settings + # warn of deprecated settings and changing defaults: + if settings.use_latex_citations is None and not settings.use_bibtex: + settings.use_latex_citations = False + warnings.warn('The default for the setting "use_latex_citations" ' + 'will change to "True" in Docutils 1.0.', + FutureWarning, stacklevel=7) + if settings.legacy_column_widths is None: + settings.legacy_column_widths = True + warnings.warn('The default for the setting "legacy_column_widths" ' + 'will change to "False" in Docutils 1.0.)', + FutureWarning, stacklevel=7) + if settings.use_verbatim_when_possible is not None: + warnings.warn( + 'The configuration setting "use_verbatim_when_possible" ' + 'will be removed in Docutils 2.0. ' + 'Use "literal_block_env: verbatim".', + FutureWarning, stacklevel=7) + + self.latex_encoding = self.to_latex_encoding(settings.output_encoding) + self.use_latex_toc = settings.use_latex_toc + self.use_latex_docinfo = settings.use_latex_docinfo + self.use_latex_citations = settings.use_latex_citations + self.reference_label = settings.reference_label + self.hyperlink_color = settings.hyperlink_color + self.compound_enumerators = settings.compound_enumerators + self.font_encoding = getattr(settings, 'font_encoding', '') + self.section_prefix_for_enumerators = ( + settings.section_prefix_for_enumerators) + self.section_enumerator_separator = ( + settings.section_enumerator_separator.replace('_', r'\_')) + # literal blocks: + self.literal_block_env = '' + self.literal_block_options = '' + if settings.literal_block_env: + (none, + self.literal_block_env, + self.literal_block_options, + none) = re.split(r'(\w+)(.*)', settings.literal_block_env) + elif settings.use_verbatim_when_possible: + self.literal_block_env = 'verbatim' + + if settings.use_bibtex: + self.use_latex_citations = True + self.bibtex = settings.use_bibtex + # language module for Docutils-generated text + # (labels, bibliographic_fields, and author_separators) + self.language_module = languages.get_language(settings.language_code, + document.reporter) + self.babel = babel_class(settings.language_code, document.reporter) + self.author_separator = self.language_module.author_separators[0] + d_options = [settings.documentoptions] + if self.babel.language not in ('english', ''): + d_options.append(self.babel.language) + self.documentoptions = ','.join(filter(None, d_options)) + self.d_class = DocumentClass(settings.documentclass, + settings.use_part_section) + # graphic package options: + if settings.graphicx_option == '': + self.graphicx_package = r'\usepackage{graphicx}' + else: + self.graphicx_package = (r'\usepackage[%s]{graphicx}' % + settings.graphicx_option) + # footnotes: TODO: implement LaTeX footnotes + self.docutils_footnotes = settings.docutils_footnotes + + # Output collection stacks + # ~~~~~~~~~~~~~~~~~~~~~~~~ + + # Document parts + self.head_prefix = [r'\documentclass[%s]{%s}' % + (self.documentoptions, + settings.documentclass)] + self.requirements = SortableDict() # made a list in depart_document() + self.requirements['__static'] = r'\usepackage{ifthen}' + self.latex_preamble = [settings.latex_preamble] + self.fallbacks = SortableDict() # made a list in depart_document() + self.pdfsetup = [] # PDF properties (hyperref package) + self.title = [] + self.subtitle = [] + self.titledata = [] # \title, \author, \date + ## self.body_prefix = ['\\begin{document}\n'] + self.body_pre_docinfo = [] # \maketitle + self.docinfo = [] + self.dedication = [] + self.abstract = [] + self.body = [] + ## self.body_suffix = ['\\end{document}\n'] + + self.context = [] + """Heterogeneous stack. + + Used by visit_* and depart_* functions in conjunction with the tree + traversal. Make sure that the pops correspond to the pushes.""" + + # Title metadata: + self.title_labels = [] + self.subtitle_labels = [] + # (if use_latex_docinfo: collects lists of + # author/organization/contact/address lines) + self.author_stack = [] + self.date = [] + + # PDF properties: pdftitle, pdfauthor + self.pdfauthor = [] + self.pdfinfo = [] + if settings.language_code != 'en': + self.pdfinfo.append(' pdflang={%s},'%settings.language_code) + + # Stack of section counters so that we don't have to use_latex_toc. + # This will grow and shrink as processing occurs. + # Initialized for potential first-level sections. + self._section_number = [0] + + # The current stack of enumerations so that we can expand + # them into a compound enumeration. + self._enumeration_counters = [] + # The maximum number of enumeration counters we've used. + # If we go beyond this number, we need to create a new + # counter; otherwise, just reuse an old one. + self._max_enumeration_counters = 0 + + self._bibitems = [] + + # object for a table while processing. + self.table_stack = [] + self.active_table = Table(self, 'longtable') + + # Where to collect the output of visitor methods (default: body) + self.out = self.body + self.out_stack = [] # stack of output collectors + + # Process settings + # ~~~~~~~~~~~~~~~~ + # Encodings: + # Docutils' output-encoding => TeX input encoding + if self.latex_encoding not in ('ascii', 'unicode', 'utf8'): + self.requirements['_inputenc'] = (r'\usepackage[%s]{inputenc}' + % self.latex_encoding) + # TeX font encoding + if not self.is_xetex: + if self.font_encoding: + self.requirements['_fontenc'] = (r'\usepackage[%s]{fontenc}' % + self.font_encoding) + # ensure \textquotedbl is defined: + for enc in self.font_encoding.split(','): + enc = enc.strip() + if enc == 'OT1': + self.requirements['_textquotedblOT1'] = ( + r'\DeclareTextSymbol{\textquotedbl}{OT1}{`\"}') + elif enc not in ('T1', 'T2A', 'T2B', 'T2C', 'T4', 'T5'): + self.requirements['_textquotedbl'] = ( + r'\DeclareTextSymbolDefault{\textquotedbl}{T1}') + # page layout with typearea (if there are relevant document options) + if (settings.documentclass.find('scr') == -1 + and (self.documentoptions.find('DIV') != -1 + or self.documentoptions.find('BCOR') != -1)): + self.requirements['typearea'] = r'\usepackage{typearea}' + + # Stylesheets + # (the name `self.stylesheet` is singular because only one + # stylesheet was supported before Docutils 0.6). + stylesheet_list = utils.get_stylesheet_list(settings) + self.fallback_stylesheet = 'docutils' in stylesheet_list + if self.fallback_stylesheet: + stylesheet_list.remove('docutils') + if settings.legacy_class_functions: + # docutils.sty is incompatible with legacy functions + self.fallback_stylesheet = False + else: + # require a minimal version: + self.fallbacks['docutils.sty'] = ( + r'\usepackage{docutils}[2020/08/28]') + + self.stylesheet = [self.stylesheet_call(path) + for path in stylesheet_list] + + # PDF setup + if self.hyperlink_color.lower() in ('0', 'false', ''): + self.hyperref_options = '' + else: + self.hyperref_options = ('colorlinks=true,' + f'linkcolor={self.hyperlink_color},' + f'urlcolor={self.hyperlink_color}') + if settings.hyperref_options: + self.hyperref_options += ',' + settings.hyperref_options + + # LaTeX Toc + # include all supported sections in toc and PDF bookmarks + # (or use documentclass-default (as currently))? + + # Section numbering + if settings.sectnum_xform: # section numbering by Docutils + PreambleCmds.secnumdepth = r'\setcounter{secnumdepth}{0}' + else: # section numbering by LaTeX: + secnumdepth = settings.sectnum_depth + # Possible values of settings.sectnum_depth: + # None "sectnum" directive without depth arg -> LaTeX default + # 0 no "sectnum" directive -> no section numbers + # >0 value of "depth" argument -> translate to LaTeX levels: + # -1 part (0 with "article" document class) + # 0 chapter (missing in "article" document class) + # 1 section + # 2 subsection + # 3 subsubsection + # 4 paragraph + # 5 subparagraph + if secnumdepth is not None: + PreambleCmds.secnumdepth = ( + r'\setcounter{secnumdepth}{%d}' + % self.d_class.latex_section_depth(secnumdepth)) + # start with specified number: + if (hasattr(settings, 'sectnum_start') + and settings.sectnum_start != 1): + self.requirements['sectnum_start'] = ( + r'\setcounter{%s}{%d}' % (self.d_class.sections[0], + settings.sectnum_start-1)) + # TODO: currently ignored (configure in a stylesheet): + ## settings.sectnum_prefix + ## settings.sectnum_suffix + + # Auxiliary Methods + # ----------------- + + def stylesheet_call(self, path): + """Return code to reference or embed stylesheet file `path`""" + path = Path(path) + # is it a package (no extension or *.sty) or "normal" tex code: + is_package = path.suffix in ('.sty', '') + # Embed content of style file: + if self.settings.embed_stylesheet: + if is_package: + path = path.with_suffix('.sty') # ensure extension + try: + content = path.read_text(encoding='utf-8') + except OSError as err: + msg = f'Cannot embed stylesheet:\n {err}'.replace('\\\\', '/') + self.document.reporter.error(msg) + return '% ' + msg.replace('\n', '\n% ') + else: + self.settings.record_dependencies.add(path.as_posix()) + if is_package: + # allow '@' in macro names: + content = (f'\\makeatletter\n{content}\n\\makeatother') + return (f'% embedded stylesheet: {path.as_posix()}\n' + f'{content}') + # Link to style file: + if is_package: + path = path.parent / path.stem # drop extension + cmd = r'\usepackage{%s}' + else: + cmd = r'\input{%s}' + if self.settings.stylesheet_path: + # adapt path relative to output (cf. config.html#stylesheet-path) + return cmd % utils.relative_path(self.settings._destination, path) + return cmd % path.as_posix() + + def to_latex_encoding(self, docutils_encoding): + """Translate docutils encoding name into LaTeX's. + + Default method is remove "-" and "_" chars from docutils_encoding. + """ + tr = {'iso-8859-1': 'latin1', # west european + 'iso-8859-2': 'latin2', # east european + 'iso-8859-3': 'latin3', # esperanto, maltese + 'iso-8859-4': 'latin4', # north european + 'iso-8859-5': 'iso88595', # cyrillic (ISO) + 'iso-8859-9': 'latin5', # turkish + 'iso-8859-15': 'latin9', # latin9, update to latin1. + 'mac_cyrillic': 'maccyr', # cyrillic (on Mac) + 'windows-1251': 'cp1251', # cyrillic (on Windows) + 'koi8-r': 'koi8-r', # cyrillic (Russian) + 'koi8-u': 'koi8-u', # cyrillic (Ukrainian) + 'windows-1250': 'cp1250', # + 'windows-1252': 'cp1252', # + 'us-ascii': 'ascii', # ASCII (US) + # unmatched encodings + # '': 'applemac', + # '': 'ansinew', # windows 3.1 ansi + # '': 'ascii', # ASCII encoding for the range 32--127. + # '': 'cp437', # dos latin us + # '': 'cp850', # dos latin 1 + # '': 'cp852', # dos latin 2 + # '': 'decmulti', + # '': 'latin10', + # 'iso-8859-6': '' # arabic + # 'iso-8859-7': '' # greek + # 'iso-8859-8': '' # hebrew + # 'iso-8859-10': '' # latin6, more complete iso-8859-4 + } + encoding = docutils_encoding.lower() # normalize case + encoding = encoding.split(':')[0] # strip the error handler + if encoding in tr: + return tr[encoding] + # drop HYPHEN or LOW LINE from "latin_1", "utf-8" and similar + return encoding.replace('_', '').replace('-', '') + + def language_label(self, docutil_label): + return self.language_module.labels[docutil_label] + + def encode(self, text): + """Return text with 'problematic' characters escaped. + + * Escape the special printing characters ``# $ % & ~ _ ^ \\ { }``, + square brackets ``[ ]``, double quotes and (in OT1) ``< | >``. + * Translate non-supported Unicode characters. + * Separate ``-`` (and more in literal text) to prevent input ligatures. + """ + if self.verbatim: + return text + # Set up the translation table: + table = CharMaps.alltt.copy() + if not self.alltt: + table.update(CharMaps.special) + # keep the underscore in citation references + if self.inside_citation_reference_label and not self.alltt: + del table[ord('_')] + # Workarounds for OT1 font-encoding + if self.font_encoding in ['OT1', ''] and not self.is_xetex: + # * out-of-order characters in cmtt + if self.literal: + # replace underscore by underlined blank, + # because this has correct width. + table[ord('_')] = '\\underline{~}' + # the backslash doesn't work, so we use a mirrored slash. + # \reflectbox is provided by graphicx: + self.requirements['graphicx'] = self.graphicx_package + table[ord('\\')] = '\\reflectbox{/}' + # * ``< | >`` come out as different chars (except for cmtt): + else: + table[ord('|')] = '\\textbar{}' + table[ord('<')] = '\\textless{}' + table[ord('>')] = '\\textgreater{}' + if self.insert_non_breaking_blanks: + table[ord(' ')] = '~' + # tab chars may occur in included files (literal or code) + # quick-and-dirty replacement with spaces + # (for better results use `--literal-block-env=lstlisting`) + table[ord('\t')] = '~' * self.settings.tab_width + # Unicode replacements for 8-bit tex engines (not required with XeTeX) + if not self.is_xetex: + if not self.latex_encoding.startswith('utf8'): + table.update(CharMaps.unsupported_unicode) + table.update(CharMaps.utf8_supported_unicode) + table.update(CharMaps.textcomp) + table.update(CharMaps.pifont) + # Characters that require a feature/package to render + for ch in text: + cp = ord(ch) + if cp in CharMaps.textcomp and not self.fallback_stylesheet: + self.requirements['textcomp'] = PreambleCmds.textcomp + elif cp in CharMaps.pifont: + self.requirements['pifont'] = '\\usepackage{pifont}' + # preamble-definitions for unsupported Unicode characters + elif (self.latex_encoding == 'utf8' + and cp in CharMaps.unsupported_unicode): + self.requirements['_inputenc'+str(cp)] = ( + '\\DeclareUnicodeCharacter{%04X}{%s}' + % (cp, CharMaps.unsupported_unicode[cp])) + text = text.translate(table) + + # Break up input ligatures e.g. '--' to '-{}-'. + if not self.is_xetex: # Not required with xetex/luatex + separate_chars = '-' + # In monospace-font, we also separate ',,', '``' and "''" and some + # other characters which can't occur in non-literal text. + if self.literal: + separate_chars += ',`\'"<>' + for char in separate_chars * 2: + # Do it twice ("* 2") because otherwise we would replace + # '---' by '-{}--'. + text = text.replace(char + char, char + '{}' + char) + + # Literal line breaks (in address or literal blocks): + if self.insert_newline: + lines = text.split('\n') + # Add a protected space to blank lines (except the last) + # to avoid ``! LaTeX Error: There's no line here to end.`` + for i, line in enumerate(lines[:-1]): + if not line.lstrip(): + lines[i] += '~' + text = (r'\\' + '\n').join(lines) + if self.literal and not self.insert_non_breaking_blanks: + # preserve runs of spaces but allow wrapping + text = text.replace(' ', ' ~') + return text + + def attval(self, text, + whitespace=re.compile('[\n\r\t\v\f]')): + """Cleanse, encode, and return attribute value text.""" + return self.encode(whitespace.sub(' ', text)) + + # TODO: is this used anywhere? -> update (use template) or delete + ## def astext(self): + ## """Assemble document parts and return as string.""" + ## head = '\n'.join(self.head_prefix + self.stylesheet + self.head) + ## body = ''.join(self.body_prefix + self.body + self.body_suffix) + ## return head + '\n' + body + + def is_inline(self, node): + """Check whether a node represents an inline or block-level element""" + return isinstance(node.parent, nodes.TextElement) + + def append_hypertargets(self, node): + """Append hypertargets for all ids of `node`""" + # hypertarget places the anchor at the target's baseline, + # so we raise it explicitly + self.out.append('%\n'.join('\\raisebox{1em}{\\hypertarget{%s}{}}' % + id for id in node['ids'])) + + def ids_to_labels(self, node, set_anchor=True, protect=False, + newline=False): + """Return list of label definitions for all ids of `node` + + If `set_anchor` is True, an anchor is set with \\phantomsection. + If `protect` is True, the \\label cmd is made robust. + If `newline` is True, a newline is added if there are labels. + """ + prefix = '\\protect' if protect else '' + labels = [prefix + '\\label{%s}' % id for id in node['ids']] + if set_anchor and labels: + labels.insert(0, '\\phantomsection') + if newline and labels: + labels.append('\n') + return labels + + def set_align_from_classes(self, node): + """Convert ``align-*`` class arguments into alignment args.""" + # separate: + align = [cls for cls in node['classes'] if cls.startswith('align-')] + if align: + node['align'] = align[-1].replace('align-', '') + node['classes'] = [cls for cls in node['classes'] + if not cls.startswith('align-')] + + def insert_align_declaration(self, node, default=None): + align = node.get('align', default) + if align == 'left': + self.out.append('\\raggedright\n') + elif align == 'center': + self.out.append('\\centering\n') + elif align == 'right': + self.out.append('\\raggedleft\n') + + def duclass_open(self, node): + """Open a group and insert declarations for class values.""" + if not isinstance(node.parent, nodes.compound): + self.out.append('\n') + for cls in node['classes']: + if cls.startswith('language-'): + language = self.babel.language_name(cls[9:]) + if language: + self.babel.otherlanguages[language] = True + self.out.append('\\begin{selectlanguage}{%s}\n' % language) + elif (isinstance(node, nodes.table) + and cls in Writer.table_style_values + ['colwidths-given']): + pass + else: + if not self.fallback_stylesheet: + self.fallbacks['DUclass'] = PreambleCmds.duclass + self.out.append('\\begin{DUclass}{%s}\n' % cls) + + def duclass_close(self, node): + """Close a group of class declarations.""" + for cls in reversed(node['classes']): + if cls.startswith('language-'): + language = self.babel.language_name(cls[9:]) + if language: + self.out.append('\\end{selectlanguage}\n') + elif (isinstance(node, nodes.table) + and cls in Writer.table_style_values + ['colwidths-given']): + pass + else: + if not self.fallback_stylesheet: + self.fallbacks['DUclass'] = PreambleCmds.duclass + self.out.append('\\end{DUclass}\n') + + def push_output_collector(self, new_out): + self.out_stack.append(self.out) + self.out = new_out + + def pop_output_collector(self): + self.out = self.out_stack.pop() + + def term_postfix(self, node): + """ + Return LaTeX code required between term or field name and content. + + In a LaTeX "description" environment (used for definition + lists and non-docinfo field lists), a ``\\leavevmode`` + between an item's label and content ensures the correct + placement of certain block constructs. + """ + for child in node: + if not isinstance(child, (nodes.Invisible, nodes.footnote, + nodes.citation)): + break + else: + return '' + if isinstance(child, (nodes.container, nodes.compound)): + return self.term_postfix(child) + if isinstance(child, nodes.image): + return '\\leavevmode\n' # Images get an additional newline. + if not isinstance(child, (nodes.paragraph, nodes.math_block)): + return '\\leavevmode' + return '' + + # Visitor methods + # --------------- + + def visit_Text(self, node): + self.out.append(self.encode(node.astext())) + + def depart_Text(self, node): + pass + + def visit_abbreviation(self, node): + node['classes'].insert(0, 'abbreviation') + self.visit_inline(node) + + def depart_abbreviation(self, node): + self.depart_inline(node) + + def visit_acronym(self, node): + node['classes'].insert(0, 'acronym') + self.visit_inline(node) + + def depart_acronym(self, node): + self.depart_inline(node) + + def visit_address(self, node): + self.visit_docinfo_item(node, 'address') + + def depart_address(self, node): + self.depart_docinfo_item(node) + + def visit_admonition(self, node): + # strip the generic 'admonition' from the list of classes + node['classes'] = [cls for cls in node['classes'] + if cls != 'admonition'] + if self.settings.legacy_class_functions: + self.fallbacks['admonition'] = PreambleCmds.admonition_legacy + if 'error' in node['classes']: + self.fallbacks['error'] = PreambleCmds.error_legacy + self.out.append('\n\\DUadmonition[%s]{'%','.join(node['classes'])) + return + if not self.fallback_stylesheet: + self.fallbacks['admonition'] = PreambleCmds.admonition + if 'error' in node['classes'] and not self.fallback_stylesheet: + self.fallbacks['error'] = PreambleCmds.error + self.duclass_open(node) + self.out.append('\\begin{DUadmonition}') + + def depart_admonition(self, node): + if self.settings.legacy_class_functions: + self.out.append('}\n') + return + self.out.append('\\end{DUadmonition}\n') + self.duclass_close(node) + + def visit_author(self, node): + self.pdfauthor.append(self.attval(node.astext())) + self.visit_docinfo_item(node, 'author') + + def depart_author(self, node): + self.depart_docinfo_item(node) + + def visit_authors(self, node): + # not used: visit_author is called anyway for each author. + pass + + def depart_authors(self, node): + pass + + def visit_block_quote(self, node): + self.duclass_open(node) + self.out.append('\\begin{quote}') + + def depart_block_quote(self, node): + self.out.append('\\end{quote}\n') + self.duclass_close(node) + + def visit_bullet_list(self, node): + self.duclass_open(node) + self.out.append('\\begin{itemize}') + + def depart_bullet_list(self, node): + self.out.append('\\end{itemize}\n') + self.duclass_close(node) + + def visit_superscript(self, node): + self.out.append(r'\textsuperscript{') + self.visit_inline(node) + + def depart_superscript(self, node): + self.depart_inline(node) + self.out.append('}') + + def visit_subscript(self, node): + self.out.append(r'\textsubscript{') + self.visit_inline(node) + + def depart_subscript(self, node): + self.depart_inline(node) + self.out.append('}') + + def visit_caption(self, node): + self.out.append('\n\\caption{') + + def depart_caption(self, node): + self.out.append('}\n') + + def visit_title_reference(self, node): + if not self.fallback_stylesheet: + self.fallbacks['titlereference'] = PreambleCmds.titlereference + self.out.append(r'\DUroletitlereference{') + self.visit_inline(node) + + def depart_title_reference(self, node): + self.depart_inline(node) + self.out.append('}') + + def visit_citation(self, node): + if self.use_latex_citations: + self.push_output_collector([]) + else: + # self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats + self.out.append(r'\begin{figure}[b]') + self.append_hypertargets(node) + + def depart_citation(self, node): + if self.use_latex_citations: + # TODO: normalize label + label = self.out[0] + text = ''.join(self.out[1:]) + self._bibitems.append([label, text]) + self.pop_output_collector() + else: + self.out.append('\\end{figure}\n') + + def visit_citation_reference(self, node): + if self.bibtex: + self._bibitems.append([node.astext()]) + if self.use_latex_citations: + if not self.inside_citation_reference_label: + self.out.append(r'\cite{') + self.inside_citation_reference_label = True + else: + assert self.out[-1] in (' ', '\n'),\ + 'unexpected non-whitespace while in reference label' + del self.out[-1] + else: + href = '' + if 'refid' in node: + href = node['refid'] + elif 'refname' in node: + href = self.document.nameids[node['refname']] + self.out.append('\\hyperlink{%s}{[' % href) + + def depart_citation_reference(self, node): + # TODO: normalize labels + if self.use_latex_citations: + followup_citation = False + # check for a following citation separated by a space or newline + sibling = node.next_node(descend=False, siblings=True) + if (isinstance(sibling, nodes.Text) + and sibling.astext() in (' ', '\n')): + sibling2 = sibling.next_node(descend=False, siblings=True) + if isinstance(sibling2, nodes.citation_reference): + followup_citation = True + if followup_citation: + self.out.append(',') + else: + self.out.append('}') + self.inside_citation_reference_label = False + else: + self.out.append(']}') + + def visit_classifier(self, node): + self.out.append('(\\textbf{') + + def depart_classifier(self, node): + self.out.append('})') + if node.next_node(nodes.term, descend=False, siblings=True): + self.out.append('\n') + + def visit_colspec(self, node): + self.active_table.visit_colspec(node) + + def depart_colspec(self, node): + pass + + def visit_comment(self, node): + if not isinstance(node.parent, nodes.compound): + self.out.append('\n') + # Precede every line with a comment sign, wrap in newlines + self.out.append('%% %s\n' % node.astext().replace('\n', '\n% ')) + raise nodes.SkipNode + + def depart_comment(self, node): + pass + + def visit_compound(self, node): + if isinstance(node.parent, nodes.compound): + self.out.append('\n') + node['classes'].insert(0, 'compound') + self.duclass_open(node) + + def depart_compound(self, node): + self.duclass_close(node) + + def visit_contact(self, node): + self.visit_docinfo_item(node, 'contact') + + def depart_contact(self, node): + self.depart_docinfo_item(node) + + def visit_container(self, node): + self.duclass_open(node) + + def depart_container(self, node): + self.duclass_close(node) + + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright') + + def depart_copyright(self, node): + self.depart_docinfo_item(node) + + def visit_date(self, node): + self.visit_docinfo_item(node, 'date') + + def depart_date(self, node): + self.depart_docinfo_item(node) + + def visit_decoration(self, node): + # header and footer + pass + + def depart_decoration(self, node): + pass + + def visit_definition(self, node): + pass + + def depart_definition(self, node): + pass + + def visit_definition_list(self, node): + self.duclass_open(node) + self.out.append('\\begin{description}\n') + + def depart_definition_list(self, node): + self.out.append('\\end{description}\n') + self.duclass_close(node) + + def visit_definition_list_item(self, node): + pass + + def depart_definition_list_item(self, node): + if node.next_node(descend=False, siblings=True) is not None: + self.out.append('\n') # TODO: just pass? + + def visit_description(self, node): + self.out.append(' ') + + def depart_description(self, node): + pass + + def visit_docinfo(self, node): + self.push_output_collector(self.docinfo) + + def depart_docinfo(self, node): + self.pop_output_collector() + # Some itmes (e.g. author) end up at other places + if self.docinfo: + # tabularx: automatic width of columns, no page breaks allowed. + self.requirements['tabularx'] = r'\usepackage{tabularx}' + if not self.fallback_stylesheet: + self.fallbacks['_providelength'] = PreambleCmds.providelength + self.fallbacks['docinfo'] = PreambleCmds.docinfo + # + self.docinfo.insert(0, '\n% Docinfo\n' + '\\begin{center}\n' + '\\begin{tabularx}{\\DUdocinfowidth}{lX}\n') + self.docinfo.append('\\end{tabularx}\n' + '\\end{center}\n') + + def visit_docinfo_item(self, node, name): + if self.use_latex_docinfo: + if name in ('author', 'organization', 'contact', 'address'): + # We attach these to the last author. If any of them precedes + # the first author, put them in a separate "author" group + # (in lack of better semantics). + if name == 'author' or not self.author_stack: + self.author_stack.append([]) + if name == 'address': # newlines are meaningful + self.insert_newline = True + text = self.encode(node.astext()) + self.insert_newline = False + else: + text = self.attval(node.astext()) + self.author_stack[-1].append(text) + raise nodes.SkipNode + elif name == 'date': + self.date.append(self.attval(node.astext())) + raise nodes.SkipNode + self.out.append('\\textbf{%s}: &\n\t' % self.language_label(name)) + if name == 'address': + self.insert_newline = True + self.out.append('{\\raggedright\n') + self.context.append(' } \\\\\n') + else: + self.context.append(' \\\\\n') + + def depart_docinfo_item(self, node): + self.out.append(self.context.pop()) + # for address we did set insert_newline + self.insert_newline = False + + def visit_doctest_block(self, node): + self.visit_literal_block(node) + + def depart_doctest_block(self, node): + self.depart_literal_block(node) + + def visit_document(self, node): + # titled document? + if (self.use_latex_docinfo or len(node) + and isinstance(node[0], nodes.title)): + protect = (self.settings.documentclass == 'memoir') + self.title_labels += self.ids_to_labels(node, set_anchor=False, + protect=protect) + + def depart_document(self, node): + # Complete "parts" with information gained from walkabout + # * language setup + if (self.babel.otherlanguages + or self.babel.language not in ('', 'english')): + self.requirements['babel'] = self.babel() + # * conditional requirements (before style sheet) + self.requirements = self.requirements.sortedvalues() + # * coditional fallback definitions (after style sheet) + self.fallbacks = self.fallbacks.sortedvalues() + # * PDF properties + self.pdfsetup.append(PreambleCmds.linking % self.hyperref_options) + if self.pdfauthor: + authors = self.author_separator.join(self.pdfauthor) + self.pdfinfo.append(' pdfauthor={%s}' % authors) + if self.pdfinfo: + self.pdfsetup += [r'\hypersetup{'] + self.pdfinfo + ['}'] + # * title (including author(s) and date if using "latex_docinfo") + if self.title or (self.use_latex_docinfo + and (self.author_stack or self.date)): + self.make_title() # see below + # * bibliography + if self._bibitems: + self.append_bibliogaphy() # see below + # * make sure to generate a toc file if needed for local contents: + if 'minitoc' in self.requirements and not self.has_latex_toc: + self.out.append('\n\\faketableofcontents % for local ToCs\n') + + def make_title(self): + # Auxiliary function called by `self.depart_document()`. + # + # Append ``\title``, ``\author``, and ``\date`` to "titledata". + # (We need all three, even if empty, to prevent errors + # and/or automatic display of the current date by \maketitle.) + # Append ``\maketitle`` to "body_pre_docinfo" parts. + # + # \title + title_arg = [''.join(self.title)] # ensure len == 1 + if self.title: + title_arg += self.title_labels + if self.subtitle: + title_arg += [r'\\', + r'\DUdocumentsubtitle{%s}' % ''.join(self.subtitle), + ] + self.subtitle_labels + self.titledata.append(r'\title{%s}' % '%\n '.join(title_arg)) + # \author + author_arg = ['\\\\\n'.join(author_entry) + for author_entry in self.author_stack] + self.titledata.append(r'\author{%s}' % + ' \\and\n'.join(author_arg)) + # \date + self.titledata.append(r'\date{%s}' % ', '.join(self.date)) + # \maketitle + # Must be in the document body. We add it to `body_pre_docinfo` + # to allow templates to put `titledata` into the document preamble. + self.body_pre_docinfo.append('\\maketitle\n') + + def append_bibliogaphy(self): + # Add bibliography at end of document. + # TODO insertion point should be configurable. + # Auxiliary function called by `depart_document`. + if self.bibtex: + self.out.append('\n\\bibliographystyle{%s}\n' % self.bibtex[0]) + self.out.append('\\bibliography{%s}\n' % ','.join(self.bibtex[1:])) + elif self.use_latex_citations: + # TODO: insert citations at point of definition. + widest_label = '' + for bibitem in self._bibitems: + if len(widest_label) < len(bibitem[0]): + widest_label = bibitem[0] + self.out.append('\n\\begin{thebibliography}{%s}\n' % + widest_label) + for bibitem in self._bibitems: + # cite_key: underscores must not be escaped + cite_key = bibitem[0].replace(r'\_', '_') + self.out.append('\\bibitem[%s]{%s}{%s}\n' % + (bibitem[0], cite_key, bibitem[1])) + self.out.append('\\end{thebibliography}\n') + + def visit_emphasis(self, node): + self.out.append('\\emph{') + self.visit_inline(node) + + def depart_emphasis(self, node): + self.depart_inline(node) + self.out.append('}') + + # Append column delimiters and advance column counter, + # if the current cell is a multi-row continuation.""" + def insert_additional_table_colum_delimiters(self): + while self.active_table.get_rowspan( + self.active_table.get_entry_number()): + self.out.append(' & ') + self.active_table.visit_entry() # increment cell count + + def visit_entry(self, node): + # cell separation + if self.active_table.get_entry_number() == 0: + self.insert_additional_table_colum_delimiters() + else: + self.out.append(' & ') + + # multirow, multicolumn + if 'morerows' in node and 'morecols' in node: + raise NotImplementedError('Cells that span multiple rows *and* ' + 'columns currently not supported ' + 'by the LaTeX writer') + # TODO: should be possible with LaTeX, see e.g. + # http://texblog.org/2012/12/21/multi-column-and-multi-row-cells-in-latex-tables/ + # multirow in LaTeX simply will enlarge the cell over several rows + # (the following n if n is positive, the former if negative). + if 'morerows' in node: + self.requirements['multirow'] = r'\usepackage{multirow}' + mrows = node['morerows'] + 1 + self.active_table.set_rowspan( + self.active_table.get_entry_number(), mrows) + self.out.append('\\multirow{%d}{%s}{' % + (mrows, self.active_table.get_column_width())) + self.context.append('}') + elif 'morecols' in node: + # the vertical bar before column is missing if it is the first + # column. the one after always. + if self.active_table.get_entry_number() == 0: + bar1 = self.active_table.get_vertical_bar() + else: + bar1 = '' + mcols = node['morecols'] + 1 + self.out.append('\\multicolumn{%d}{%s%s%s}{' % + (mcols, + bar1, + self.active_table.get_multicolumn_width( + self.active_table.get_entry_number(), mcols), + self.active_table.get_vertical_bar())) + self.context.append('}') + else: + self.context.append('') + + # bold header/stub-column + if len(node) and (isinstance(node.parent.parent, nodes.thead) + or self.active_table.is_stub_column()): + self.out.append('\\textbf{') + self.context.append('}') + else: + self.context.append('') + + # if line ends with '{', mask line break + if (not self.active_table.colwidths_auto + and self.out[-1].endswith("{") + and node.astext()): + self.out.append("%") + + self.active_table.visit_entry() # increment cell count + + def depart_entry(self, node): + self.out.append(self.context.pop()) # header / not header + self.out.append(self.context.pop()) # multirow/column + # insert extra "&"s, if following rows are spanned from above: + self.insert_additional_table_colum_delimiters() + + def visit_row(self, node): + self.active_table.visit_row() + + def depart_row(self, node): + self.out.extend(self.active_table.depart_row()) + + def visit_enumerated_list(self, node): + # enumeration styles: + types = {'': '', + 'arabic': 'arabic', + 'loweralpha': 'alph', + 'upperalpha': 'Alph', + 'lowerroman': 'roman', + 'upperroman': 'Roman'} + # default LaTeX enumeration labels: + default_labels = [ + # (präfix, enumtype, suffix) + ('', 'arabic', '.'), # 1. + ('(', 'alph', ')'), # (a) + ('', 'roman', '.'), # i. + ('', 'Alph', '.')] # A. + + prefix = '' + if self.compound_enumerators: + if (self.section_prefix_for_enumerators and self.section_level + and not self._enumeration_counters): + prefix = '.'.join(str(n) for n in + self._section_number[:self.section_level] + ) + self.section_enumerator_separator + if self._enumeration_counters: + prefix += self._enumeration_counters[-1] + prefix += node.get('prefix', '') + enumtype = types[node.get('enumtype', 'arabic')] + suffix = node.get('suffix', '.') + + enum_level = len(self._enumeration_counters)+1 + counter_name = 'enum' + roman.toRoman(enum_level).lower() + label = r'%s\%s{%s}%s' % (prefix, enumtype, counter_name, suffix) + self._enumeration_counters.append(label) + + self.duclass_open(node) + if enum_level <= 4: + self.out.append('\\begin{enumerate}') + if (prefix, enumtype, suffix) != default_labels[enum_level-1]: + self.out.append('\n\\renewcommand{\\label%s}{%s}' % + (counter_name, label)) + else: + self.fallbacks[counter_name] = '\\newcounter{%s}' % counter_name + self.out.append('\\begin{list}') + self.out.append('{%s}' % label) + self.out.append('{\\usecounter{%s}}' % counter_name) + if 'start' in node: + self.out.append('\n\\setcounter{%s}{%d}' % + (counter_name, node['start']-1)) + + def depart_enumerated_list(self, node): + if len(self._enumeration_counters) <= 4: + self.out.append('\\end{enumerate}\n') + else: + self.out.append('\\end{list}\n') + self.duclass_close(node) + self._enumeration_counters.pop() + + def visit_field(self, node): + # output is done in field_body, field_name + pass + + def depart_field(self, node): + pass + + def visit_field_body(self, node): + if not isinstance(node.parent.parent, nodes.docinfo): + self.out.append(self.term_postfix(node)) + + def depart_field_body(self, node): + if self.out is self.docinfo: + self.out.append(r'\\'+'\n') + + def visit_field_list(self, node): + self.duclass_open(node) + if self.out is not self.docinfo: + if not self.fallback_stylesheet: + self.fallbacks['fieldlist'] = PreambleCmds.fieldlist + self.out.append('\\begin{DUfieldlist}') + + def depart_field_list(self, node): + if self.out is not self.docinfo: + self.out.append('\\end{DUfieldlist}\n') + self.duclass_close(node) + + def visit_field_name(self, node): + if self.out is self.docinfo: + self.out.append('\\textbf{') + else: + # Commands with optional args inside an optional arg must be put + # in a group, e.g. ``\item[{\hyperref[label]{text}}]``. + self.out.append('\n\\item[{') + + def depart_field_name(self, node): + if self.out is self.docinfo: + self.out.append('}: &') + else: + self.out.append(':}]') + + def visit_figure(self, node): + self.requirements['float'] = PreambleCmds.float + self.duclass_open(node) + # The 'align' attribute sets the "outer alignment", + # for "inner alignment" use LaTeX default alignment (similar to HTML) + alignment = node.attributes.get('align', 'center') + if alignment != 'center': + # The LaTeX "figure" environment always uses the full linewidth, + # so "outer alignment" is ignored. Just write a comment. + # TODO: use the wrapfigure environment? + self.out.append('\\begin{figure} %% align = "%s"\n' % alignment) + else: + self.out.append('\\begin{figure}\n') + self.out += self.ids_to_labels(node, newline=True) + + def depart_figure(self, node): + self.out.append('\\end{figure}\n') + self.duclass_close(node) + + def visit_footer(self, node): + self.push_output_collector([]) + self.out.append(r'\newcommand{\DUfooter}{') + + def depart_footer(self, node): + self.out.append('}') + self.requirements['~footer'] = ''.join(self.out) + self.pop_output_collector() + + def visit_footnote(self, node): + try: + backref = node['backrefs'][0] + except IndexError: + backref = node['ids'][0] # no backref, use self-ref instead + if self.docutils_footnotes: + if not self.fallback_stylesheet: + self.fallbacks['footnotes'] = PreambleCmds.footnotes + num = node[0].astext() + if self.settings.footnote_references == 'brackets': + num = '[%s]' % num + self.out.append('%%\n\\DUfootnotetext{%s}{%s}{%s}{' % + (node['ids'][0], backref, self.encode(num))) + if node['ids'] == node['names']: + self.out += self.ids_to_labels(node) + # prevent spurious whitespace if footnote starts with paragraph: + if len(node) > 1 and isinstance(node[1], nodes.paragraph): + self.out.append('%') + # TODO: "real" LaTeX \footnote{}s (see visit_footnotes_reference()) + + def depart_footnote(self, node): + self.out.append('}\n') + + def visit_footnote_reference(self, node): + href = '' + if 'refid' in node: + href = node['refid'] + elif 'refname' in node: + href = self.document.nameids[node['refname']] + # if not self.docutils_footnotes: + # # TODO: insert footnote content at (or near) this place + # # see also docs/dev/todo.txt + # try: + # referenced_node = self.document.ids[node['refid']] + # except (AttributeError, KeyError): + # self.document.reporter.error( + # 'unresolved footnote-reference %s' % node) + # print('footnote-ref to %s' % referenced_node) + format = self.settings.footnote_references + if format == 'brackets': + self.append_hypertargets(node) + self.out.append('\\hyperlink{%s}{[' % href) + self.context.append(']}') + else: + if not self.fallback_stylesheet: + self.fallbacks['footnotes'] = PreambleCmds.footnotes + self.out.append(r'\DUfootnotemark{%s}{%s}{' % + (node['ids'][0], href)) + self.context.append('}') + + def depart_footnote_reference(self, node): + self.out.append(self.context.pop()) + + # footnote/citation label + def label_delim(self, node, bracket, superscript): + if isinstance(node.parent, nodes.footnote): + raise nodes.SkipNode + else: + assert isinstance(node.parent, nodes.citation) + if not self.use_latex_citations: + self.out.append(bracket) + + def visit_label(self, node): + """footnote or citation label: in brackets or as superscript""" + self.label_delim(node, '[', '\\textsuperscript{') + + def depart_label(self, node): + self.label_delim(node, ']', '}') + + # elements generated by the framework e.g. section numbers. + def visit_generated(self, node): + pass + + def depart_generated(self, node): + pass + + def visit_header(self, node): + self.push_output_collector([]) + self.out.append(r'\newcommand{\DUheader}{') + + def depart_header(self, node): + self.out.append('}') + self.requirements['~header'] = ''.join(self.out) + self.pop_output_collector() + + def to_latex_length(self, length_str, pxunit=None): + """Convert `length_str` with rst length to LaTeX length + """ + if pxunit is not None: + warnings.warn( + 'The optional argument `pxunit` ' + 'of LaTeXTranslator.to_latex_length() is ignored ' + 'and will be removed in Docutils 0.21 or later', + DeprecationWarning, stacklevel=2) + match = re.match(r'(\d*\.?\d*)\s*(\S*)', length_str) + if not match: + return length_str + value, unit = match.groups()[:2] + # no unit or "DTP" points (called 'bp' in TeX): + if unit in ('', 'pt'): + length_str = '%sbp' % value + # percentage: relate to current line width + elif unit == '%': + length_str = '%.3f\\linewidth' % (float(value)/100.0) + elif self.is_xetex and unit == 'px': + # XeTeX does not know the length unit px. + # Use \pdfpxdimen, the macro to set the value of 1 px in pdftex. + # This way, configuring works the same for pdftex and xetex. + if not self.fallback_stylesheet: + self.fallbacks['_providelength'] = PreambleCmds.providelength + self.fallbacks['px'] = '\n\\DUprovidelength{\\pdfpxdimen}{1bp}\n' + length_str = r'%s\pdfpxdimen' % value + return length_str + + def visit_image(self, node): + self.requirements['graphicx'] = self.graphicx_package + attrs = node.attributes + # Convert image URI to a local file path + imagepath = url2pathname(attrs['uri']).replace('\\', '/') + # alignment defaults: + if 'align' not in attrs: + # Set default align of image in a figure to 'center' + if isinstance(node.parent, nodes.figure): + attrs['align'] = 'center' + self.set_align_from_classes(node) + # pre- and postfix (prefix inserted in reverse order) + pre = [] + post = [] + include_graphics_options = [] + align_codes = { + # inline images: by default latex aligns the bottom. + 'bottom': ('', ''), + 'middle': (r'\raisebox{-0.5\height}{', '}'), + 'top': (r'\raisebox{-\height}{', '}'), + # block level images: + 'center': (r'\noindent\makebox[\linewidth][c]{', '}'), + 'left': (r'\noindent{', r'\hfill}'), + 'right': (r'\noindent{\hfill', '}'), + } + if 'align' in attrs: + # TODO: warn or ignore non-applicable alignment settings? + try: + align_code = align_codes[attrs['align']] + pre.append(align_code[0]) + post.append(align_code[1]) + except KeyError: + pass # TODO: warn? + if 'height' in attrs: + include_graphics_options.append( + 'height=%s' % self.to_latex_length(attrs['height'])) + if 'scale' in attrs: + include_graphics_options.append( + 'scale=%f' % (attrs['scale'] / 100.0)) + if 'width' in attrs: + include_graphics_options.append( + 'width=%s' % self.to_latex_length(attrs['width'])) + if not (self.is_inline(node) + or isinstance(node.parent, (nodes.figure, nodes.compound))): + pre.append('\n') + if not (self.is_inline(node) + or isinstance(node.parent, nodes.figure)): + post.append('\n') + pre.reverse() + self.out.extend(pre) + options = '' + if include_graphics_options: + options = '[%s]' % (','.join(include_graphics_options)) + self.out.append('\\includegraphics%s{%s}' % (options, imagepath)) + self.out.extend(post) + + def depart_image(self, node): + self.out += self.ids_to_labels(node, newline=True) + + def visit_inline(self, node): # <span>, i.e. custom roles + for cls in node['classes']: + if cls.startswith('language-'): + language = self.babel.language_name(cls[9:]) + if language: + self.babel.otherlanguages[language] = True + self.out.append(r'\foreignlanguage{%s}{' % language) + else: + if not self.fallback_stylesheet: + self.fallbacks['inline'] = PreambleCmds.inline + self.out.append(r'\DUrole{%s}{' % cls) + + def depart_inline(self, node): + self.out.append('}' * len(node['classes'])) + + def visit_legend(self, node): + if not self.fallback_stylesheet: + self.fallbacks['legend'] = PreambleCmds.legend + self.out.append('\\begin{DUlegend}') + + def depart_legend(self, node): + self.out.append('\\end{DUlegend}\n') + + def visit_line(self, node): + self.out.append(r'\item[] ') + + def depart_line(self, node): + self.out.append('\n') + + def visit_line_block(self, node): + if not self.fallback_stylesheet: + self.fallbacks['_providelength'] = PreambleCmds.providelength + self.fallbacks['lineblock'] = PreambleCmds.lineblock + self.set_align_from_classes(node) + if isinstance(node.parent, nodes.line_block): + self.out.append('\\item[]\n' + '\\begin{DUlineblock}{\\DUlineblockindent}\n') + # nested line-blocks cannot be given class arguments + else: + self.duclass_open(node) + self.out.append('\\begin{DUlineblock}{0em}\n') + self.insert_align_declaration(node) + + def depart_line_block(self, node): + self.out.append('\\end{DUlineblock}\n') + self.duclass_close(node) + + def visit_list_item(self, node): + self.out.append('\n\\item ') + + def depart_list_item(self, node): + pass + + def visit_literal(self, node): + self.literal = True + if ('code' in node['classes'] + and self.settings.syntax_highlight != 'none'): + self.requirements['color'] = PreambleCmds.color + if not self.fallback_stylesheet: + self.fallbacks['code'] = PreambleCmds.highlight_rules + self.out.append('\\texttt{') + self.visit_inline(node) + + def depart_literal(self, node): + self.literal = False + self.depart_inline(node) + self.out.append('}') + + # Literal blocks are used for '::'-prefixed literal-indented + # blocks of text, where the inline markup is not recognized, + # but are also the product of the "parsed-literal" directive, + # where the markup is respected. + # + # In both cases, we want to use a typewriter/monospaced typeface. + # For "real" literal-blocks, we can use \verbatim, while for all + # the others we must use \ttfamily and \raggedright. + # + # We can distinguish between the two kinds by the number of + # siblings that compose this node: if it is composed by a + # single element, it's either + # * a real one, + # * a parsed-literal that does not contain any markup, or + # * a parsed-literal containing just one markup construct. + def is_plaintext(self, node): + """Check whether a node can be typeset verbatim""" + return (len(node) == 1) and isinstance(node[0], nodes.Text) + + def visit_literal_block(self, node): + """Render a literal block. + + Corresponding rST elements: literal block, parsed-literal, code. + """ + packages = {'lstlisting': r'\usepackage{listings}' '\n' + r'\lstset{xleftmargin=\leftmargin}', + 'listing': r'\usepackage{moreverb}', + 'Verbatim': r'\usepackage{fancyvrb}', + 'verbatimtab': r'\usepackage{moreverb}'} + + literal_env = self.literal_block_env + + # Check, if it is possible to use a literal-block environment + _plaintext = self.is_plaintext(node) + _in_table = self.active_table.is_open() + # TODO: fails if normal text precedes the literal block. + # Check parent node instead? + _autowidth_table = _in_table and self.active_table.colwidths_auto + _no_env_nodes = (nodes.footnote, nodes.sidebar) + if self.settings.legacy_class_functions: + _no_env_nodes += (nodes.admonition, nodes.system_message) + _use_env = _plaintext and not isinstance(node.parent, _no_env_nodes) + _use_listings = (literal_env == 'lstlisting') and _use_env + + # Labels and classes: + self.duclass_open(node) + self.out += self.ids_to_labels(node, newline=True) + # Highlight code? + if (not _plaintext + and 'code' in node['classes'] + and self.settings.syntax_highlight != 'none'): + self.requirements['color'] = PreambleCmds.color + if not self.fallback_stylesheet: + self.fallbacks['code'] = PreambleCmds.highlight_rules + # Wrap? + if _in_table and _use_env and not _autowidth_table: + # Wrap in minipage to prevent extra vertical space + # with alltt and verbatim-like environments: + self.fallbacks['ttem'] = PreambleCmds.ttem + self.out.append( + '\\begin{minipage}{%d\\ttemwidth}\n' % + (max(len(line) for line in node.astext().split('\n')))) + self.context.append('\n\\end{minipage}\n') + elif not _in_table and not _use_listings: + # Wrap in quote to set off vertically and indent + self.out.append('\\begin{quote}\n') + self.context.append('\n\\end{quote}\n') + else: + self.context.append('\n') + + # Use verbatim-like environment, if defined and possible + # (in an auto-width table, only listings works): + if literal_env and _use_env and (not _autowidth_table + or _use_listings): + try: + self.requirements['literal_block'] = packages[literal_env] + except KeyError: + pass + self.verbatim = True + if _in_table and _use_listings: + self.out.append('\\lstset{xleftmargin=0pt}\n') + self.out.append('\\begin{%s}%s\n' % + (literal_env, self.literal_block_options)) + self.context.append('\n\\end{%s}' % literal_env) + elif _use_env and not _autowidth_table: + self.alltt = True + self.requirements['alltt'] = r'\usepackage{alltt}' + self.out.append('\\begin{alltt}\n') + self.context.append('\n\\end{alltt}') + else: + self.literal = True + self.insert_newline = True + self.insert_non_breaking_blanks = True + # \raggedright ensures leading blanks are respected but + # leads to additional leading vspace if the first line + # of the block is overfull :-( + self.out.append('\\ttfamily\\raggedright\n') + self.context.append('') + + def depart_literal_block(self, node): + self.insert_non_breaking_blanks = False + self.insert_newline = False + self.literal = False + self.verbatim = False + self.alltt = False + self.out.append(self.context.pop()) + self.out.append(self.context.pop()) + self.duclass_close(node) + + def visit_meta(self, node): + name = node.attributes.get('name') + content = node.attributes.get('content') + if not name or not content: + return + if name in ('author', 'creator', 'keywords', 'subject', 'title'): + # fields with dedicated hyperref options: + self.pdfinfo.append(' pdf%s={%s},'%(name, content)) + elif name == 'producer': + self.pdfinfo.append(' addtopdfproducer={%s},'%content) + else: + # generic interface (case sensitive!) + # TODO: filter irrelevant nodes ("http-equiv", ...)? + self.pdfinfo.append(' pdfinfo={%s={%s}},'%(name, content)) + + def depart_meta(self, node): + pass + + def visit_math(self, node, math_env='$'): + """math role""" + self.visit_inline(node) + self.requirements['amsmath'] = r'\usepackage{amsmath}' + math_code = node.astext().translate(unichar2tex.uni2tex_table) + if math_env == '$': + if self.alltt: + wrapper = ['\\(', '\\)'] + else: + wrapper = ['$', '$'] + else: + labels = self.ids_to_labels(node, set_anchor=False, newline=True) + wrapper = ['%%\n\\begin{%s}\n' % math_env, + '\n', + ''.join(labels), + '\\end{%s}' % math_env] + wrapper.insert(1, math_code) + self.out.extend(wrapper) + self.depart_inline(node) + # Content already processed: + raise nodes.SkipNode + + def depart_math(self, node): + pass # never reached + + def visit_math_block(self, node): + math_env = pick_math_environment(node.astext()) + self.visit_math(node, math_env=math_env) + + def depart_math_block(self, node): + pass # never reached + + def visit_option(self, node): + if self.context[-1]: + # this is not the first option + self.out.append(', ') + + def depart_option(self, node): + # flag that the first option is done. + self.context[-1] += 1 + + def visit_option_argument(self, node): + """Append the delimiter between an option and its argument to body.""" + self.out.append(node.get('delimiter', ' ')) + + def depart_option_argument(self, node): + pass + + def visit_option_group(self, node): + self.out.append('\\item[') + # flag for first option + self.context.append(0) + + def depart_option_group(self, node): + self.context.pop() # the flag + self.out.append('] ') + + def visit_option_list(self, node): + if not self.fallback_stylesheet: + self.fallbacks['_providelength'] = PreambleCmds.providelength + self.fallbacks['optionlist'] = PreambleCmds.optionlist + self.duclass_open(node) + self.out.append('\\begin{DUoptionlist}\n') + + def depart_option_list(self, node): + self.out.append('\\end{DUoptionlist}\n') + self.duclass_close(node) + + def visit_option_list_item(self, node): + pass + + def depart_option_list_item(self, node): + pass + + def visit_option_string(self, node): + ## self.out.append(self.starttag(node, 'span', '', CLASS='option')) + pass + + def depart_option_string(self, node): + ## self.out.append('</span>') + pass + + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization') + + def depart_organization(self, node): + self.depart_docinfo_item(node) + + def visit_paragraph(self, node): + # insert blank line, unless + # * the paragraph is first in a list item, compound, or container + # * follows a non-paragraph node in a compound, + # * is in a table with auto-width columns + index = node.parent.index(node) + if index == 0 and isinstance(node.parent, + (nodes.list_item, nodes.description, + nodes.compound, nodes.container)): + pass + elif (index > 0 + and isinstance(node.parent, nodes.compound) + and not isinstance(node.parent[index - 1], + (nodes.paragraph, nodes.compound))): + pass + elif self.active_table.colwidths_auto: + if index == 1: # second paragraph + self.warn('LaTeX merges paragraphs in tables ' + 'with auto-sized columns!', base_node=node) + if index > 0: + self.out.append('\n') + else: + self.out.append('\n') + self.out += self.ids_to_labels(node, newline=True) + self.visit_inline(node) + + def depart_paragraph(self, node): + self.depart_inline(node) + if not self.active_table.colwidths_auto: + self.out.append('\n') + + def visit_problematic(self, node): + self.requirements['color'] = PreambleCmds.color + self.out.append('%\n') + self.append_hypertargets(node) + self.out.append(r'\hyperlink{%s}{\textbf{\color{red}' % node['refid']) + + def depart_problematic(self, node): + self.out.append('}}') + + def visit_raw(self, node): + if 'latex' not in node.get('format', '').split(): + raise nodes.SkipNode + if not (self.is_inline(node) + or isinstance(node.parent, nodes.compound)): + self.out.append('\n') + self.visit_inline(node) + # append "as-is" skipping any LaTeX-encoding + self.verbatim = True + + def depart_raw(self, node): + self.verbatim = False + self.depart_inline(node) + if not self.is_inline(node): + self.out.append('\n') + + def has_unbalanced_braces(self, string): + """Test whether there are unmatched '{' or '}' characters.""" + level = 0 + for ch in string: + if ch == '{': + level += 1 + if ch == '}': + level -= 1 + if level < 0: + return True + return level != 0 + + def visit_reference(self, node): + # We need to escape #, \, and % if we use the URL in a command. + special_chars = {ord('#'): '\\#', + ord('%'): '\\%', + ord('\\'): '\\\\', + } + # external reference (URL) + if 'refuri' in node: + href = str(node['refuri']).translate(special_chars) + # problematic chars double caret and unbalanced braces: + if href.find('^^') != -1 or self.has_unbalanced_braces(href): + self.error( + f'External link "{href}" not supported by LaTeX.\n' + ' (Must not contain "^^" or unbalanced braces.)') + if node['refuri'] == node.astext(): + self.out.append(r'\url{%s}' % href) + raise nodes.SkipNode + self.out.append(r'\href{%s}{' % href) + return + # internal reference + if 'refid' in node: + href = node['refid'] + elif 'refname' in node: + href = self.document.nameids[node['refname']] + else: + raise AssertionError('Unknown reference.') + if not self.is_inline(node): + self.out.append('\n') + self.out.append('\\hyperref[%s]{' % href) + if self.reference_label: + # TODO: don't use \hyperref if self.reference_label is True + self.out.append('\\%s{%s}}' % + (self.reference_label, href.replace('#', ''))) + raise nodes.SkipNode + + def depart_reference(self, node): + self.out.append('}') + if not self.is_inline(node): + self.out.append('\n') + + def visit_revision(self, node): + self.visit_docinfo_item(node, 'revision') + + def depart_revision(self, node): + self.depart_docinfo_item(node) + + def visit_rubric(self, node): + if not self.fallback_stylesheet: + self.fallbacks['rubric'] = PreambleCmds.rubric + # class wrapper would interfere with ``\section*"`` type commands + # (spacing/indent of first paragraph) + self.out.append('\n\\DUrubric{') + + def depart_rubric(self, node): + self.out.append('}\n') + + def visit_section(self, node): + self.section_level += 1 + # Initialize counter for potential subsections: + self._section_number.append(0) + # Counter for this section's level (initialized by parent section): + self._section_number[self.section_level - 1] += 1 + + def depart_section(self, node): + # Remove counter for potential subsections: + self._section_number.pop() + self.section_level -= 1 + + def visit_sidebar(self, node): + self.duclass_open(node) + self.requirements['color'] = PreambleCmds.color + if not self.fallback_stylesheet: + self.fallbacks['sidebar'] = PreambleCmds.sidebar + self.out.append('\\DUsidebar{') + + def depart_sidebar(self, node): + self.out.append('}\n') + self.duclass_close(node) + + attribution_formats = {'dash': ('—', ''), # EM DASH + 'parentheses': ('(', ')'), + 'parens': ('(', ')'), + 'none': ('', '')} + + def visit_attribution(self, node): + prefix, suffix = self.attribution_formats[self.settings.attribution] + self.out.append('\\nopagebreak\n\n\\raggedleft ') + self.out.append(prefix) + self.context.append(suffix) + + def depart_attribution(self, node): + self.out.append(self.context.pop() + '\n') + + def visit_status(self, node): + self.visit_docinfo_item(node, 'status') + + def depart_status(self, node): + self.depart_docinfo_item(node) + + def visit_strong(self, node): + self.out.append('\\textbf{') + self.visit_inline(node) + + def depart_strong(self, node): + self.depart_inline(node) + self.out.append('}') + + def visit_substitution_definition(self, node): + raise nodes.SkipNode + + def visit_substitution_reference(self, node): + self.unimplemented_visit(node) + + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.document): + self.push_output_collector(self.subtitle) + if not self.fallback_stylesheet: + self.fallbacks['documentsubtitle'] = PreambleCmds.documentsubtitle # noqa:E501 + protect = (self.settings.documentclass == 'memoir') + self.subtitle_labels += self.ids_to_labels(node, set_anchor=False, + protect=protect) + # section subtitle: "starred" (no number, not in ToC) + elif isinstance(node.parent, nodes.section): + self.out.append(r'\%s*{' % + self.d_class.section(self.section_level + 1)) + else: + if not self.fallback_stylesheet: + self.fallbacks['subtitle'] = PreambleCmds.subtitle + self.out.append('\n\\DUsubtitle{') + + def depart_subtitle(self, node): + if isinstance(node.parent, nodes.document): + self.pop_output_collector() + else: + self.out.append('}\n') + + def visit_system_message(self, node): + self.requirements['color'] = PreambleCmds.color + if not self.fallback_stylesheet: + self.fallbacks['title'] = PreambleCmds.title + if self.settings.legacy_class_functions: + self.fallbacks['title'] = PreambleCmds.title_legacy + node['classes'] = ['system-message'] + self.visit_admonition(node) + if self.settings.legacy_class_functions: + self.out.append('\n\\DUtitle[system-message]{system-message\n') + else: + self.out.append('\n\\DUtitle{system-message\n') + self.append_hypertargets(node) + try: + line = ', line~%s' % node['line'] + except KeyError: + line = '' + self.out.append('}\n\n{\\color{red}%s/%s} in \\texttt{%s}%s\n' % + (node['type'], node['level'], + self.encode(node['source']), line)) + if len(node['backrefs']) == 1: + self.out.append('\n\\hyperlink{%s}{' % node['backrefs'][0]) + self.context.append('}') + else: + backrefs = ['\\hyperlink{%s}{%d}' % (href, i+1) + for (i, href) in enumerate(node['backrefs'])] + self.context.append('backrefs: ' + ' '.join(backrefs)) + + def depart_system_message(self, node): + self.out.append(self.context.pop()) + self.depart_admonition(node) + + def visit_table(self, node): + self.duclass_open(node) + self.requirements['table'] = PreambleCmds.table + if not self.settings.legacy_column_widths: + self.requirements['table1'] = PreambleCmds.table_columnwidth + if self.active_table.is_open(): + self.table_stack.append(self.active_table) + # nesting longtable does not work (e.g. 2007-04-18) + self.active_table = Table(self, 'tabular') + # A longtable moves before \paragraph and \subparagraph + # section titles if it immediately follows them: + if (self.active_table._latex_type == 'longtable' + and isinstance(node.parent, nodes.section) + and node.parent.index(node) == 1 + and self.d_class.section( + self.section_level).find('paragraph') != -1): + self.out.append('\\leavevmode') + self.active_table.open() + self.active_table.set_table_style(node, self.settings) + if self.active_table.borders == 'booktabs': + self.requirements['booktabs'] = r'\usepackage{booktabs}' + self.push_output_collector([]) + + def depart_table(self, node): + # wrap content in the right environment: + content = self.out + self.pop_output_collector() + try: + width = self.to_latex_length(node['width']) + except KeyError: + width = r'\linewidth' + # Insert hyperlabel and anchor before the table + # if it has no caption/title. + # See visit_thead() for tables with caption. + if not self.active_table.caption: + self.out.extend(self.ids_to_labels( + node, set_anchor=len(self.table_stack) != 1, + newline=True)) + # TODO: Don't use a longtable or add \noindent before + # the next paragraph, when in a "compound paragraph". + # Start a new line or a new paragraph? + # if (isinstance(node.parent, nodes.compound) + # and self._latex_type != 'longtable')? + self.out.append(self.active_table.get_opening(width)) + self.out += content + self.out.append(self.active_table.get_closing() + '\n') + self.active_table.close() + if len(self.table_stack) > 0: + self.active_table = self.table_stack.pop() + self.duclass_close(node) + + def visit_target(self, node): + # Skip indirect targets: + if ('refuri' in node # external hyperlink + or 'refid' in node # resolved internal link + or 'refname' in node): # unresolved internal link + ## self.out.append('%% %s\n' % node) # for debugging + return + self.out.append('%\n') + # do we need an anchor (\phantomsection)? + set_anchor = not isinstance(node.parent, (nodes.caption, nodes.title)) + # TODO: where else can/must we omit the \phantomsection? + self.out += self.ids_to_labels(node, set_anchor) + + def depart_target(self, node): + pass + + def visit_tbody(self, node): + # BUG write preamble if not yet done (colspecs not []) + # for tables without heads. + if not self.active_table.get('preamble written'): + self.visit_thead(node) + self.depart_thead(None) + + def depart_tbody(self, node): + pass + + def visit_term(self, node): + """definition list term""" + # Commands with optional args inside an optional arg must be put + # in a group, e.g. ``\item[{\hyperref[label]{text}}]``. + self.out.append('\\item[{') + + def depart_term(self, node): + self.out.append('}] ') + # Do we need a \leavevmode (line break if the field body begins + # with a list or environment)? + next_node = node.next_node(descend=False, siblings=True) + if isinstance(next_node, nodes.term): + self.out.append('\n') + elif not isinstance(next_node, nodes.classifier): + self.out.append(self.term_postfix(next_node)) + + def visit_tgroup(self, node): + pass + + def depart_tgroup(self, node): + pass + + _thead_depth = 0 + + def thead_depth(self): + return self._thead_depth + + def visit_thead(self, node): + self._thead_depth += 1 + if 1 == self.thead_depth(): + self.out.append('{%s}\n' % self.active_table.get_colspecs(node)) + self.active_table.set('preamble written', 1) + if self.active_table.caption: + if self._thead_depth == 1: + pre = [r'\caption{'] + post = self.ids_to_labels(node.parent.parent, False) + [r'}\\'] + else: + pre = [r'\caption[]{'] + post = [r' (... continued)}\\'] + self.out.extend(pre + self.active_table.caption + post + ['\n']) + self.out.extend(self.active_table.visit_thead()) + + def depart_thead(self, node): + if node is not None: + self.out.extend(self.active_table.depart_thead()) + if self.active_table.need_recurse(): + node.walkabout(self) + self._thead_depth -= 1 + + def visit_title(self, node): + """Append section and other titles.""" + # Document title + if isinstance(node.parent, nodes.document): + self.push_output_collector(self.title) + self.context.append('') + self.pdfinfo.append(' pdftitle={%s},' % + self.encode(node.astext())) + # Topic titles (topic, admonition, sidebar) + elif (isinstance(node.parent, nodes.topic) + or isinstance(node.parent, nodes.admonition) + or isinstance(node.parent, nodes.sidebar)): + classes = node.parent['classes'] or [node.parent.tagname] + if self.settings.legacy_class_functions: + self.fallbacks['title'] = PreambleCmds.title_legacy + self.out.append('\n\\DUtitle[%s]{' % ','.join(classes)) + else: + if not self.fallback_stylesheet: + self.fallbacks['title'] = PreambleCmds.title + self.out.append('\n\\DUtitle{') + self.context.append('}\n') + # Table caption + elif isinstance(node.parent, nodes.table): + self.push_output_collector(self.active_table.caption) + self.context.append('') + # Section title + else: + if hasattr(PreambleCmds, 'secnumdepth'): + self.requirements['secnumdepth'] = PreambleCmds.secnumdepth + level = self.section_level + section_name = self.d_class.section(level) + self.out.append('\n\n') + if level > len(self.d_class.sections): + # section level not supported by LaTeX + if self.settings.legacy_class_functions: + self.fallbacks['title'] = PreambleCmds.title_legacy + section_name += '[section%s]' % roman.toRoman(level) + else: + if not self.fallback_stylesheet: + self.fallbacks['title'] = PreambleCmds.title + self.fallbacks['DUclass'] = PreambleCmds.duclass + self.out.append('\\begin{DUclass}{section%s}\n' + % roman.toRoman(level)) + + # System messages heading in red: + if 'system-messages' in node.parent['classes']: + self.requirements['color'] = PreambleCmds.color + section_title = self.encode(node.astext()) + self.out.append(r'\%s[%s]{\color{red}' % ( + section_name, section_title)) + else: + self.out.append(r'\%s{' % section_name) + + # label and ToC entry: + bookmark = [''] + # add sections with unsupported level to toc and pdfbookmarks? + ## if level > len(self.d_class.sections): + ## section_title = self.encode(node.astext()) + ## bookmark.append(r'\addcontentsline{toc}{%s}{%s}' % + ## (section_name, section_title)) + bookmark += self.ids_to_labels(node.parent, set_anchor=False) + self.context.append('%\n '.join(bookmark) + '%\n}\n') + if (level > len(self.d_class.sections) + and not self.settings.legacy_class_functions): + self.context[-1] += '\\end{DUclass}\n' + # MAYBE postfix paragraph and subparagraph with \leavevmode to + # ensure floats stay in the section and text starts on a new line. + + def depart_title(self, node): + self.out.append(self.context.pop()) + if isinstance(node.parent, (nodes.table, nodes.document)): + self.pop_output_collector() + + def visit_contents(self, node): + """Write the table of contents. + + Called from visit_topic() for "contents" topics. + """ + # requirements/setup for local ToC with package "minitoc", + if self.use_latex_toc and 'local' in node['classes']: + section_name = self.d_class.section(self.section_level) + # minitoc only supports "part" and toplevel sections + minitoc_names = {'part': 'part', + 'chapter': 'mini', + 'section': 'sect'} + if 'chapter' in self.d_class.sections: + del minitoc_names['section'] + try: + mtc_name = minitoc_names[section_name] + except KeyError: + self.warn('Skipping local ToC at "%s" level.\n' + ' Feature not supported with option "use-latex-toc"' + % section_name, base_node=node) + raise nodes.SkipNode + + # labels and PDF bookmark (sidebar entry) + self.out.append('\n') # start new paragraph + if node['names']: # don't add labels just for auto-ids + self.out += self.ids_to_labels(node, newline=True) + if (isinstance(node.next_node(), nodes.title) + and 'local' not in node['classes'] + and self.settings.documentclass != 'memoir'): + self.out.append('\\pdfbookmark[%d]{%s}{%s}\n' % + (self.section_level+1, + node.next_node().astext(), + node.get('ids', ['contents'])[0])) + + # Docutils generated contents list (no page numbers) + if not self.use_latex_toc: + self.fallbacks['toc-list'] = PreambleCmds.toc_list + self.duclass_open(node) + return + + # ToC by LaTeX + depth = node.get('depth', 0) + maxdepth = len(self.d_class.sections) + if isinstance(node.next_node(), nodes.title): + title = self.encode(node[0].astext()) + else: + title = '' + if 'local' in node['classes']: + # use the "minitoc" package + self.requirements['minitoc'] = PreambleCmds.minitoc + self.requirements['minitoc-'+mtc_name] = r'\do%stoc'%mtc_name + self.requirements['minitoc-%s-depth' % mtc_name] = ( + r'\mtcsetdepth{%stoc}{%d}' % (mtc_name, maxdepth)) + # "depth" option: Docutils stores a relative depth while + # minitoc expects an absolute depth!: + offset = {'sect': 1, 'mini': 0, 'part': 0} + if 'chapter' in self.d_class.sections: + offset['part'] = -1 + if depth: + self.out.append('\\setcounter{%stocdepth}{%d}' % + (mtc_name, depth + offset[mtc_name])) + # title: + self.out.append('\\mtcsettitle{%stoc}{%s}\n' % (mtc_name, title)) + # the toc-generating command: + self.out.append('\\%stoc\n' % mtc_name) + else: + if depth: + self.out.append('\\setcounter{tocdepth}{%d}\n' + % self.d_class.latex_section_depth(depth)) + if title != 'Contents': + self.out.append('\\renewcommand{\\contentsname}{%s}\n' % title) + self.out.append('\\tableofcontents\n') + self.has_latex_toc = True + # ignore rest of node content + raise nodes.SkipNode + + def visit_topic(self, node): + # Topic nodes can be generic topic, abstract, dedication, or ToC. + # table of contents: + if 'contents' in node['classes']: + self.visit_contents(node) + elif ('abstract' in node['classes'] + and self.settings.use_latex_abstract): + self.push_output_collector(self.abstract) + self.out.append('\\begin{abstract}') + if isinstance(node.next_node(), nodes.title): + node.pop(0) # LaTeX provides its own title + else: + # special topics: + if 'abstract' in node['classes']: + if not self.fallback_stylesheet: + self.fallbacks['abstract'] = PreambleCmds.abstract + if self.settings.legacy_class_functions: + self.fallbacks['abstract'] = PreambleCmds.abstract_legacy + self.push_output_collector(self.abstract) + elif 'dedication' in node['classes']: + if not self.fallback_stylesheet: + self.fallbacks['dedication'] = PreambleCmds.dedication + self.push_output_collector(self.dedication) + else: + node['classes'].insert(0, 'topic') + self.visit_block_quote(node) + + def depart_topic(self, node): + if ('abstract' in node['classes'] + and self.settings.use_latex_abstract): + self.out.append('\\end{abstract}\n') + elif 'contents' in node['classes']: + self.duclass_close(node) + else: + self.depart_block_quote(node) + if ('abstract' in node['classes'] + or 'dedication' in node['classes']): + self.pop_output_collector() + + def visit_transition(self, node): + if not self.fallback_stylesheet: + self.fallbacks['transition'] = PreambleCmds.transition + self.out.append('\n%' + '_' * 75 + '\n') + self.out.append('\\DUtransition\n') + + def depart_transition(self, node): + pass + + def visit_version(self, node): + self.visit_docinfo_item(node, 'version') + + def depart_version(self, node): + self.depart_docinfo_item(node) + + def unimplemented_visit(self, node): + raise NotImplementedError('visiting unimplemented node type: %s' % + node.__class__.__name__) + +# def unknown_visit(self, node): +# def default_visit(self, node): + +# vim: set ts=4 et ai : diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/default.tex b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/default.tex new file mode 100644 index 00000000..86552ab6 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/default.tex @@ -0,0 +1,14 @@ +$head_prefix% generated by Docutils <https://docutils.sourceforge.io/> +\usepackage{cmap} % fix search and cut-and-paste in Acrobat +$requirements +%%% Custom LaTeX preamble +$latex_preamble +%%% User specified packages and stylesheets +$stylesheet +%%% Fallback definitions for Docutils-specific commands +$fallbacks +$pdfsetup +%%% Body +\begin{document} +$titledata$body_pre_docinfo$docinfo$dedication$abstract$body +\end{document} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/docutils.sty b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/docutils.sty new file mode 100644 index 00000000..52386bb9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/docutils.sty @@ -0,0 +1,223 @@ +%% docutils.sty: macros for Docutils LaTeX output. +%% +%% Copyright © 2020 Günter Milde +%% Released under the terms of the `2-Clause BSD license`, in short: +%% +%% Copying and distribution of this file, with or without modification, +%% are permitted in any medium without royalty provided the copyright +%% notice and this notice are preserved. +%% This file is offered as-is, without any warranty. + +% .. include:: README.md +% +% Implementation +% ============== +% +% :: + +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage{docutils} + [2021/05/18 macros for Docutils LaTeX output] + +% Helpers +% ------- +% +% duclass:: + +% class handling for environments (block-level elements) +% \begin{DUclass}{spam} tries \DUCLASSspam and +% \end{DUclass}{spam} tries \endDUCLASSspam +\ifx\DUclass\undefined % poor man's "provideenvironment" + \newenvironment{DUclass}[1]% + {% "#1" does not work in end-part of environment. + \def\DocutilsClassFunctionName{DUCLASS#1} + \csname \DocutilsClassFunctionName \endcsname}% + {\csname end\DocutilsClassFunctionName \endcsname}% +\fi + +% providelength:: + +% Provide a length variable and set default, if it is new +\providecommand*{\DUprovidelength}[2]{ + \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{} +} + + +% Configuration defaults +% ---------------------- +% +% See `Docutils LaTeX Writer`_ for details. +% +% abstract:: + +\providecommand*{\DUCLASSabstract}{ + \renewcommand{\DUtitle}[1]{\centerline{\textbf{##1}}} +} + +% dedication:: + +% special topic for dedications +\providecommand*{\DUCLASSdedication}{% + \renewenvironment{quote}{\begin{center}}{\end{center}}% +} + +% TODO: add \em to set dedication text in italics? +% +% docinfo:: + +% width of docinfo table +\DUprovidelength{\DUdocinfowidth}{0.9\linewidth} + +% error:: + +\providecommand*{\DUCLASSerror}{\color{red}} + +% highlight_rules:: + +% basic code highlight: +\providecommand*\DUrolecomment[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\providecommand*\DUroledeleted[1]{\textcolor[rgb]{0.40,0.40,0.40}{#1}} +\providecommand*\DUrolekeyword[1]{\textbf{#1}} +\providecommand*\DUrolestring[1]{\textit{#1}} + +% Elements +% -------- +% +% Definitions for unknown or to-be-configured Docutils elements. +% +% admonition:: + +% admonition environment (specially marked topic) +\ifx\DUadmonition\undefined % poor man's "provideenvironment" + \newbox{\DUadmonitionbox} + \newenvironment{DUadmonition}% + {\begin{center} + \begin{lrbox}{\DUadmonitionbox} + \begin{minipage}{0.9\linewidth} + }% + { \end{minipage} + \end{lrbox} + \fbox{\usebox{\DUadmonitionbox}} + \end{center} + } +\fi + +% fieldlist:: + +% field list environment (for separate configuration of `field lists`) +\ifthenelse{\isundefined{\DUfieldlist}}{ + \newenvironment{DUfieldlist}% + {\quote\description} + {\enddescription\endquote} +}{} + +% footnotes:: + +% numerical or symbol footnotes with hyperlinks and backlinks +\providecommand*{\DUfootnotemark}[3]{% + \raisebox{1em}{\hypertarget{#1}{}}% + \hyperlink{#2}{\textsuperscript{#3}}% +} +\providecommand{\DUfootnotetext}[4]{% + \begingroup% + \renewcommand{\thefootnote}{% + \protect\raisebox{1em}{\protect\hypertarget{#1}{}}% + \protect\hyperlink{#2}{#3}}% + \footnotetext{#4}% + \endgroup% +} + +% inline:: + +% custom inline roles: \DUrole{#1}{#2} tries \DUrole#1{#2} +\providecommand*{\DUrole}[2]{% + \ifcsname DUrole#1\endcsname% + \csname DUrole#1\endcsname{#2}% + \else% + #2% + \fi% +} + +% legend:: + +% legend environment (in figures and formal tables) +\ifthenelse{\isundefined{\DUlegend}}{ + \newenvironment{DUlegend}{\small}{} +}{} + +% lineblock:: + +% line block environment +\DUprovidelength{\DUlineblockindent}{2.5em} +\ifthenelse{\isundefined{\DUlineblock}}{ + \newenvironment{DUlineblock}[1]{% + \list{}{\setlength{\partopsep}{\parskip} + \addtolength{\partopsep}{\baselineskip} + \setlength{\topsep}{0pt} + \setlength{\itemsep}{0.15\baselineskip} + \setlength{\parsep}{0pt} + \setlength{\leftmargin}{#1}} + \raggedright + } + {\endlist} +}{} + +% optionlist:: + +% list of command line options +\providecommand*{\DUoptionlistlabel}[1]{\bfseries #1 \hfill} +\DUprovidelength{\DUoptionlistindent}{3cm} +\ifthenelse{\isundefined{\DUoptionlist}}{ + \newenvironment{DUoptionlist}{% + \list{}{\setlength{\labelwidth}{\DUoptionlistindent} + \setlength{\rightmargin}{1cm} + \setlength{\leftmargin}{\rightmargin} + \addtolength{\leftmargin}{\labelwidth} + \addtolength{\leftmargin}{\labelsep} + \renewcommand{\makelabel}{\DUoptionlistlabel}} + } + {\endlist} +}{} + +% rubric:: + +% informal heading +\providecommand*{\DUrubric}[1]{\subsubsection*{\emph{#1}}} + +% sidebar:: + +% text outside the main text flow +\providecommand{\DUsidebar}[1]{% + \begin{center} + \colorbox[gray]{0.80}{\parbox{0.9\linewidth}{#1}} + \end{center} +} + +% title:: + +% title for topics, admonitions, unsupported section levels, and sidebar +\providecommand*{\DUtitle}[1]{% + \smallskip\noindent\textbf{#1}\smallskip} + +% subtitle:: + +% subtitle (for sidebar) +\providecommand*{\DUsubtitle}[1]{\par\emph{#1}\smallskip} + +% documentsubtitle:: + +% subtitle (in document title) +\providecommand*{\DUdocumentsubtitle}[1]{{\large #1}} + +% titlereference:: + +% titlereference standard role +\providecommand*{\DUroletitlereference}[1]{\textsl{#1}} + +% transition:: + +% transition (break / fancybreak / anonymous section) +\providecommand*{\DUtransition}{% + \hspace*{\fill}\hrulefill\hspace*{\fill} + \vskip 0.5\baselineskip +} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/titlepage.tex b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/titlepage.tex new file mode 100644 index 00000000..278fba80 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/titlepage.tex @@ -0,0 +1,19 @@ +% generated by Docutils <https://docutils.sourceforge.io/> +$head_prefix +\usepackage{cmap} % fix search and cut-and-paste in Acrobat +$requirements +%%% Custom LaTeX preamble +$latex_preamble +%%% User specified packages and stylesheets +$stylesheet +%%% Fallback definitions for Docutils-specific commands +$fallbacks$pdfsetup +$titledata +%%% Body +\begin{document} +\begin{titlepage} +$body_pre_docinfo$docinfo$dedication$abstract +\thispagestyle{empty} +\end{titlepage} +$body +\end{document} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/titlingpage.tex b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/titlingpage.tex new file mode 100644 index 00000000..1e96806d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/titlingpage.tex @@ -0,0 +1,18 @@ +% generated by Docutils <https://docutils.sourceforge.io/> +$head_prefix +$requirements +%%% Custom LaTeX preamble +$latex_preamble +%%% User specified packages and stylesheets +$stylesheet +%%% Fallback definitions for Docutils-specific commands +$fallbacks$pdfsetup +$titledata +%%% Body +\begin{document} +\begin{titlingpage} +\thispagestyle{empty} +$body_pre_docinfo$docinfo$dedication$abstract +\end{titlingpage} +$body +\end{document} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/xelatex.tex b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/xelatex.tex new file mode 100644 index 00000000..4d802805 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/latex2e/xelatex.tex @@ -0,0 +1,21 @@ +$head_prefix% generated by Docutils <https://docutils.sourceforge.io/> +% rubber: set program xelatex +\usepackage{fontspec} +% \defaultfontfeatures{Scale=MatchLowercase} +% straight double quotes (defined T1 but missing in TU): +\ifdefined \UnicodeEncodingName + \DeclareTextCommand{\textquotedbl}{\UnicodeEncodingName}{% + {\addfontfeatures{RawFeature=-tlig,Mapping=}\char34}}% +\fi +$requirements +%%% Custom LaTeX preamble +$latex_preamble +%%% User specified packages and stylesheets +$stylesheet +%%% Fallback definitions for Docutils-specific commands +$fallbacks$pdfsetup +$titledata +%%% Body +\begin{document} +$body_pre_docinfo$docinfo$dedication$abstract$body +\end{document} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/manpage.py b/.venv/lib/python3.12/site-packages/docutils/writers/manpage.py new file mode 100644 index 00000000..9c0ab479 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/manpage.py @@ -0,0 +1,1214 @@ +# $Id: manpage.py 9610 2024-04-03 17:29:36Z grubert $ +# Author: Engelbert Gruber <grubert@users.sourceforge.net> +# Copyright: This module is put into the public domain. + +""" +Simple man page writer for reStructuredText. + +Man pages (short for "manual pages") contain system documentation on unix-like +systems. The pages are grouped in numbered sections: + + 1 executable programs and shell commands + 2 system calls + 3 library functions + 4 special files + 5 file formats + 6 games + 7 miscellaneous + 8 system administration + +Man pages are written *troff*, a text file formatting system. + +See https://www.tldp.org/HOWTO/Man-Page for a start. + +Man pages have no subsection only parts. +Standard parts + + NAME , + SYNOPSIS , + DESCRIPTION , + OPTIONS , + FILES , + SEE ALSO , + BUGS , + +and + + AUTHOR . + +A unix-like system keeps an index of the DESCRIPTIONs, which is accessible +by the command whatis or apropos. + +""" + +__docformat__ = 'reStructuredText' + +import re + +from docutils import nodes, writers, languages +try: + import roman +except ImportError: + import docutils.utils.roman as roman + +FIELD_LIST_INDENT = 7 +DEFINITION_LIST_INDENT = 7 +OPTION_LIST_INDENT = 7 +BLOCKQOUTE_INDENT = 3.5 +LITERAL_BLOCK_INDENT = 3.5 + +# Define two macros so man/roff can calculate the +# indent/unindent margins by itself +MACRO_DEF = (r""". +.nr rst2man-indent-level 0 +. +.de1 rstReportMargin +\\$1 \\n[an-margin] +level \\n[rst2man-indent-level] +level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] +- +\\n[rst2man-indent0] +\\n[rst2man-indent1] +\\n[rst2man-indent2] +.. +.de1 INDENT +.\" .rstReportMargin pre: +. RS \\$1 +. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] +. nr rst2man-indent-level +1 +.\" .rstReportMargin post: +.. +.de UNINDENT +. RE +.\" indent \\n[an-margin] +.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] +.nr rst2man-indent-level -1 +.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] +.in \\n[rst2man-indent\\n[rst2man-indent-level]]u +.. +""") + + +class Writer(writers.Writer): + + supported = ('manpage',) + """Formats this writer supports.""" + + output = None + """Final translated form of `document`.""" + + def __init__(self): + writers.Writer.__init__(self) + self.translator_class = Translator + + def translate(self): + visitor = self.translator_class(self.document) + self.document.walkabout(visitor) + self.output = visitor.astext() + + +class Table: + def __init__(self): + self._rows = [] + self._options = ['box', 'center'] + self._tab_char = '\t' + self._coldefs = [] + + def new_row(self): + self._rows.append([]) + + def append_separator(self, separator): + """Append the separator for table head.""" + self._rows.append([separator]) + + def append_cell(self, cell_lines): + """cell_lines is an array of lines""" + start = 0 + if len(cell_lines) > 0 and cell_lines[0] == '.sp\n': + start = 1 + self._rows[-1].append(cell_lines[start:]) + if len(self._coldefs) < len(self._rows[-1]): + self._coldefs.append('l') + + def _minimize_cell(self, cell_lines): + """Remove leading and trailing blank and ``.sp`` lines""" + while cell_lines and cell_lines[0] in ('\n', '.sp\n'): + del cell_lines[0] + while cell_lines and cell_lines[-1] in ('\n', '.sp\n'): + del cell_lines[-1] + + def as_list(self): + text = ['.TS\n'] + text.append(' '.join(self._options) + ';\n') + text.append('%s.\n' % ('|'.join(self._coldefs))) + for row in self._rows: + # row = array of cells. cell = array of lines. + text.append('T{\n') + for i in range(len(row)): + cell = row[i] + self._minimize_cell(cell) + text.extend(cell) + if not text[-1].endswith('\n'): + text[-1] += '\n' + if i < len(row)-1: + text.append('T}'+self._tab_char+'T{\n') + else: + text.append('T}\n') + text.append('_\n') # line between rows + text.pop() # pop last "line between" + text.append('.TE\n') + return text + + +class Translator(nodes.NodeVisitor): + """""" + + words_and_spaces = re.compile(r'\S+| +|\n') + possibly_a_roff_command = re.compile(r'\.\w') + document_start = """Man page generated from reStructuredText.""" + # TODO add "from docutils 0.21rc1." + + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + self.settings = settings = document.settings + lcode = settings.language_code + self.language = languages.get_language(lcode, document.reporter) + self.head = [] + self.body = [] + self.foot = [] + self.section_level = 0 + self.context = [] + self.topic_class = '' + self.colspecs = [] + self.compact_p = 1 + self.compact_simple = None + # the list style "*" bullet or "#" numbered + self._list_char = [] + # writing the header .TH and .SH NAME is postboned after + # docinfo. + self._docinfo = { + "title": "", "title_upper": "", + "subtitle": "", + "manual_section": "", "manual_group": "", + "author": [], + "date": "", + "copyright": "", + "version": "", + } + self._docinfo_keys = [] # a list to keep the sequence as in source. + self._docinfo_names = {} # to get name from text not normalized. + self._in_docinfo = None + self._field_name = None + self._active_table = None + self._has_a_table = False # is there a table in this document + self._in_literal = False + self.header_written = 0 + self._line_block = 0 + self.authors = [] + self.section_level = 0 + self._indent = [0] + # central definition of simple processing rules + # what to output on : visit, depart + # Do not use paragraph requests ``.PP`` because these set indentation. + # use ``.sp``. Remove superfluous ``.sp`` in ``astext``. + # + # Fonts are put on a stack, the top one is used. + # ``.ft P`` or ``\\fP`` pop from stack. + # But ``.BI`` seams to fill stack with BIBIBIBIB... + # ``B`` bold, ``I`` italic, ``R`` roman should be available. + # + # Requests start wit a dot ``.`` or the no-break control character, + # a neutral apostrophe ``'`` suppresses the break implied by some + # requests. + self.defs = { + 'indent': ('.INDENT %.1f\n', '.UNINDENT\n'), + 'definition_list_item': ('.TP', ''), # par. with hanging tag + 'field_name': ('.TP\n.B ', '\n'), + 'literal': ('\\fB', '\\fP'), + 'literal_block': ('.sp\n.EX\n', '\n.EE\n'), + + 'option_list_item': ('.TP\n', ''), + + 'reference': (r'\fI\%', r'\fP'), + 'emphasis': ('\\fI', '\\fP'), + 'strong': ('\\fB', '\\fP'), + 'title_reference': ('\\fI', '\\fP'), + + 'topic-title': ('.SS ',), + 'sidebar-title': ('.SS ',), + + 'problematic': ('\n.nf\n', '\n.fi\n'), + } + # NOTE do not specify the newline before a dot-command, but ensure + # it is there. + + def comment_begin(self, text): + """Return commented version of the passed text WITHOUT end of + line/comment.""" + prefix = '.\\" ' + out_text = ''.join([(prefix + in_line + '\n') + for in_line in text.split('\n')]) + return out_text + + def comment(self, text): + """Return commented version of the passed text.""" + return self.comment_begin(text)+'.\n' + + def ensure_eol(self): + """Ensure the last line in body is terminated by new line.""" + if len(self.body) > 0 and self.body[-1][-1] != '\n': + self.body.append('\n') + + def astext(self): + """Return the final formatted document as a string.""" + if not self.header_written: + # ensure we get a ".TH" as viewers require it. + self.append_header() + # filter body + for i in range(len(self.body)-1, 0, -1): + # remove superfluous vertical gaps. + if self.body[i] == '.sp\n': + if self.body[i - 1][:4] in ('.BI ', '.IP '): + self.body[i] = '.\n' + elif (self.body[i - 1][:3] == '.B ' + and self.body[i - 2][:4] == '.TP\n'): + self.body[i] = '.\n' + elif (self.body[i - 1] == '\n' + and not self.possibly_a_roff_command.match( + self.body[i - 2]) + and (self.body[i - 3][:7] == '.TP\n.B ' + or self.body[i - 3][:4] == '\n.B ') + ): + self.body[i] = '.\n' + return ''.join(self.head + self.body + self.foot) + + def deunicode(self, text): + text = text.replace('\xa0', '\\ ') + text = text.replace('\u2020', '\\(dg') + return text + + def visit_Text(self, node): + text = node.astext() + text = text.replace('\\', '\\e') + replace_pairs = [ + ('-', '\\-'), + ('\'', '\\(aq'), + ('´', "\\'"), + ('`', '\\(ga'), + ('"', '\\(dq'), # double quotes are a problem on macro lines + ] + for (in_char, out_markup) in replace_pairs: + text = text.replace(in_char, out_markup) + # unicode + text = self.deunicode(text) + # prevent interpretation of "." at line start + if text.startswith('.'): + text = '\\&' + text + if self._in_literal: + text = text.replace('\n.', '\n\\&.') + self.body.append(text) + + def depart_Text(self, node): + pass + + def list_start(self, node): + class EnumChar: + enum_style = { + 'bullet': '\\(bu', + 'emdash': '\\(em', + } + + def __init__(self, style): + self._style = style + if 'start' in node: + self._cnt = node['start'] - 1 + else: + self._cnt = 0 + self._indent = 2 + if style == 'arabic': + # indentation depends on number of children + # and start value. + self._indent = len(str(len(node.children))) + self._indent += len(str(self._cnt)) + 1 + elif style == 'loweralpha': + self._cnt += ord('a') - 1 + self._indent = 3 + elif style == 'upperalpha': + self._cnt += ord('A') - 1 + self._indent = 3 + elif style.endswith('roman'): + self._indent = 5 + + def __next__(self): + if self._style == 'bullet': + return self.enum_style[self._style] + elif self._style == 'emdash': + return self.enum_style[self._style] + self._cnt += 1 + # TODO add prefix postfix + if self._style == 'arabic': + return "%d." % self._cnt + elif self._style in ('loweralpha', 'upperalpha'): + return "%c." % self._cnt + elif self._style.endswith('roman'): + res = roman.toRoman(self._cnt) + '.' + if self._style.startswith('upper'): + return res.upper() + return res.lower() + else: + return "%d." % self._cnt + + def get_width(self): + return self._indent + + def __repr__(self): + return 'enum_style-%s' % list(self._style) + + if 'enumtype' in node: + self._list_char.append(EnumChar(node['enumtype'])) + else: + self._list_char.append(EnumChar('bullet')) + if len(self._list_char) > 1: + # indent nested lists + self.indent(self._list_char[-2].get_width()) + else: + self.indent(self._list_char[-1].get_width()) + + def list_end(self): + self.dedent() + self._list_char.pop() + + def header(self): + th = (".TH \"%(title_upper)s\" \"%(manual_section)s\"" + " \"%(date)s\" \"%(version)s\"") % self._docinfo + if self._docinfo["manual_group"]: + th += " \"%(manual_group)s\"" % self._docinfo + th += "\n" + sh_tmpl = (".SH NAME\n" + "%(title)s \\- %(subtitle)s\n") + return th + sh_tmpl % self._docinfo + + def append_header(self): + """append header with .TH and .SH NAME""" + # NOTE before everything + # .TH title_upper section date source manual + # BUT macros before .TH for whatis database generators. + if self.header_written: + return + self.head.append(MACRO_DEF) + self.head.append(self.header()) + self.header_written = 1 + + def visit_address(self, node): + self.visit_docinfo_item(node, 'address') + + def depart_address(self, node): + pass + + def visit_admonition(self, node, name=None): + # + # Make admonitions a simple block quote + # with a strong heading + # + # Using .IP/.RE doesn't preserve indentation + # when admonitions contain bullets, literal, + # and/or block quotes. + # + if name: + # .. admonition:: has no name + self.body.append('.sp\n') + name = '%s%s:%s\n' % ( + self.defs['strong'][0], + self.language.labels.get(name, name).upper(), + self.defs['strong'][1], + ) + self.body.append(name) + self.visit_block_quote(node) + + def depart_admonition(self, node): + self.depart_block_quote(node) + + def visit_attention(self, node): + self.visit_admonition(node, 'attention') + + depart_attention = depart_admonition + + def visit_docinfo_item(self, node, name): + if name == 'author': + self._docinfo[name].append(node.astext()) + else: + self._docinfo[name] = node.astext() + self._docinfo_keys.append(name) + raise nodes.SkipNode + + def depart_docinfo_item(self, node): + pass + + def visit_author(self, node): + self.visit_docinfo_item(node, 'author') + + depart_author = depart_docinfo_item + + def visit_authors(self, node): + # _author is called anyway. + pass + + def depart_authors(self, node): + pass + + def visit_block_quote(self, node): + # BUG/HACK: indent always uses the _last_ indentation, + # thus we need two of them. + self.indent(BLOCKQOUTE_INDENT) + self.indent(0) + + def depart_block_quote(self, node): + self.dedent() + self.dedent() + + def visit_bullet_list(self, node): + self.list_start(node) + + def depart_bullet_list(self, node): + self.list_end() + + def visit_caption(self, node): + pass + + def depart_caption(self, node): + pass + + def visit_caution(self, node): + self.visit_admonition(node, 'caution') + + depart_caution = depart_admonition + + def visit_citation(self, node): + num = node.astext().split(None, 1)[0] + num = num.strip() + self.body.append('.IP [%s] 5\n' % num) + + def depart_citation(self, node): + pass + + def visit_citation_reference(self, node): + self.body.append('['+node.astext()+']') + raise nodes.SkipNode + + def visit_classifier(self, node): + self.body.append('(') + + def depart_classifier(self, node): + self.body.append(')') + self.depart_term(node) # close the term element after last classifier + + def visit_colspec(self, node): + self.colspecs.append(node) + + def depart_colspec(self, node): + pass + + def write_colspecs(self): + self.body.append("%s.\n" % ('L '*len(self.colspecs))) + + def visit_comment(self, node, + sub=re.compile('-(?=-)').sub): + self.body.append(self.comment(node.astext())) + raise nodes.SkipNode + + def visit_contact(self, node): + self.visit_docinfo_item(node, 'contact') + + depart_contact = depart_docinfo_item + + def visit_container(self, node): + pass + + def depart_container(self, node): + pass + + def visit_compound(self, node): + pass + + def depart_compound(self, node): + pass + + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright') + + def visit_danger(self, node): + self.visit_admonition(node, 'danger') + + depart_danger = depart_admonition + + def visit_date(self, node): + self.visit_docinfo_item(node, 'date') + + def visit_decoration(self, node): + pass + + def depart_decoration(self, node): + pass + + def visit_definition(self, node): + pass + + def depart_definition(self, node): + pass + + def visit_definition_list(self, node): + self.indent(DEFINITION_LIST_INDENT) + + def depart_definition_list(self, node): + self.dedent() + + def visit_definition_list_item(self, node): + self.body.append(self.defs['definition_list_item'][0]) + + def depart_definition_list_item(self, node): + self.body.append(self.defs['definition_list_item'][1]) + + def visit_description(self, node): + pass + + def depart_description(self, node): + pass + + def visit_docinfo(self, node): + self._in_docinfo = 1 + + def depart_docinfo(self, node): + self._in_docinfo = None + # NOTE nothing should be written before this + self.append_header() + + def visit_doctest_block(self, node): + self.body.append(self.defs['literal_block'][0]) + self._in_literal = True + + def depart_doctest_block(self, node): + self._in_literal = False + self.body.append(self.defs['literal_block'][1]) + + def visit_document(self, node): + # no blank line between comment and header. + self.head.append(self.comment(self.document_start).rstrip()+'\n') + # writing header is postponed + self.header_written = 0 + + def depart_document(self, node): + if self._docinfo['author']: + self.body.append('.SH AUTHOR\n%s\n' + % ', '.join(self._docinfo['author'])) + skip = ('author', 'copyright', 'date', + 'manual_group', 'manual_section', + 'subtitle', + 'title', 'title_upper', 'version') + for name in self._docinfo_keys: + if name == 'address': + self.body.append("\n%s:\n%s%s.nf\n%s\n.fi\n%s%s" % ( + self.language.labels.get(name, name), + self.defs['indent'][0] % 0, + self.defs['indent'][0] % BLOCKQOUTE_INDENT, + self._docinfo[name], + self.defs['indent'][1], + self.defs['indent'][1])) + elif name not in skip: + if name in self._docinfo_names: + label = self._docinfo_names[name] + else: + label = self.language.labels.get(name, name) + self.body.append("\n%s: %s\n" % (label, self._docinfo[name])) + if self._docinfo['copyright']: + self.body.append('.SH COPYRIGHT\n%s\n' + % self._docinfo['copyright']) + self.body.append(self.comment('Generated by docutils manpage writer.')) + + def visit_emphasis(self, node): + self.body.append(self.defs['emphasis'][0]) + + def depart_emphasis(self, node): + self.body.append(self.defs['emphasis'][1]) + + def visit_entry(self, node): + # a cell in a table row + if 'morerows' in node: + self.document.reporter.warning( + '"table row spanning" not supported', base_node=node) + if 'morecols' in node: + self.document.reporter.warning( + '"table cell spanning" not supported', base_node=node) + self.context.append(len(self.body)) + + def depart_entry(self, node): + start = self.context.pop() + self._active_table.append_cell(self.body[start:]) + del self.body[start:] + + def visit_enumerated_list(self, node): + self.list_start(node) + + def depart_enumerated_list(self, node): + self.list_end() + + def visit_error(self, node): + self.visit_admonition(node, 'error') + + depart_error = depart_admonition + + def visit_field(self, node): + pass + + def depart_field(self, node): + pass + + def visit_field_body(self, node): + if self._in_docinfo: + name_normalized = self._field_name.lower().replace(" ", "_") + self._docinfo_names[name_normalized] = self._field_name + self.visit_docinfo_item(node, name_normalized) + raise nodes.SkipNode + + def depart_field_body(self, node): + pass + + def visit_field_list(self, node): + self.indent(FIELD_LIST_INDENT) + + def depart_field_list(self, node): + self.dedent() + + def visit_field_name(self, node): + if self._in_docinfo: + self._field_name = node.astext() + raise nodes.SkipNode + else: + self.body.append(self.defs['field_name'][0]) + + def depart_field_name(self, node): + self.body.append(self.defs['field_name'][1]) + + def visit_figure(self, node): + self.indent(2.5) + self.indent(0) + + def depart_figure(self, node): + self.dedent() + self.dedent() + + def visit_footer(self, node): + self.document.reporter.warning('"footer" not supported', + base_node=node) + # avoid output the link to document source + raise nodes.SkipNode + + def depart_footer(self, node): + pass + + def visit_footnote(self, node): + num, text = node.astext().split(None, 1) + num = num.strip() + self.body.append('.IP [%s] 5\n' % self.deunicode(num)) + + def depart_footnote(self, node): + pass + + def footnote_backrefs(self, node): + self.document.reporter.warning('"footnote_backrefs" not supported', + base_node=node) + + def visit_footnote_reference(self, node): + self.body.append('['+self.deunicode(node.astext())+']') + raise nodes.SkipNode + + def depart_footnote_reference(self, node): + pass + + def visit_generated(self, node): + pass + + def depart_generated(self, node): + pass + + def visit_header(self, node): + raise NotImplementedError(node.astext()) + + def depart_header(self, node): + pass + + def visit_hint(self, node): + self.visit_admonition(node, 'hint') + + depart_hint = depart_admonition + + def visit_subscript(self, node): + self.body.append('\\s-2\\d') + + def depart_subscript(self, node): + self.body.append('\\u\\s0') + + def visit_superscript(self, node): + self.body.append('\\s-2\\u') + + def depart_superscript(self, node): + self.body.append('\\d\\s0') + + def visit_attribution(self, node): + self.body.append('\\(em ') + + def depart_attribution(self, node): + self.body.append('\n') + + def visit_image(self, node): + self.document.reporter.warning('"image" not supported', + base_node=node) + text = [] + if 'alt' in node.attributes: + text.append(node.attributes['alt']) + if 'uri' in node.attributes: + text.append(node.attributes['uri']) + self.body.append('[image: %s]\n' % ('/'.join(text))) + raise nodes.SkipNode + + def visit_important(self, node): + self.visit_admonition(node, 'important') + + depart_important = depart_admonition + + def visit_inline(self, node): + pass + + def depart_inline(self, node): + pass + + def visit_label(self, node): + # footnote and citation + if (isinstance(node.parent, nodes.footnote) + or isinstance(node.parent, nodes.citation)): + raise nodes.SkipNode + self.document.reporter.warning('"unsupported "label"', + base_node=node) + self.body.append('[') + + def depart_label(self, node): + self.body.append(']\n') + + def visit_legend(self, node): + pass + + def depart_legend(self, node): + pass + + # WHAT should we use .INDENT, .UNINDENT ? + def visit_line_block(self, node): + self._line_block += 1 + if self._line_block == 1: + # TODO: separate inline blocks from previous paragraphs + # see http://hg.intevation.org/mercurial/crew/rev/9c142ed9c405 + # self.body.append('.sp\n') + # but it does not work for me. + self.body.append('.nf\n') + else: + self.body.append('.in +2\n') + + def depart_line_block(self, node): + self._line_block -= 1 + if self._line_block == 0: + self.body.append('.fi\n') + self.body.append('.sp\n') + else: + self.body.append('.in -2\n') + + def visit_line(self, node): + pass + + def depart_line(self, node): + self.body.append('\n') + + def visit_list_item(self, node): + # man 7 man argues to use ".IP" instead of ".TP" + self.body.append('.IP %s %d\n' % ( + next(self._list_char[-1]), + self._list_char[-1].get_width(),)) + + def depart_list_item(self, node): + pass + + def visit_literal(self, node): + self.body.append(self.defs['literal'][0]) + + def depart_literal(self, node): + self.body.append(self.defs['literal'][1]) + + def visit_literal_block(self, node): + # BUG/HACK: indent always uses the _last_ indentation, + # thus we need two of them. + self.indent(LITERAL_BLOCK_INDENT) + self.indent(0) + self.body.append(self.defs['literal_block'][0]) + self._in_literal = True + + def depart_literal_block(self, node): + self._in_literal = False + self.body.append(self.defs['literal_block'][1]) + self.dedent() + self.dedent() + + 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) + + # <meta> shall become an optional standard node: + # def visit_meta(self, node): + # raise NotImplementedError(node.astext()) + + # def depart_meta(self, node): + # pass + + def visit_note(self, node): + self.visit_admonition(node, 'note') + + depart_note = depart_admonition + + def indent(self, by=0.5): + # if we are in a section ".SH" there already is a .RS + step = self._indent[-1] + self._indent.append(by) + self.body.append(self.defs['indent'][0] % step) + + def dedent(self): + self._indent.pop() + self.body.append(self.defs['indent'][1]) + + def visit_option_list(self, node): + self.indent(OPTION_LIST_INDENT) + + def depart_option_list(self, node): + self.dedent() + + def visit_option_list_item(self, node): + # one item of the list + self.body.append(self.defs['option_list_item'][0]) + + def depart_option_list_item(self, node): + self.body.append(self.defs['option_list_item'][1]) + + def visit_option_group(self, node): + # as one option could have several forms it is a group + # options without parameter bold only, .B, -v + # options with parameter bold italic, .BI, -f file + # + # we do not know if .B or .BI, blind guess: + self.context.append('.B ') # Add blank for sphinx (docutils/bugs/380) + self.context.append(len(self.body)) # to be able to insert later + self.context.append(0) # option counter + + def depart_option_group(self, node): + self.context.pop() # the counter + start_position = self.context.pop() + text = self.body[start_position:] + del self.body[start_position:] + self.body.append('%s%s\n' % (self.context.pop(), ''.join(text))) + + def visit_option(self, node): + # each form of the option will be presented separately + if self.context[-1] > 0: + if self.context[-3] == '.BI': + self.body.append('\\fR,\\fB ') + else: + self.body.append('\\fP,\\fB ') + if self.context[-3] == '.BI': + self.body.append('\\') + self.body.append(' ') + + def depart_option(self, node): + self.context[-1] += 1 + + def visit_option_string(self, node): + # do not know if .B or .BI + pass + + def depart_option_string(self, node): + pass + + def visit_option_argument(self, node): + self.context[-3] = '.BI' # bold/italic alternate + if node['delimiter'] != ' ': + self.body.append('\\fB%s ' % node['delimiter']) + elif self.body[len(self.body)-1].endswith('='): + # a blank only means no blank in output, just changing font + self.body.append(' ') + else: + # blank backslash blank, switch font then a blank + self.body.append(' \\ ') + + def depart_option_argument(self, node): + pass + + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization') + + def depart_organization(self, node): + pass + + def first_child(self, node): + first = isinstance(node.parent[0], nodes.label) # skip label + for child in node.parent.children[first:]: + if isinstance(child, nodes.Invisible): + continue + if child is node: + return 1 + break + return 0 + + def visit_paragraph(self, node): + # ``.PP`` : Start standard indented paragraph. + # ``.LP`` : Start block paragraph, all except the first. + # ``.P [type]`` : Start paragraph type. + # NOTE do not use paragraph starts because they reset indentation. + # ``.sp`` is only vertical space + self.ensure_eol() + if not self.first_child(node): + self.body.append('.sp\n') + # set in literal to escape dots after a new-line-character + self._in_literal = True + + def depart_paragraph(self, node): + self._in_literal = False + self.body.append('\n') + + def visit_problematic(self, node): + self.body.append(self.defs['problematic'][0]) + + def depart_problematic(self, node): + self.body.append(self.defs['problematic'][1]) + + def visit_raw(self, node): + if 'manpage' in node.get('format', '').split(): + self.body.append(node.astext() + "\n") + # Keep non-manpage raw text out of output: + raise nodes.SkipNode + + def visit_reference(self, node): + """E.g. link or email address.""" + # .UR and .UE macros in roff use OSC8 escape sequences + # which are not supported everywhere yet + # therefore make the markup ourself + if 'refuri' in node: + # if content has the "email" do not output "mailto:email" + if node['refuri'].endswith(node.astext()): + self.body.append(" <") + #TODO elif 'refid' in node: + + def depart_reference(self, node): + if 'refuri' in node: + # if content has the "email" do not output "mailto:email" + if node['refuri'].endswith(node.astext()): + self.body.append("> ") + else: + self.body.append(" <%s>\n" % node['refuri']) + #TODO elif 'refid' in node: + + def visit_revision(self, node): + self.visit_docinfo_item(node, 'revision') + + depart_revision = depart_docinfo_item + + def visit_row(self, node): + self._active_table.new_row() + + def depart_row(self, node): + pass + + def visit_section(self, node): + self.section_level += 1 + + def depart_section(self, node): + self.section_level -= 1 + + def visit_status(self, node): + self.visit_docinfo_item(node, 'status') + + depart_status = depart_docinfo_item + + def visit_strong(self, node): + self.body.append(self.defs['strong'][0]) + + def depart_strong(self, node): + self.body.append(self.defs['strong'][1]) + + def visit_substitution_definition(self, node): + """Internal only.""" + raise nodes.SkipNode + + def visit_substitution_reference(self, node): + self.document.reporter.warning( + '"substitution_reference" not supported', base_node=node) + + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.sidebar): + self.body.append(self.defs['strong'][0]) + elif isinstance(node.parent, nodes.document): + self.visit_docinfo_item(node, 'subtitle') + elif isinstance(node.parent, nodes.section): + self.body.append(self.defs['strong'][0]) + + def depart_subtitle(self, node): + # document subtitle calls SkipNode + self.body.append(self.defs['strong'][1]+'\n.PP\n') + + def visit_system_message(self, node): + # TODO add report_level + # if node['level'] < self.document.reporter['writer'].report_level: + # Level is too low to display: + # raise nodes.SkipNode + attr = {} + if node.hasattr('id'): + attr['name'] = node['id'] + if node.hasattr('line'): + line = ', line %s' % node['line'] + else: + line = '' + self.body.append('.IP "System Message: %s/%s (%s:%s)"\n' + % (node['type'], node['level'], node['source'], line)) + + def depart_system_message(self, node): + pass + + def visit_table(self, node): + self._active_table = Table() + if not self._has_a_table: + self._has_a_table = True + # the comment to hint that preprocessor tbl should be called + self.head.insert(0, "'\\\" t\n") # ``'\" t`` + newline + + def depart_table(self, node): + self.ensure_eol() + self.body.extend(self._active_table.as_list()) + self._active_table = None + + def visit_target(self, node): + # targets are in-document hyper targets, without any use for man-pages. + raise nodes.SkipNode + + def visit_tbody(self, node): + pass + + def depart_tbody(self, node): + pass + + def visit_term(self, node): + self.body.append('\n.B ') + + def depart_term(self, node): + _next = node.next_node(None, descend=False, siblings=True) + # Nest (optional) classifier(s) in the <term> element + if isinstance(_next, nodes.classifier): + self.body.append(' ') + return # skip (depart_classifier() calls this function again) + if isinstance(_next, nodes.term): + self.body.append('\n.TQ') + else: + self.body.append('\n') + + def visit_tgroup(self, node): + pass + + def depart_tgroup(self, node): + pass + + def visit_thead(self, node): + # MAYBE double line '=' + pass + + def depart_thead(self, node): + # MAYBE double line '=' + pass + + def visit_tip(self, node): + self.visit_admonition(node, 'tip') + + depart_tip = depart_admonition + + def visit_title(self, node): + if isinstance(node.parent, nodes.topic): + self.body.append(self.defs['topic-title'][0]) + elif isinstance(node.parent, nodes.sidebar): + self.body.append(self.defs['sidebar-title'][0]) + elif isinstance(node.parent, nodes.admonition): + self.body.append('.IP "') + elif self.section_level == 0: + self._docinfo['title'] = node.astext() + # document title for .TH + self._docinfo['title_upper'] = node.astext().upper() + raise nodes.SkipNode + elif self.section_level == 1: + self.body.append('.SH %s\n'%self.deunicode(node.astext().upper())) + raise nodes.SkipNode + else: + self.body.append('.SS ') + + def depart_title(self, node): + if isinstance(node.parent, nodes.admonition): + self.body.append('"') + self.body.append('\n') + + def visit_title_reference(self, node): + """inline citation reference""" + self.body.append(self.defs['title_reference'][0]) + + def depart_title_reference(self, node): + self.body.append(self.defs['title_reference'][1]) + + def visit_topic(self, node): + pass + + def depart_topic(self, node): + pass + + def visit_sidebar(self, node): + pass + + def depart_sidebar(self, node): + pass + + def visit_rubric(self, node): + pass + + def depart_rubric(self, node): + self.body.append('\n') + + def visit_transition(self, node): + # .PP Begin a new paragraph and reset prevailing indent. + # .sp N leaves N lines of blank space. + # .ce centers the next line + self.body.append('\n.sp\n.ce\n----\n') + + def depart_transition(self, node): + self.body.append('\n.ce 0\n.sp\n') + + def visit_version(self, node): + self.visit_docinfo_item(node, 'version') + + def visit_warning(self, node): + self.visit_admonition(node, 'warning') + + depart_warning = depart_admonition + + def unimplemented_visit(self, node): + raise NotImplementedError('visiting unimplemented node type: %s' + % node.__class__.__name__) + +# vim: set fileencoding=utf-8 et ts=4 ai : diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/null.py b/.venv/lib/python3.12/site-packages/docutils/writers/null.py new file mode 100644 index 00000000..db4c6720 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/null.py @@ -0,0 +1,25 @@ +# $Id: null.py 9352 2023-04-17 20:26:41Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +A do-nothing Writer. + +`self.output` will change from ``None`` to the empty string +in Docutils 0.22. +""" + +from docutils import writers + + +class Writer(writers.UnfilteredWriter): + + supported = ('null',) + """Formats this writer supports.""" + + config_section = 'null writer' + config_section_dependencies = ('writers',) + + def translate(self): + # output = None # TODO in 0.22 + pass 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("&", "&") + text = text.replace("<", "<") + text = text.replace(">", ">") + 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(" ", "\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 Binary files differnew file mode 100644 index 00000000..e17b0072 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/odf_odt/styles.odt diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/__init__.py new file mode 100644 index 00000000..dfde2e47 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/__init__.py @@ -0,0 +1,101 @@ +# $Id: __init__.py 9541 2024-02-17 10:37:13Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +PEP HTML Writer. +""" + +__docformat__ = 'reStructuredText' + + +import os +import os.path + +from docutils import frontend, nodes, utils +from docutils.writers import html4css1 + + +class Writer(html4css1.Writer): + + default_stylesheet = 'pep.css' + + 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 = html4css1.Writer.settings_spec + ( + 'PEP/HTML Writer Options', + 'For the PEP/HTML writer, the default value for the --stylesheet-path ' + 'option is "%s", and the default value for --template is "%s". ' + 'See HTML Writer Options above.' + % (default_stylesheet_path, default_template_path), + (('Python\'s home URL. Default is "https://www.python.org".', + ['--python-home'], + {'default': 'https://www.python.org', 'metavar': '<URL>'}), + ('Home URL prefix for PEPs. Default is "." (current directory).', + ['--pep-home'], + {'default': '.', 'metavar': '<URL>'}), + # For testing. + (frontend.SUPPRESS_HELP, + ['--no-random'], + {'action': 'store_true', 'validator': frontend.validate_boolean}),)) + + settings_default_overrides = {'stylesheet_path': default_stylesheet_path, + 'template': default_template_path} + relative_path_settings = ('template',) + config_section = 'pep_html writer' + config_section_dependencies = ('writers', 'html writers', + 'html4css1 writer') + + def __init__(self): + html4css1.Writer.__init__(self) + self.translator_class = HTMLTranslator + + def interpolation_dict(self): + subs = html4css1.Writer.interpolation_dict(self) + settings = self.document.settings + pyhome = settings.python_home + subs['pyhome'] = pyhome + subs['pephome'] = settings.pep_home + if pyhome == '..': + subs['pepindex'] = '.' + else: + subs['pepindex'] = pyhome + '/dev/peps' + index = self.document.first_child_matching_class(nodes.field_list) + header = self.document[index] + self.pepnum = header[0][1].astext() + subs['pep'] = self.pepnum + if settings.no_random: + subs['banner'] = 0 + else: + import random + subs['banner'] = random.randrange(64) + try: + subs['pepnum'] = '%04i' % int(self.pepnum) + except ValueError: + subs['pepnum'] = self.pepnum + self.title = header[1][1].astext() + subs['title'] = self.title + subs['body'] = ''.join( + self.body_pre_docinfo + self.docinfo + self.body) + return subs + + def assemble_parts(self): + html4css1.Writer.assemble_parts(self) + self.parts['title'] = [self.title] + self.parts['pepnum'] = self.pepnum + + +class HTMLTranslator(html4css1.HTMLTranslator): + + def depart_field_list(self, node): + html4css1.HTMLTranslator.depart_field_list(self, node) + if 'rfc2822' in node['classes']: + self.body.append('<hr />\n') diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/pep.css b/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/pep.css new file mode 100644 index 00000000..5231cd1a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/pep.css @@ -0,0 +1,344 @@ +/* +:Author: David Goodger +:Contact: goodger@python.org +:date: $Date: 2022-01-29 23:26:10 +0100 (Sa, 29. Jän 2022) $ +:version: $Revision: 8995 $ +:copyright: This stylesheet has been placed in the public domain. + +Default cascading style sheet for the PEP HTML output of Docutils. +*/ + +/* "! important" is used here to override other ``margin-top`` and + ``margin-bottom`` styles that are later in the stylesheet or + more specific. See http://www.w3.org/TR/CSS1#the-cascade */ +.first { + margin-top: 0 ! important } + +.last, .with-subtitle { + margin-bottom: 0 ! important } + +.hidden { + display: none } + +.navigation { + width: 100% ; + background: #99ccff ; + margin-top: 0px ; + margin-bottom: 0px } + +.navigation .navicon { + width: 150px ; + height: 35px } + +.navigation .textlinks { + padding-left: 1em ; + text-align: left } + +.navigation td, .navigation th { + padding-left: 0em ; + padding-right: 0em ; + vertical-align: middle } + +.rfc2822 { + margin-top: 0.5em ; + margin-left: 0.5em ; + margin-right: 0.5em ; + margin-bottom: 0em } + +.rfc2822 td { + text-align: left } + +.rfc2822 th.field-name { + text-align: right ; + font-family: sans-serif ; + padding-right: 0.5em ; + font-weight: bold ; + margin-bottom: 0em } + +a.toc-backref { + text-decoration: none ; + color: black } + +blockquote.epigraph { + margin: 2em 5em ; } + +body { + margin: 0px ; + margin-bottom: 1em ; + padding: 0px } + +dl.docutils dd { + margin-bottom: 0.5em } + +div.section { + margin-left: 1em ; + margin-right: 1em ; + margin-bottom: 1.5em } + +div.section div.section { + margin-left: 0em ; + margin-right: 0em ; + margin-top: 1.5em } + +div.abstract { + margin: 2em 5em } + +div.abstract p.topic-title { + font-weight: bold ; + text-align: center } + +div.admonition, div.attention, div.caution, div.danger, div.error, +div.hint, div.important, div.note, div.tip, div.warning { + margin: 2em ; + border: medium outset ; + padding: 1em } + +div.admonition p.admonition-title, div.hint p.admonition-title, +div.important p.admonition-title, div.note p.admonition-title, +div.tip p.admonition-title { + font-weight: bold ; + font-family: sans-serif } + +div.attention p.admonition-title, div.caution p.admonition-title, +div.danger p.admonition-title, div.error p.admonition-title, +div.warning p.admonition-title { + color: red ; + font-weight: bold ; + font-family: sans-serif } + +/* Uncomment (and remove this text!) to get reduced vertical space in + compound paragraphs. +div.compound .compound-first, div.compound .compound-middle { + margin-bottom: 0.5em } + +div.compound .compound-last, div.compound .compound-middle { + margin-top: 0.5em } +*/ + +div.dedication { + margin: 2em 5em ; + text-align: center ; + font-style: italic } + +div.dedication p.topic-title { + font-weight: bold ; + font-style: normal } + +div.figure { + margin-left: 2em ; + margin-right: 2em } + +div.footer, div.header { + clear: both; + font-size: smaller } + +div.footer { + margin-left: 1em ; + margin-right: 1em } + +div.line-block { + display: block ; + margin-top: 1em ; + margin-bottom: 1em } + +div.line-block div.line-block { + margin-top: 0 ; + margin-bottom: 0 ; + margin-left: 1.5em } + +div.sidebar { + margin-left: 1em ; + border: medium outset ; + padding: 1em ; + background-color: #ffffee ; + width: 40% ; + float: right ; + clear: right } + +div.sidebar p.rubric { + font-family: sans-serif ; + font-size: medium } + +div.system-messages { + margin: 5em } + +div.system-messages h1 { + color: red } + +div.system-message { + border: medium outset ; + padding: 1em } + +div.system-message p.system-message-title { + color: red ; + font-weight: bold } + +div.topic { + margin: 2em } + +h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, +h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { + margin-top: 0.4em } + +h1 { + font-family: sans-serif ; + font-size: large } + +h2 { + font-family: sans-serif ; + font-size: medium } + +h3 { + font-family: sans-serif ; + font-size: small } + +h4 { + font-family: sans-serif ; + font-style: italic ; + font-size: small } + +h5 { + font-family: sans-serif; + font-size: x-small } + +h6 { + font-family: sans-serif; + font-style: italic ; + font-size: x-small } + +hr.docutils { + width: 75% } + +img.align-left { + clear: left } + +img.align-right { + clear: right } + +img.borderless { + border: 0 } + +ol.simple, ul.simple { + margin-bottom: 1em } + +ol.arabic { + list-style: decimal } + +ol.loweralpha { + list-style: lower-alpha } + +ol.upperalpha { + list-style: upper-alpha } + +ol.lowerroman { + list-style: lower-roman } + +ol.upperroman { + list-style: upper-roman } + +p.attribution { + text-align: right ; + margin-left: 50% } + +p.caption { + font-style: italic } + +p.credits { + font-style: italic ; + font-size: smaller } + +p.label { + white-space: nowrap } + +p.rubric { + font-weight: bold ; + font-size: larger ; + color: maroon ; + text-align: center } + +p.sidebar-title { + font-family: sans-serif ; + font-weight: bold ; + font-size: larger } + +p.sidebar-subtitle { + font-family: sans-serif ; + font-weight: bold } + +p.topic-title { + font-family: sans-serif ; + font-weight: bold } + +pre.address { + margin-bottom: 0 ; + margin-top: 0 ; + font-family: serif ; + font-size: 100% } + +pre.literal-block, pre.doctest-block { + margin-left: 2em ; + margin-right: 2em } + +span.classifier { + font-family: sans-serif ; + font-style: oblique } + +span.classifier-delimiter { + font-family: sans-serif ; + font-weight: bold } + +span.interpreted { + font-family: sans-serif } + +span.option { + white-space: nowrap } + +span.option-argument { + font-style: italic } + +span.pre { + white-space: pre } + +span.problematic { + color: red } + +span.section-subtitle { + /* font-size relative to parent (h1..h6 element) */ + font-size: 80% } + +table.citation { + border-left: solid 1px gray; + margin-left: 1px } + +table.docinfo { + margin: 2em 4em } + +table.docutils { + margin-top: 0.5em ; + margin-bottom: 0.5em } + +table.footnote { + border-left: solid 1px black; + margin-left: 1px } + +table.docutils td, table.docutils th, +table.docinfo td, table.docinfo th { + padding-left: 0.5em ; + padding-right: 0.5em ; + vertical-align: top } + +td.num { + text-align: right } + +th.field-name { + font-weight: bold ; + text-align: left ; + white-space: nowrap ; + padding-left: 0 } + +h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, +h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { + font-size: 100% } + +ul.auto-toc { + list-style-type: none } diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/template.txt b/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/template.txt new file mode 100644 index 00000000..e8cd351c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/pep_html/template.txt @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="%(encoding)s"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<!-- +This HTML is auto-generated. DO NOT EDIT THIS FILE! If you are writing a new +PEP, see http://peps.python.org/pep-0001 for instructions and links +to templates. DO NOT USE THIS HTML FILE AS YOUR TEMPLATE! +--> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=%(encoding)s" /> + <meta name="generator" content="Docutils %(version)s: https://docutils.sourceforge.io/" /> + <title>PEP %(pep)s - %(title)s</title> + %(stylesheet)s +</head> +<body bgcolor="white"> +<div class="header"> +<strong>Python Enhancement Proposals</strong> +| <a href="%(pyhome)s/">Python</a> +» <a href="https://peps.python.org/pep-0000/">PEP Index</a> +» PEP %(pep)s – %(title)s +<hr class="header"/> +</div> +<div class="document"> +%(body)s +%(body_suffix)s diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/pseudoxml.py b/.venv/lib/python3.12/site-packages/docutils/writers/pseudoxml.py new file mode 100644 index 00000000..0e238a88 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/pseudoxml.py @@ -0,0 +1,40 @@ +# $Id: pseudoxml.py 9043 2022-03-11 12:09:16Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Simple internal document tree Writer, writes indented pseudo-XML. +""" + +__docformat__ = 'reStructuredText' + + +from docutils import writers, frontend + + +class Writer(writers.Writer): + + supported = ('pseudoxml', 'pprint', 'pformat') + """Formats this writer supports.""" + + settings_spec = ( + '"Docutils pseudo-XML" Writer Options', + None, + (('Pretty-print <#text> nodes.', + ['--detailed'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ) + ) + + config_section = 'pseudoxml writer' + config_section_dependencies = ('writers',) + + output = None + """Final translated form of `document`.""" + + def translate(self): + self.output = self.document.pformat() + + def supports(self, format): + """This writer supports all format-specific elements.""" + return True diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/__init__.py new file mode 100644 index 00000000..7014de33 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/__init__.py @@ -0,0 +1,353 @@ +# $Id: __init__.py 9542 2024-02-17 10:37:23Z milde $ +# Authors: Chris Liechti <cliechti@gmx.net>; +# David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +S5/HTML Slideshow Writer. +""" + +__docformat__ = 'reStructuredText' + +import sys +import os +import re +import docutils +from docutils import frontend, nodes, utils +from docutils.writers import html4css1 + +themes_dir_path = utils.relative_path( + os.path.join(os.getcwd(), 'dummy'), + os.path.join(os.path.dirname(__file__), 'themes')) + + +def find_theme(name): + # Where else to look for a theme? + # Check working dir? Destination dir? Config dir? Plugins dir? + path = os.path.join(themes_dir_path, name) + if not os.path.isdir(path): + raise docutils.ApplicationError( + 'Theme directory not found: %r (path: %r)' % (name, path)) + return path + + +class Writer(html4css1.Writer): + + settings_spec = html4css1.Writer.settings_spec + ( + 'S5 Slideshow Specific Options', + 'For the S5/HTML writer, the --no-toc-backlinks option ' + '(defined in General Docutils Options above) is the default, ' + 'and should not be changed.', + (('Specify an installed S5 theme by name. Overrides --theme-url. ' + 'The default theme name is "default". The theme files will be ' + 'copied into a "ui/<theme>" directory, in the same directory as the ' + 'destination file (output HTML). Note that existing theme files ' + 'will not be overwritten (unless --overwrite-theme-files is used).', + ['--theme'], + {'default': 'default', 'metavar': '<name>', + 'overrides': 'theme_url'}), + ('Specify an S5 theme URL. The destination file (output HTML) will ' + 'link to this theme; nothing will be copied. Overrides --theme.', + ['--theme-url'], + {'metavar': '<URL>', 'overrides': 'theme'}), + ('Allow existing theme files in the ``ui/<theme>`` directory to be ' + 'overwritten. The default is not to overwrite theme files.', + ['--overwrite-theme-files'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Keep existing theme files in the ``ui/<theme>`` directory; do not ' + 'overwrite any. This is the default.', + ['--keep-theme-files'], + {'dest': 'overwrite_theme_files', 'action': 'store_false'}), + ('Set the initial view mode to "slideshow" [default] or "outline".', + ['--view-mode'], + {'choices': ['slideshow', 'outline'], 'default': 'slideshow', + 'metavar': '<mode>'}), + ('Normally hide the presentation controls in slideshow mode. ' + 'This is the default.', + ['--hidden-controls'], + {'action': 'store_true', 'default': True, + 'validator': frontend.validate_boolean}), + ('Always show the presentation controls in slideshow mode. ' + 'The default is to hide the controls.', + ['--visible-controls'], + {'dest': 'hidden_controls', 'action': 'store_false'}), + ('Enable the current slide indicator ("1 / 15"). ' + 'The default is to disable it.', + ['--current-slide'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Disable the current slide indicator. This is the default.', + ['--no-current-slide'], + {'dest': 'current_slide', 'action': 'store_false'}),)) + + settings_default_overrides = {'toc_backlinks': 0} + + config_section = 's5_html writer' + config_section_dependencies = ('writers', 'html writers', + 'html4css1 writer') + + def __init__(self): + html4css1.Writer.__init__(self) + self.translator_class = S5HTMLTranslator + + +class S5HTMLTranslator(html4css1.HTMLTranslator): + + s5_stylesheet_template = """\ +<!-- configuration parameters --> +<meta name="defaultView" content="%(view_mode)s" /> +<meta name="controlVis" content="%(control_visibility)s" /> +<!-- style sheet links --> +<script src="%(path)s/slides.js" type="text/javascript"></script> +<link rel="stylesheet" href="%(path)s/slides.css" + type="text/css" media="projection" id="slideProj" /> +<link rel="stylesheet" href="%(path)s/outline.css" + type="text/css" media="screen" id="outlineStyle" /> +<link rel="stylesheet" href="%(path)s/print.css" + type="text/css" media="print" id="slidePrint" /> +<link rel="stylesheet" href="%(path)s/opera.css" + type="text/css" media="projection" id="operaFix" />\n""" + # The script element must go in front of the link elements to + # avoid a flash of unstyled content (FOUC), reproducible with + # Firefox. + + disable_current_slide = """ +<style type="text/css"> +#currentSlide {display: none;} +</style>\n""" + + layout_template = """\ +<div class="layout"> +<div id="controls"></div> +<div id="currentSlide"></div> +<div id="header"> +%(header)s +</div> +<div id="footer"> +%(title)s%(footer)s +</div> +</div>\n""" +# <div class="topleft"></div> +# <div class="topright"></div> +# <div class="bottomleft"></div> +# <div class="bottomright"></div> + + default_theme = 'default' + """Name of the default theme.""" + + base_theme_file = '__base__' + """Name of the file containing the name of the base theme.""" + + direct_theme_files = ( + 'slides.css', 'outline.css', 'print.css', 'opera.css', 'slides.js') + """Names of theme files directly linked to in the output HTML""" + + indirect_theme_files = ( + 's5-core.css', 'framing.css', 'pretty.css') + """Names of files used indirectly; imported or used by files in + `direct_theme_files`.""" + + required_theme_files = indirect_theme_files + direct_theme_files + """Names of mandatory theme files.""" + + def __init__(self, *args): + html4css1.HTMLTranslator.__init__(self, *args) + # insert S5-specific stylesheet and script stuff: + self.theme_file_path = None + try: + self.setup_theme() + except docutils.ApplicationError as e: + self.document.reporter.warning(e) + view_mode = self.document.settings.view_mode + control_visibility = ('visible', 'hidden')[self.document.settings + .hidden_controls] + self.stylesheet.append(self.s5_stylesheet_template + % {'path': self.theme_file_path, + 'view_mode': view_mode, + 'control_visibility': control_visibility}) + if not self.document.settings.current_slide: + self.stylesheet.append(self.disable_current_slide) + self.meta.append('<meta name="version" content="S5 1.1" />\n') + self.s5_footer = [] + self.s5_header = [] + self.section_count = 0 + self.theme_files_copied = None + + def setup_theme(self): + if self.document.settings.theme: + self.copy_theme() + elif self.document.settings.theme_url: + self.theme_file_path = self.document.settings.theme_url + else: + raise docutils.ApplicationError( + 'No theme specified for S5/HTML writer.') + + def copy_theme(self): + """ + Locate & copy theme files. + + A theme may be explicitly based on another theme via a '__base__' + file. The default base theme is 'default'. Files are accumulated + from the specified theme, any base themes, and 'default'. + """ + settings = self.document.settings + path = find_theme(settings.theme) + theme_paths = [path] + self.theme_files_copied = {} + required_files_copied = {} + # This is a link (URL) in HTML, so we use "/", not os.sep: + self.theme_file_path = 'ui/%s' % settings.theme + if not settings.output: + raise docutils.ApplicationError( + 'Output path not specified, you may need to copy' + ' the S5 theme files "by hand" or set the "--output" option.') + dest = os.path.join( + os.path.dirname(settings.output), 'ui', settings.theme) + if not os.path.isdir(dest): + os.makedirs(dest) + default = False + while path: + for f in os.listdir(path): # copy all files from each theme + if f == self.base_theme_file: + continue # ... except the "__base__" file + if (self.copy_file(f, path, dest) + and f in self.required_theme_files): + required_files_copied[f] = True + if default: + break # "default" theme has no base theme + # Find the "__base__" file in theme directory: + base_theme_file = os.path.join(path, self.base_theme_file) + # If it exists, read it and record the theme path: + if os.path.isfile(base_theme_file): + with open(base_theme_file, encoding='utf-8') as f: + lines = f.readlines() + for line in lines: + line = line.strip() + if line and not line.startswith('#'): + path = find_theme(line) + if path in theme_paths: # check for duplicates/cycles + path = None # if found, use default base + else: + theme_paths.append(path) + break + else: # no theme name found + path = None # use default base + else: # no base theme file found + path = None # use default base + if not path: + path = find_theme(self.default_theme) + theme_paths.append(path) + default = True + if len(required_files_copied) != len(self.required_theme_files): + # Some required files weren't found & couldn't be copied. + required = list(self.required_theme_files) + for f in required_files_copied.keys(): + required.remove(f) + raise docutils.ApplicationError( + 'Theme files not found: %s' + % ', '.join('%r' % f for f in required)) + + files_to_skip_pattern = re.compile(r'~$|\.bak$|#$|\.cvsignore$') + + def copy_file(self, name, source_dir, dest_dir): + """ + Copy file `name` from `source_dir` to `dest_dir`. + Return True if the file exists in either `source_dir` or `dest_dir`. + """ + source = os.path.join(source_dir, name) + dest = os.path.join(dest_dir, name) + if dest in self.theme_files_copied: + return True + else: + self.theme_files_copied[dest] = True + if os.path.isfile(source): + if self.files_to_skip_pattern.search(source): + return None + settings = self.document.settings + if os.path.exists(dest) and not settings.overwrite_theme_files: + settings.record_dependencies.add(dest) + else: + with open(source, 'rb') as src_file: + src_data = src_file.read() + with open(dest, 'wb') as dest_file: + dest_dir = dest_dir.replace(os.sep, '/') + dest_file.write(src_data.replace( + b'ui/default', + dest_dir[dest_dir.rfind('ui/'):].encode( + sys.getfilesystemencoding()))) + settings.record_dependencies.add(source) + return True + if os.path.isfile(dest): + return True + + def depart_document(self, node): + self.head_prefix.extend([self.doctype, + self.head_prefix_template % + {'lang': self.settings.language_code}]) + self.html_prolog.append(self.doctype) + self.head = self.meta[:] + self.head + if self.math_header: + if self.math_output == 'mathjax': + self.head.extend(self.math_header) + else: + self.stylesheet.extend(self.math_header) + # skip content-type meta tag with interpolated charset value: + self.html_head.extend(self.head[1:]) + self.fragment.extend(self.body) + # special S5 code up to the next comment line + header = ''.join(self.s5_header) + footer = ''.join(self.s5_footer) + title = ''.join(self.html_title).replace('<h1 class="title">', '<h1>') + layout = self.layout_template % {'header': header, + 'title': title, + 'footer': footer} + self.body_prefix.extend(layout) + self.body_prefix.append('<div class="presentation">\n') + self.body_prefix.append( + self.starttag({'classes': ['slide'], 'ids': ['slide0']}, 'div')) + if not self.section_count: + self.body.append('</div>\n') + # + self.body_suffix.insert(0, '</div>\n') + self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo + + self.docinfo + self.body + + self.body_suffix[:-1]) + + def depart_footer(self, node): + start = self.context.pop() + self.s5_footer.append('<h2>') + self.s5_footer.extend(self.body[start:]) + self.s5_footer.append('</h2>') + del self.body[start:] + + def depart_header(self, node): + start = self.context.pop() + header = ['<div id="header">\n'] + header.extend(self.body[start:]) + header.append('\n</div>\n') + del self.body[start:] + self.s5_header.extend(header) + + def visit_section(self, node): + if not self.section_count: + self.body.append('\n</div>\n') + self.section_count += 1 + self.section_level += 1 + if self.section_level > 1: + # dummy for matching div's + self.body.append(self.starttag(node, 'div', CLASS='section')) + else: + self.body.append(self.starttag(node, 'div', CLASS='slide')) + + def visit_subtitle(self, node): + if isinstance(node.parent, nodes.section): + level = self.section_level + self.initial_header_level - 1 + if level == 1: + level = 2 + tag = 'h%s' % level + self.body.append(self.starttag(node, tag, '')) + self.context.append('</%s>\n' % tag) + else: + html4css1.HTMLTranslator.visit_subtitle(self, node) + + def visit_title(self, node): + html4css1.HTMLTranslator.visit_title(self, node) diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/README.txt b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/README.txt new file mode 100644 index 00000000..605d08f5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/README.txt @@ -0,0 +1,6 @@ +Except where otherwise noted, all files in this +directory have been released into the Public Domain. + +These files are based on files from S5 1.1, released into the Public +Domain by Eric Meyer. For further details, please see +http://www.meyerweb.com/eric/tools/s5/credits.html. diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/__base__ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/__base__ new file mode 100644 index 00000000..f08be9ad --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/__base__ @@ -0,0 +1,2 @@ +# base theme of this theme: +big-white diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/framing.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/framing.css new file mode 100644 index 00000000..a945abbc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/framing.css @@ -0,0 +1,25 @@ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#header {top: 0; z-index: 1;} +div#footer {display:none;} +.slide {top: 0; width: 92%; padding: 0.1em 4% 4%; z-index: 2;} +/* list-style: none;} */ +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/pretty.css new file mode 100644 index 00000000..85f07cf0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-black/pretty.css @@ -0,0 +1,109 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: black; color: white;} +:link, :visited {text-decoration: none; color: cyan;} +#controls :active {color: #888 !important;} +#controls :focus {outline: 1px dotted #CCC;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +.slide {font-size: 3em; font-family: sans-serif; font-weight: bold;} +.slide h1 {padding-top: 0; z-index: 1; margin: 0; font-size: 120%;} +.slide h2 {font-size: 110%;} +.slide h3 {font-size: 105%;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #888; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: black; color: #CCC;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #AAA; + font-family: sans-serif; font-weight: bold;} + +#slide0 h1 {position: static; margin: 0 0 0.5em; padding-top: 0.3em; top: 0; + font-size: 150%; white-space: normal; background: transparent;} +#slide0 h2 {font: 110%; font-style: italic; color: gray;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #FCC;} + +.incremental, .incremental *, .incremental *:after { + color: black; visibility: visible; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: lime;} + +.slide-display {display: inline ! important;} + +.huge {font-size: 150%;} +.big {font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 0.5em 0 0.5em 1em;} + +div.sidebar {background-color: black;} + +pre.literal-block, pre.doctest-block {background-color: black;} + +tt.docutils {background-color: black;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-white/framing.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-white/framing.css new file mode 100644 index 00000000..45f123f3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-white/framing.css @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#footer {display:none;} +.slide {top: 0; width: 92%; padding: 0.25em 4% 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-white/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-white/pretty.css new file mode 100644 index 00000000..68fe863a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/big-white/pretty.css @@ -0,0 +1,107 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: white; color: black;} +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #88A !important;} +#controls :focus {outline: 1px dotted #227;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +.slide {font-size: 3em; font-family: sans-serif; font-weight: bold;} +.slide h1 {padding-top: 0; z-index: 1; margin: 0; font-size: 120%;} +.slide h2 {font-size: 110%;} +.slide h3 {font-size: 105%;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #005; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #227;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #444; + font-family: sans-serif; font-weight: bold;} + +#slide0 h1 {position: static; margin: 0 0 0.5em; padding-top: 0.3em; top: 0; + font-size: 150%; white-space: normal; background: transparent;} +#slide0 h2 {font: 110%; font-style: italic; color: gray;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after { + color: white; visibility: visible; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-size: 150%;} +.big {font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 0.5em 0 0.5em 1em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/framing.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/framing.css new file mode 100644 index 00000000..b19b1f04 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/framing.css @@ -0,0 +1,25 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#header {position: fixed; top: 0; height: 3em; z-index: 1;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 2.5em 4% 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/opera.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/opera.css new file mode 100644 index 00000000..c9d1148b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/opera.css @@ -0,0 +1,8 @@ +/* This file has been placed in the public domain. */ +/* DO NOT CHANGE THESE unless you really want to break Opera Show */ +.slide { + visibility: visible !important; + position: static !important; + page-break-before: always; +} +#slide0 {page-break-before: avoid;} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/outline.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/outline.css new file mode 100644 index 00000000..fa767e22 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/outline.css @@ -0,0 +1,16 @@ +/* This file has been placed in the public domain. */ +/* Don't change this unless you want the layout stuff to show up in the + outline view! */ + +.layout div, #footer *, #controlForm * {display: none;} +#footer, #controls, #controlForm, #navLinks, #toggle { + display: block; visibility: visible; margin: 0; padding: 0;} +#toggle {float: right; padding: 0.5em;} +html>body #toggle {position: fixed; top: 0; right: 0;} + +/* making the outline look pretty-ish */ + +#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;} +#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;} + +.outline {display: inline ! important;} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/pretty.css new file mode 100644 index 00000000..7d48fff5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/pretty.css @@ -0,0 +1,120 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: white; color: black;} +/* Replace the background style above with the style below (and again for + div#header) for a graphic: */ +/* background: white url(bodybg.gif) -16px 0 no-repeat; */ +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #88A !important;} +#controls :focus {outline: 1px dotted #227;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#header, div#footer {background: #005; color: #AAB; font-family: sans-serif;} +/* background: #005 url(bodybg.gif) -16px 0 no-repeat; */ +div#footer {font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.2em;} +.slide h1 {position: absolute; top: 0.45em; z-index: 1; + margin: 0; padding-left: 0.7em; white-space: nowrap; + font: bold 150% sans-serif; color: #DDE; background: #005;} +.slide h2 {font: bold 120%/1em sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 100% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + background: #005; border: none; color: #779; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #227;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #449; + font-family: sans-serif; font-weight: bold;} + +#slide0 {padding-top: 1.5em} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; color: #000; + font: bold 2em sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 1em sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after {visibility: visible; + color: white; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 1em 0 0.5em 2em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/print.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/print.css new file mode 100644 index 00000000..9d057cc8 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/print.css @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following rule is necessary to have all slides appear in print! + DO NOT REMOVE IT! */ +.slide, ul {page-break-inside: avoid; visibility: visible !important;} +h1 {page-break-after: avoid;} + +body {font-size: 12pt; background: white;} +* {color: black;} + +#slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;} +#slide0 h3 {margin: 0; padding: 0;} +#slide0 h4 {margin: 0 0 0.5em; padding: 0;} +#slide0 {margin-bottom: 3em;} + +#header {display: none;} +#footer h1 {margin: 0; border-bottom: 1px solid; color: gray; + font-style: italic;} +#footer h2, #controls {display: none;} + +.print {display: inline ! important;} + +/* The following rule keeps the layout stuff out of print. + Remove at your own risk! */ +.layout, .layout * {display: none !important;} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/s5-core.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/s5-core.css new file mode 100644 index 00000000..62e1b7b1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/s5-core.css @@ -0,0 +1,11 @@ +/* This file has been placed in the public domain. */ +/* Do not edit or override these styles! + The system will likely break if you do. */ + +div#header, div#footer, div#controls, .slide {position: absolute;} +html>body div#header, html>body div#footer, + html>body div#controls, html>body .slide {position: fixed;} +.handout {display: none;} +.layout {display: block;} +.slide, .hideme, .incremental {visibility: hidden;} +#slide0 {visibility: visible;} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/slides.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/slides.css new file mode 100644 index 00000000..82bdc0ee --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/slides.css @@ -0,0 +1,10 @@ +/* This file has been placed in the public domain. */ + +/* required to make the slide show run at all */ +@import url(s5-core.css); + +/* sets basic placement and size of slide components */ +@import url(framing.css); + +/* styles that make the slides look good */ +@import url(pretty.css); diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/slides.js b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/slides.js new file mode 100644 index 00000000..cd0e0e43 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/default/slides.js @@ -0,0 +1,558 @@ +// S5 v1.1 slides.js -- released into the Public Domain +// Modified for Docutils (https://docutils.sourceforge.io) by David Goodger +// +// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for +// information about all the wonderful and talented contributors to this code! + +var undef; +var slideCSS = ''; +var snum = 0; +var smax = 1; +var slideIDs = new Array(); +var incpos = 0; +var number = undef; +var s5mode = true; +var defaultView = 'slideshow'; +var controlVis = 'visible'; + +var isIE = navigator.appName == 'Microsoft Internet Explorer' ? 1 : 0; +var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0; +var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0; + +function hasClass(object, className) { + if (!object.className) return false; + return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1); +} + +function hasValue(object, value) { + if (!object) return false; + return (object.search('(^|\\s)' + value + '(\\s|$)') != -1); +} + +function removeClass(object,className) { + if (!object) return; + object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2); +} + +function addClass(object,className) { + if (!object || hasClass(object, className)) return; + if (object.className) { + object.className += ' '+className; + } else { + object.className = className; + } +} + +function GetElementsWithClassName(elementName,className) { + var allElements = document.getElementsByTagName(elementName); + var elemColl = new Array(); + for (var i = 0; i< allElements.length; i++) { + if (hasClass(allElements[i], className)) { + elemColl[elemColl.length] = allElements[i]; + } + } + return elemColl; +} + +function isParentOrSelf(element, id) { + if (element == null || element.nodeName=='BODY') return false; + else if (element.id == id) return true; + else return isParentOrSelf(element.parentNode, id); +} + +function nodeValue(node) { + var result = ""; + if (node.nodeType == 1) { + var children = node.childNodes; + for (var i = 0; i < children.length; ++i) { + result += nodeValue(children[i]); + } + } + else if (node.nodeType == 3) { + result = node.nodeValue; + } + return(result); +} + +function slideLabel() { + var slideColl = GetElementsWithClassName('*','slide'); + var list = document.getElementById('jumplist'); + smax = slideColl.length; + for (var n = 0; n < smax; n++) { + var obj = slideColl[n]; + + var did = 'slide' + n.toString(); + if (obj.getAttribute('id')) { + slideIDs[n] = obj.getAttribute('id'); + } + else { + obj.setAttribute('id',did); + slideIDs[n] = did; + } + if (isOp) continue; + + var otext = ''; + var menu = obj.firstChild; + if (!menu) continue; // to cope with empty slides + while (menu && menu.nodeType == 3) { + menu = menu.nextSibling; + } + if (!menu) continue; // to cope with slides with only text nodes + + var menunodes = menu.childNodes; + for (var o = 0; o < menunodes.length; o++) { + otext += nodeValue(menunodes[o]); + } + list.options[list.length] = new Option(n + ' : ' + otext, n); + } +} + +function currentSlide() { + var cs; + var footer_nodes; + var vis = 'visible'; + if (document.getElementById) { + cs = document.getElementById('currentSlide'); + footer_nodes = document.getElementById('footer').childNodes; + } else { + cs = document.currentSlide; + footer = document.footer.childNodes; + } + cs.innerHTML = '<span id="csHere">' + snum + '<\/span> ' + + '<span id="csSep">\/<\/span> ' + + '<span id="csTotal">' + (smax-1) + '<\/span>'; + if (snum == 0) { + vis = 'hidden'; + } + cs.style.visibility = vis; + for (var i = 0; i < footer_nodes.length; i++) { + if (footer_nodes[i].nodeType == 1) { + footer_nodes[i].style.visibility = vis; + } + } +} + +function go(step) { + if (document.getElementById('slideProj').disabled || step == 0) return; + var jl = document.getElementById('jumplist'); + var cid = slideIDs[snum]; + var ce = document.getElementById(cid); + if (incrementals[snum].length > 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + removeClass(incrementals[snum][i], 'current'); + removeClass(incrementals[snum][i], 'incremental'); + } + } + if (step != 'j') { + snum += step; + lmax = smax - 1; + if (snum > lmax) snum = lmax; + if (snum < 0) snum = 0; + } else + snum = parseInt(jl.value); + var nid = slideIDs[snum]; + var ne = document.getElementById(nid); + if (!ne) { + ne = document.getElementById(slideIDs[0]); + snum = 0; + } + if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;} + if (incrementals[snum].length > 0 && incpos == 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + if (hasClass(incrementals[snum][i], 'current')) + incpos = i + 1; + else + addClass(incrementals[snum][i], 'incremental'); + } + } + if (incrementals[snum].length > 0 && incpos > 0) + addClass(incrementals[snum][incpos - 1], 'current'); + ce.style.visibility = 'hidden'; + ne.style.visibility = 'visible'; + jl.selectedIndex = snum; + currentSlide(); + number = 0; +} + +function goTo(target) { + if (target >= smax || target == snum) return; + go(target - snum); +} + +function subgo(step) { + if (step > 0) { + removeClass(incrementals[snum][incpos - 1],'current'); + removeClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos],'current'); + incpos++; + } else { + incpos--; + removeClass(incrementals[snum][incpos],'current'); + addClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos - 1],'current'); + } +} + +function toggle() { + var slideColl = GetElementsWithClassName('*','slide'); + var slides = document.getElementById('slideProj'); + var outline = document.getElementById('outlineStyle'); + if (!slides.disabled) { + slides.disabled = true; + outline.disabled = false; + s5mode = false; + fontSize('1em'); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'visible'; + } + } else { + slides.disabled = false; + outline.disabled = true; + s5mode = true; + fontScale(); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'hidden'; + } + slideColl[snum].style.visibility = 'visible'; + } +} + +function showHide(action) { + var obj = GetElementsWithClassName('*','hideme')[0]; + switch (action) { + case 's': obj.style.visibility = 'visible'; break; + case 'h': obj.style.visibility = 'hidden'; break; + case 'k': + if (obj.style.visibility != 'visible') { + obj.style.visibility = 'visible'; + } else { + obj.style.visibility = 'hidden'; + } + break; + } +} + +// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/) +function keys(key) { + if (!key) { + key = event; + key.which = key.keyCode; + } + if (key.which == 84) { + toggle(); + return; + } + if (s5mode) { + switch (key.which) { + case 10: // return + case 13: // enter + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + if(number != undef) { + goTo(number); + break; + } + case 32: // spacebar + case 34: // page down + case 39: // rightkey + case 40: // downkey + if(number != undef) { + go(number); + } else if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + break; + case 33: // page up + case 37: // leftkey + case 38: // upkey + if(number != undef) { + go(-1 * number); + } else if (!incrementals[snum] || incpos <= 0) { + go(-1); + } else { + subgo(-1); + } + break; + case 36: // home + goTo(0); + break; + case 35: // end + goTo(smax-1); + break; + case 67: // c + showHide('k'); + break; + } + if (key.which < 48 || key.which > 57) { + number = undef; + } else { + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + number = (((number != undef) ? number : 0) * 10) + (key.which - 48); + } + } + return false; +} + +function clicker(e) { + number = undef; + var target; + if (window.event) { + target = window.event.srcElement; + e = window.event; + } else target = e.target; + if (target.href != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target, 'object')) return true; + if (!e.which || e.which == 1) { + if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + } +} + +function findSlide(hash) { + var target = document.getElementById(hash); + if (target) { + for (var i = 0; i < slideIDs.length; i++) { + if (target.id == slideIDs[i]) return i; + } + } + return null; +} + +function slideJump() { + if (window.location.hash == null || window.location.hash == '') { + currentSlide(); + return; + } + if (window.location.hash == null) return; + var dest = null; + dest = findSlide(window.location.hash.slice(1)); + if (dest == null) { + dest = 0; + } + go(dest - snum); +} + +function fixLinks() { + var thisUri = window.location.href; + thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length); + var aelements = document.getElementsByTagName('A'); + for (var i = 0; i < aelements.length; i++) { + var a = aelements[i].href; + var slideID = a.match('\#.+'); + if ((slideID) && (slideID[0].slice(0,1) == '#')) { + var dest = findSlide(slideID[0].slice(1)); + if (dest != null) { + if (aelements[i].addEventListener) { + aelements[i].addEventListener("click", new Function("e", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "if (e.preventDefault) e.preventDefault();"), true); + } else if (aelements[i].attachEvent) { + aelements[i].attachEvent("onclick", new Function("", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "event.returnValue = false;")); + } + } + } + } +} + +function externalLinks() { + if (!document.getElementsByTagName) return; + var anchors = document.getElementsByTagName('a'); + for (var i=0; i<anchors.length; i++) { + var anchor = anchors[i]; + if (anchor.getAttribute('href') && hasValue(anchor.rel, 'external')) { + anchor.target = '_blank'; + addClass(anchor,'external'); + } + } +} + +function createControls() { + var controlsDiv = document.getElementById("controls"); + if (!controlsDiv) return; + var hider = ' onmouseover="showHide(\'s\');" onmouseout="showHide(\'h\');"'; + var hideDiv, hideList = ''; + if (controlVis == 'hidden') { + hideDiv = hider; + } else { + hideList = hider; + } + controlsDiv.innerHTML = '<form action="#" id="controlForm"' + hideDiv + '>' + + '<div id="navLinks">' + + '<a accesskey="t" id="toggle" href="javascript:toggle();">Ø<\/a>' + + '<a accesskey="z" id="prev" href="javascript:go(-1);">«<\/a>' + + '<a accesskey="x" id="next" href="javascript:go(1);">»<\/a>' + + '<div id="navList"' + hideList + '><select id="jumplist" onchange="go(\'j\');"><\/select><\/div>' + + '<\/div><\/form>'; + if (controlVis == 'hidden') { + var hidden = document.getElementById('navLinks'); + } else { + var hidden = document.getElementById('jumplist'); + } + addClass(hidden,'hideme'); +} + +function fontScale() { // causes layout problems in FireFox that get fixed if browser's Reload is used; same may be true of other Gecko-based browsers + if (!s5mode) return false; + var vScale = 22; // both yield 32 (after rounding) at 1024x768 + var hScale = 32; // perhaps should auto-calculate based on theme's declared value? + if (window.innerHeight) { + var vSize = window.innerHeight; + var hSize = window.innerWidth; + } else if (document.documentElement.clientHeight) { + var vSize = document.documentElement.clientHeight; + var hSize = document.documentElement.clientWidth; + } else if (document.body.clientHeight) { + var vSize = document.body.clientHeight; + var hSize = document.body.clientWidth; + } else { + var vSize = 700; // assuming 1024x768, minus chrome and such + var hSize = 1024; // these do not account for kiosk mode or Opera Show + } + var newSize = Math.min(Math.round(vSize/vScale),Math.round(hSize/hScale)); + fontSize(newSize + 'px'); + if (isGe) { // hack to counter incremental reflow bugs + var obj = document.getElementsByTagName('body')[0]; + obj.style.display = 'none'; + obj.style.display = 'block'; + } +} + +function fontSize(value) { + if (!(s5ss = document.getElementById('s5ss'))) { + if (!isIE) { + document.getElementsByTagName('head')[0].appendChild(s5ss = document.createElement('style')); + s5ss.setAttribute('media','screen, projection'); + s5ss.setAttribute('id','s5ss'); + } else { + document.createStyleSheet(); + document.s5ss = document.styleSheets[document.styleSheets.length - 1]; + } + } + if (!isIE) { + while (s5ss.lastChild) s5ss.removeChild(s5ss.lastChild); + s5ss.appendChild(document.createTextNode('body {font-size: ' + value + ' !important;}')); + } else { + document.s5ss.addRule('body','font-size: ' + value + ' !important;'); + } +} + +function notOperaFix() { + slideCSS = document.getElementById('slideProj').href; + var slides = document.getElementById('slideProj'); + var outline = document.getElementById('outlineStyle'); + slides.setAttribute('media','screen'); + outline.disabled = true; + if (isGe) { + slides.setAttribute('href','null'); // Gecko fix + slides.setAttribute('href',slideCSS); // Gecko fix + } + if (isIE && document.styleSheets && document.styleSheets[0]) { + document.styleSheets[0].addRule('img', 'behavior: url(ui/default/iepngfix.htc)'); + document.styleSheets[0].addRule('div', 'behavior: url(ui/default/iepngfix.htc)'); + document.styleSheets[0].addRule('.slide', 'behavior: url(ui/default/iepngfix.htc)'); + } +} + +function getIncrementals(obj) { + var incrementals = new Array(); + if (!obj) + return incrementals; + var children = obj.childNodes; + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (hasClass(child, 'incremental')) { + if (child.nodeName == 'OL' || child.nodeName == 'UL') { + removeClass(child, 'incremental'); + for (var j = 0; j < child.childNodes.length; j++) { + if (child.childNodes[j].nodeType == 1) { + addClass(child.childNodes[j], 'incremental'); + } + } + } else { + incrementals[incrementals.length] = child; + removeClass(child,'incremental'); + } + } + if (hasClass(child, 'show-first')) { + if (child.nodeName == 'OL' || child.nodeName == 'UL') { + removeClass(child, 'show-first'); + if (child.childNodes[isGe].nodeType == 1) { + removeClass(child.childNodes[isGe], 'incremental'); + } + } else { + incrementals[incrementals.length] = child; + } + } + incrementals = incrementals.concat(getIncrementals(child)); + } + return incrementals; +} + +function createIncrementals() { + var incrementals = new Array(); + for (var i = 0; i < smax; i++) { + incrementals[i] = getIncrementals(document.getElementById(slideIDs[i])); + } + return incrementals; +} + +function defaultCheck() { + var allMetas = document.getElementsByTagName('meta'); + for (var i = 0; i< allMetas.length; i++) { + if (allMetas[i].name == 'defaultView') { + defaultView = allMetas[i].content; + } + if (allMetas[i].name == 'controlVis') { + controlVis = allMetas[i].content; + } + } +} + +// Key trap fix, new function body for trap() +function trap(e) { + if (!e) { + e = event; + e.which = e.keyCode; + } + try { + modifierKey = e.ctrlKey || e.altKey || e.metaKey; + } + catch(e) { + modifierKey = false; + } + return modifierKey || e.which == 0; +} + +function startup() { + defaultCheck(); + if (!isOp) createControls(); + slideLabel(); + fixLinks(); + externalLinks(); + fontScale(); + if (!isOp) { + notOperaFix(); + incrementals = createIncrementals(); + slideJump(); + if (defaultView == 'outline') { + toggle(); + } + document.onkeyup = keys; + document.onkeypress = trap; + document.onclick = clicker; + } +} + +window.onload = startup; +window.onresize = function(){setTimeout('fontScale()', 50);} diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-black/__base__ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-black/__base__ new file mode 100644 index 00000000..401b621b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-black/__base__ @@ -0,0 +1,2 @@ +# base theme of this theme: +medium-white diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-black/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-black/pretty.css new file mode 100644 index 00000000..81df4bc1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-black/pretty.css @@ -0,0 +1,115 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: black; color: white;} +:link, :visited {text-decoration: none; color: cyan;} +#controls :active {color: #888 !important;} +#controls :focus {outline: 1px dotted #CCC;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#footer {font-family: sans-serif; color: #AAA; + font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.75em;} +.slide h1 {padding-top: 0; z-index: 1; margin: 0; font: bold 150% sans-serif;} +.slide h2 {font: bold 125% sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 110% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #888; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: black; color: #CCC;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #AAA; + font-family: sans-serif; font-weight: bold;} + +#slide0 h1 {position: static; margin: 0 0 0.5em; padding-top: 1em; top: 0; + font: bold 150% sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 125% sans-serif; color: gray;} +#slide0 h3 {margin-top: 1.5em; font: bold 110% sans-serif;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #FCC;} + +.incremental, .incremental *, .incremental *:after { + color: black; visibility: visible; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: lime;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 0.5em 0 0.5em 1em;} + +div.sidebar {background-color: black;} + +pre.literal-block, pre.doctest-block {background-color: black;} + +tt.docutils {background-color: black;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-white/framing.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-white/framing.css new file mode 100644 index 00000000..ebb8a573 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-white/framing.css @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 0.75em 4% 0 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-white/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-white/pretty.css new file mode 100644 index 00000000..1c9fafdf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/medium-white/pretty.css @@ -0,0 +1,113 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: white; color: black;} +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #888 !important;} +#controls :focus {outline: 1px dotted #222;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#footer {font-family: sans-serif; color: #444; + font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.75em;} +.slide h1 {padding-top: 0; z-index: 1; margin: 0; font: bold 150% sans-serif;} +.slide h2 {font: bold 125% sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 110% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #888; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #222;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #444; + font-family: sans-serif; font-weight: bold;} + +#slide0 h1 {position: static; margin: 0 0 0.5em; padding-top: 1em; top: 0; + font: bold 150% sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 125% sans-serif; color: gray;} +#slide0 h3 {margin-top: 1.5em; font: bold 110% sans-serif;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after { + color: white; visibility: visible; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 0.5em 0 0.5em 1em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-black/__base__ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-black/__base__ new file mode 100644 index 00000000..67f4db2b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-black/__base__ @@ -0,0 +1,2 @@ +# base theme of this theme: +small-white diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-black/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-black/pretty.css new file mode 100644 index 00000000..5524e12e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-black/pretty.css @@ -0,0 +1,116 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: black; color: white;} +:link, :visited {text-decoration: none; color: cyan;} +#controls :active {color: #888 !important;} +#controls :focus {outline: 1px dotted #CCC;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#footer {font-family: sans-serif; color: #AAA; + font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.2em;} +.slide h1 {padding-top: 0; z-index: 1; margin: 0; font: bold 150% sans-serif;} +.slide h2 {font: bold 120% sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 100% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #888; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: black; color: #CCC;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #AAA; + font-family: sans-serif; font-weight: bold;} + +#slide0 {padding-top: 0em} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; + font: bold 2em sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 1em sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #FCC;} + +.incremental, .incremental *, .incremental *:after { + color: black; visibility: visible; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: lime;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 1em 0 0.5em 2em;} + +div.sidebar {background-color: black;} + +pre.literal-block, pre.doctest-block {background-color: black;} + +tt.docutils {background-color: black;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-white/framing.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-white/framing.css new file mode 100644 index 00000000..f6578749 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-white/framing.css @@ -0,0 +1,24 @@ +/* This file has been placed in the public domain. */ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 1em 4% 0 4%; z-index: 2;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; + z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-white/pretty.css b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-white/pretty.css new file mode 100644 index 00000000..edf4cb5e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/s5_html/themes/small-white/pretty.css @@ -0,0 +1,114 @@ +/* This file has been placed in the public domain. */ +/* Following are the presentation styles -- edit away! */ + +html, body {margin: 0; padding: 0;} +body {background: white; color: black;} +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #888 !important;} +#controls :focus {outline: 1px dotted #222;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} + +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em;} +blockquote p {margin: 0;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide pre {padding: 0; margin-left: 0; margin-right: 0; font-size: 90%;} +.slide ul ul li {list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} +.slide tt {font-size: 90%;} + +div#footer {font-family: sans-serif; color: #444; + font-size: 0.5em; font-weight: bold; padding: 1em 0;} +#footer h1 {display: block; padding: 0 1em;} +#footer h2 {display: block; padding: 0.8em 1em 0;} + +.slide {font-size: 1.2em;} +.slide h1 {padding-top: 0; z-index: 1; margin: 0; font: bold 150% sans-serif;} +.slide h2 {font: bold 120% sans-serif; padding-top: 0.5em;} +.slide h3 {font: bold 100% sans-serif; padding-top: 0.5em;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; text-align: right; font: bold 0.9em sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #888; cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; + background: #DDD; color: #222;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #444; + font-family: sans-serif; font-weight: bold;} + +#slide0 {padding-top: 0em} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; + font: bold 2em sans-serif; white-space: normal; background: transparent;} +#slide0 h2 {font: bold italic 1em sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after { + color: white; visibility: visible; border: 0; border: 0;} +img.incremental {visibility: hidden;} +.slide .current {color: green;} + +.slide-display {display: inline ! important;} + +.huge {font-family: sans-serif; font-weight: bold; font-size: 150%;} +.big {font-family: sans-serif; font-weight: bold; font-size: 120%;} +.small {font-size: 75%;} +.tiny {font-size: 50%;} +.huge tt, .big tt, .small tt, .tiny tt {font-size: 115%;} +.huge pre, .big pre, .small pre, .tiny pre {font-size: 115%;} + +.maroon {color: maroon;} +.red {color: red;} +.magenta {color: magenta;} +.fuchsia {color: fuchsia;} +.pink {color: #FAA;} +.orange {color: orange;} +.yellow {color: yellow;} +.lime {color: lime;} +.green {color: green;} +.olive {color: olive;} +.teal {color: teal;} +.cyan {color: cyan;} +.aqua {color: aqua;} +.blue {color: blue;} +.navy {color: navy;} +.purple {color: purple;} +.black {color: black;} +.gray {color: gray;} +.silver {color: silver;} +.white {color: white;} + +.left {text-align: left ! important;} +.center {text-align: center ! important;} +.right {text-align: right ! important;} + +.animation {position: relative; margin: 1em 0; padding: 0;} +.animation img {position: absolute;} + +/* Docutils-specific overrides */ + +.slide table.docinfo {margin: 1em 0 0.5em 2em;} + +pre.literal-block, pre.doctest-block {background-color: white;} + +tt.docutils {background-color: white;} + +/* diagnostics */ +/* +li:after {content: " [" attr(class) "]"; color: #F88;} +div:before {content: "[" attr(class) "]"; color: #F88;} +*/ diff --git a/.venv/lib/python3.12/site-packages/docutils/writers/xetex/__init__.py b/.venv/lib/python3.12/site-packages/docutils/writers/xetex/__init__.py new file mode 100644 index 00000000..a7bad3fd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/writers/xetex/__init__.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# :Author: Günter Milde <milde@users.sf.net> +# :Revision: $Revision: 9293 $ +# :Date: $Date: 2022-12-01 22:13:54 +0100 (Do, 01. Dez 2022) $ +# :Copyright: © 2010 Günter Milde. +# :License: Released under the terms of the `2-Clause BSD license`_, in short: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause + +""" +XeLaTeX document tree Writer. + +A variant of Docutils' standard 'latex2e' writer producing LaTeX output +suited for processing with the Unicode-aware TeX engines +LuaTeX and XeTeX. +""" + +__docformat__ = 'reStructuredText' + +from docutils import frontend +from docutils.writers import latex2e + + +class Writer(latex2e.Writer): + """A writer for Unicode-aware LaTeX variants (XeTeX, LuaTeX)""" + + supported = ('latex', 'tex', 'xetex', 'xelatex', 'luatex', 'lualatex') + """Formats this writer supports.""" + + default_template = 'xelatex.tex' + default_preamble = """\ +% Linux Libertine (free, wide coverage, not only for Linux) +\\setmainfont{Linux Libertine O} +\\setsansfont{Linux Biolinum O} +\\setmonofont[HyphenChar=None,Scale=MatchLowercase]{DejaVu Sans Mono}""" + + config_section = 'xetex writer' + config_section_dependencies = ('writers', 'latex writers') + + # use a copy of the parent spec with some modifications: + settings_spec = frontend.filter_settings_spec( + latex2e.Writer.settings_spec, + # removed settings + 'font_encoding', + # changed settings: + template=('Template file. Default: "%s".' % default_template, + ['--template'], + {'default': default_template, 'metavar': '<file>'}), + latex_preamble=('Customization by LaTeX code in the preamble. ' + 'Default: select "Linux Libertine" fonts.', + ['--latex-preamble'], + {'default': default_preamble}), + ) + + def __init__(self): + latex2e.Writer.__init__(self) + self.settings_defaults.update({'fontencoding': ''}) # use default (TU) + self.translator_class = XeLaTeXTranslator + + +class Babel(latex2e.Babel): + """Language specifics for XeTeX. + + Use `polyglossia` instead of `babel` and adapt settings. + """ + language_codes = latex2e.Babel.language_codes.copy() + # Additionally supported or differently named languages: + language_codes.update({ + # code Polyglossia-name comment + 'cop': 'coptic', + 'de': 'german', # new spelling (de_1996) + 'de-1901': 'ogerman', # old spelling + 'dv': 'divehi', # Maldivian + 'dsb': 'lsorbian', + 'el-polyton': 'polygreek', + 'fa': 'farsi', + 'grc': 'ancientgreek', + 'ko': 'korean', + 'hsb': 'usorbian', + 'sh-Cyrl': 'serbian', # Serbo-Croatian, Cyrillic script + 'sh-Latn': 'croatian', # Serbo-Croatian, Latin script + 'sq': 'albanian', + 'sr': 'serbian', # Cyrillic script (sr-Cyrl) + 'th': 'thai', + 'vi': 'vietnamese', + # zh-Latn: ??? # Chinese Pinyin + }) + # normalize (downcase) keys + language_codes = {k.lower(): v for k, v in language_codes.items()} + + # Languages without Polyglossia support: + for key in ('af', # 'afrikaans', + 'de-AT', # 'naustrian', + 'de-AT-1901', # 'austrian', + # TODO: use variant=... for English variants + 'en-CA', # 'canadian', + 'en-GB', # 'british', + 'en-NZ', # 'newzealand', + 'en-US', # 'american', + 'fr-CA', # 'canadien', + 'grc-ibycus', # 'ibycus', (Greek Ibycus encoding) + 'sr-Latn', # 'serbian script=latin' + ): + del language_codes[key.lower()] + + def __init__(self, language_code, reporter): + self.language_code = language_code + self.reporter = reporter + self.language = self.language_name(language_code) + self.otherlanguages = {} + self.warn_msg = 'Language "%s" not supported by Polyglossia.' + self.quote_index = 0 + self.quotes = ('"', '"') + # language dependent configuration: + # double quotes are "active" in some languages (e.g. German). + self.literal_double_quote = '"' # TODO: use \textquotedbl ? + + def __call__(self): + setup = [r'\usepackage{polyglossia}', + r'\setdefaultlanguage{%s}' % self.language] + if self.otherlanguages: + setup.append(r'\setotherlanguages{%s}' % + ','.join(sorted(self.otherlanguages.keys()))) + return '\n'.join(setup) + + +class XeLaTeXTranslator(latex2e.LaTeXTranslator): + """ + Generate code for LaTeX using Unicode fonts (XeLaTex or LuaLaTeX). + + See the docstring of docutils.writers._html_base.HTMLTranslator for + notes on and examples of safe subclassing. + """ + + def __init__(self, document): + self.is_xetex = True # typeset with XeTeX or LuaTeX engine + latex2e.LaTeXTranslator.__init__(self, document, Babel) + if self.latex_encoding == 'utf8': + self.requirements.pop('_inputenc', None) + else: + self.requirements['_inputenc'] = (r'\XeTeXinputencoding %s ' + % self.latex_encoding) |