diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/docutils')
202 files changed, 52224 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/docutils/__init__.py b/.venv/lib/python3.12/site-packages/docutils/__init__.py new file mode 100644 index 00000000..16af4108 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/__init__.py @@ -0,0 +1,291 @@ +# $Id: __init__.py 9649 2024-04-23 18:54:26Z grubert $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This is the Docutils (Python Documentation Utilities) package. + +Package Structure +================= + +Modules: + +- __init__.py: Contains component base classes, exception classes, and + Docutils version information. + +- core.py: Contains the ``Publisher`` class and ``publish_*()`` convenience + functions. + +- frontend.py: Runtime settings (command-line interface, configuration files) + processing, for Docutils front-ends. + +- io.py: Provides a uniform API for low-level input and output. + +- nodes.py: Docutils document tree (doctree) node class library. + +- statemachine.py: A finite state machine specialized for + regular-expression-based text filters. + +Subpackages: + +- languages: Language-specific mappings of terms. + +- parsers: Syntax-specific input parser modules or packages. + +- readers: Context-specific input handlers which understand the data + source and manage a parser. + +- transforms: Modules used by readers and writers to modify + the Docutils document tree. + +- utils: Contains the ``Reporter`` system warning class and miscellaneous + utilities used by readers, writers, and transforms. + + utils/urischemes.py: Contains a complete mapping of known URI addressing + scheme names to descriptions. + +- utils/math: Contains functions for conversion of mathematical notation + between different formats (LaTeX, MathML, text, ...). + +- writers: Format-specific output translators. +""" + +from collections import namedtuple + +__docformat__ = 'reStructuredText' + +__version__ = '0.21.2' +"""Docutils version identifier (complies with PEP 440):: + + major.minor[.micro][releaselevel[serial]][.dev] + +For version comparison operations, use `__version_info__` (see, below) +rather than parsing the text of `__version__`. + +https://docutils.sourceforge.io/docs/dev/policies.html#version-identification +""" + +__version_details__ = '' +"""Optional extra version details (e.g. 'snapshot 2005-05-29, r3410'). + +For development and release status, use `__version__ and `__version_info__`. +""" + + +class VersionInfo(namedtuple('VersionInfo', + 'major minor micro releaselevel serial release')): + + def __new__(cls, major=0, minor=0, micro=0, + releaselevel='final', serial=0, release=True): + releaselevels = ('alpha', 'beta', 'candidate', 'final') + if releaselevel not in releaselevels: + raise ValueError('releaselevel must be one of %r.' + % (releaselevels, )) + if releaselevel == 'final': + if not release: + raise ValueError('releaselevel "final" must not be used ' + 'with development versions (leads to wrong ' + 'version ordering of the related __version__') + # cf. https://peps.python.org/pep-0440/#summary-of-permitted-suffixes-and-relative-ordering # noqa + if serial != 0: + raise ValueError('"serial" must be 0 for final releases') + + return super().__new__(cls, major, minor, micro, + releaselevel, serial, release) + + def __lt__(self, other): + if isinstance(other, tuple): + other = VersionInfo(*other) + return tuple.__lt__(self, other) + + def __gt__(self, other): + if isinstance(other, tuple): + other = VersionInfo(*other) + return tuple.__gt__(self, other) + + def __le__(self, other): + if isinstance(other, tuple): + other = VersionInfo(*other) + return tuple.__le__(self, other) + + def __ge__(self, other): + if isinstance(other, tuple): + other = VersionInfo(*other) + return tuple.__ge__(self, other) + + +__version_info__ = VersionInfo( + major=0, + minor=21, + micro=2, + releaselevel='final', # one of 'alpha', 'beta', 'candidate', 'final' + serial=0, # pre-release number (0 for final releases and snapshots) + release=True # True for official releases and pre-releases + ) +"""Comprehensive version information tuple. + +https://docutils.sourceforge.io/docs/dev/policies.html#version-identification +""" + + +class ApplicationError(Exception): pass +class DataError(ApplicationError): pass + + +class SettingsSpec: + + """ + Runtime setting specification base class. + + SettingsSpec subclass objects used by `docutils.frontend.OptionParser`. + """ + + # TODO: replace settings_specs with a new data structure + # Backwards compatiblity: + # Drop-in components: + # Sphinx supplies settings_spec in the current format in some places + # Myst parser provides a settings_spec tuple + # + # Sphinx reads a settings_spec in order to set a default value + # in writers/html.py:59 + # https://github.com/sphinx-doc/sphinx/blob/4.x/sphinx/writers/html.py + # This should be changed (before retiring the old format) + # to use `settings_default_overrides` instead. + settings_spec = () + """Runtime settings specification. Override in subclasses. + + Defines runtime settings and associated command-line options, as used by + `docutils.frontend.OptionParser`. This is a tuple of: + + - Option group title (string or `None` which implies no group, just a list + of single options). + + - Description (string or `None`). + + - A sequence of option tuples. Each consists of: + + - Help text (string) + + - List of option strings (e.g. ``['-Q', '--quux']``). + + - Dictionary of keyword arguments sent to the OptionParser/OptionGroup + ``add_option`` method. + + Runtime setting names are derived implicitly from long option names + ('--a-setting' becomes ``settings.a_setting``) or explicitly from the + 'dest' keyword argument. + + Most settings will also have a 'validator' keyword & function. The + validator function validates setting values (from configuration files + and command-line option arguments) and converts them to appropriate + types. For example, the ``docutils.frontend.validate_boolean`` + function, **required by all boolean settings**, converts true values + ('1', 'on', 'yes', and 'true') to 1 and false values ('0', 'off', + 'no', 'false', and '') to 0. Validators need only be set once per + setting. See the `docutils.frontend.validate_*` functions. + + See the optparse docs for more details. + + - More triples of group title, description, options, as many times as + needed. Thus, `settings_spec` tuples can be simply concatenated. + """ + + settings_defaults = None + """A dictionary of defaults for settings not in `settings_spec` (internal + settings, intended to be inaccessible by command-line and config file). + Override in subclasses.""" + + settings_default_overrides = None + """A dictionary of auxiliary defaults, to override defaults for settings + defined in other components' `setting_specs`. Override in subclasses.""" + + relative_path_settings = () + """Settings containing filesystem paths. Override in subclasses. + Settings listed here are to be interpreted relative to the current working + directory.""" + + config_section = None + """The name of the config file section specific to this component + (lowercase, no brackets). Override in subclasses.""" + + config_section_dependencies = None + """A list of names of config file sections that are to be applied before + `config_section`, in order (from general to specific). In other words, + the settings in `config_section` are to be overlaid on top of the settings + from these sections. The "general" section is assumed implicitly. + Override in subclasses.""" + + +class TransformSpec: + """ + Runtime transform specification base class. + + Provides the interface to register "transforms" and helper functions + to resolve references with a `docutils.transforms.Transformer`. + + https://docutils.sourceforge.io/docs/ref/transforms.html + """ + + def get_transforms(self): + """Transforms required by this class. Override in subclasses.""" + if self.default_transforms != (): + import warnings + warnings.warn('TransformSpec: the "default_transforms" attribute ' + 'will be removed in Docutils 2.0.\n' + 'Use get_transforms() method instead.', + DeprecationWarning) + return list(self.default_transforms) + return [] + + # Deprecated; for compatibility. + default_transforms = () + + unknown_reference_resolvers = () + """List of functions to try to resolve unknown references. + + Unknown references have a 'refname' attribute which doesn't correspond + to any target in the document. Called when the transforms in + `docutils.transforms.references` are unable to find a correct target. + + The list should contain functions which will try to resolve unknown + references, with the following signature:: + + def reference_resolver(node): + '''Returns boolean: true if resolved, false if not.''' + + If the function is able to resolve the reference, it should also remove + the 'refname' attribute and mark the node as resolved:: + + del node['refname'] + node.resolved = 1 + + Each function must have a "priority" attribute which will affect the order + the unknown_reference_resolvers are run:: + + reference_resolver.priority = 100 + + This hook is provided for 3rd party extensions. + Example use case: the `MoinMoin - ReStructured Text Parser` + in ``sandbox/mmgilbe/rst.py``. + """ + + +class Component(SettingsSpec, TransformSpec): + + """Base class for Docutils components.""" + + component_type = None + """Name of the component type ('reader', 'parser', 'writer'). Override in + subclasses.""" + + supported = () + """Name and aliases for this component. Override in subclasses.""" + + def supports(self, format): + """ + Is `format` supported by this component? + + To be used by transforms to ask the dependent component if it supports + a certain input context or output format. + """ + return format in self.supported diff --git a/.venv/lib/python3.12/site-packages/docutils/__main__.py b/.venv/lib/python3.12/site-packages/docutils/__main__.py new file mode 100755 index 00000000..ce614891 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/__main__.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +# :Copyright: © 2020, 2022 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 +# +# Revision: $Revision: 9107 $ +# Date: $Date: 2022-07-06 15:59:57 +0200 (Mi, 06. Jul 2022) $ + +"""Generic command line interface for the `docutils` package. + +See also +https://docs.python.org/3/library/__main__.html#main-py-in-python-packages +""" + +import argparse +import locale +import sys + +import docutils +from docutils.core import Publisher, publish_cmdline, default_description + + +class CliSettingsSpec(docutils.SettingsSpec): + """Runtime settings & command-line options for the generic CLI. + + Configurable reader, parser, and writer components. + + The "--writer" default will change to 'html' in Docutils 2.0 + when 'html' becomes an alias for the current value 'html5'. + """ + + settings_spec = ( + 'Docutils Application Options', + 'Reader, writer, and parser settings influence the available options. ' + ' Example: use `--help --writer=latex` to see LaTeX writer options. ', + # options: ('help text', [<option strings>], {<keyword arguments>}) + (('Reader name (currently: "%default").', + ['--reader'], {'default': 'standalone', 'metavar': '<reader>'}), + ('Parser name (currently: "%default").', + ['--parser'], {'default': 'rst', 'metavar': '<parser>'}), + ('Writer name (currently: "%default").', + ['--writer'], {'default': 'html5', 'metavar': '<writer>'}), + ) + ) + config_section = 'docutils application' + config_section_dependencies = ('docutils-cli application', # back-compat + 'applications') + + +def main(): + """Generic command line interface for the Docutils Publisher. + """ + locale.setlocale(locale.LC_ALL, '') + + description = ('Convert documents into useful formats. ' + + default_description) + + # Update component selection from config file(s) + components = Publisher().get_settings(settings_spec=CliSettingsSpec) + + # Update component selection from command-line + argparser = argparse.ArgumentParser(add_help=False, allow_abbrev=False) + argparser.add_argument('--reader', default=components.reader) + argparser.add_argument('--parser', default=components.parser) + argparser.add_argument('--writer', default=components.writer) + # other options are parsed in a second pass via `publish_cmdline()` + (args, remainder) = argparser.parse_known_args() + # Ensure the current component selections are shown in help: + CliSettingsSpec.settings_default_overrides = args.__dict__ + + try: + publish_cmdline(reader_name=args.reader, + parser_name=args.parser, + writer_name=args.writer, + settings_spec=CliSettingsSpec, + description=description, + argv=remainder) + except ImportError as error: + print('%s.' % error, file=sys.stderr) + if '--traceback' in remainder: + raise + else: + print('Use "--traceback" to show details.') + + +if __name__ == '__main__': + if sys.argv[0].endswith('__main__.py'): + # fix "usage" message + sys.argv[0] = '%s -m docutils' % sys.executable + main() diff --git a/.venv/lib/python3.12/site-packages/docutils/core.py b/.venv/lib/python3.12/site-packages/docutils/core.py new file mode 100644 index 00000000..adf80759 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/core.py @@ -0,0 +1,780 @@ +# $Id: core.py 9369 2023-05-02 23:04:27Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Calling the ``publish_*`` convenience functions (or instantiating a +`Publisher` object) with component names will result in default +behavior. For custom behavior (setting component options), create +custom component objects first, and pass *them* to +``publish_*``/`Publisher`. See `The Docutils Publisher`_. + +.. _The Docutils Publisher: + https://docutils.sourceforge.io/docs/api/publisher.html +""" + +__docformat__ = 'reStructuredText' + +import locale +import pprint +import os +import sys +import warnings + +from docutils import (__version__, __version_details__, SettingsSpec, + io, utils, readers, writers) +from docutils.frontend import OptionParser +from docutils.readers import doctree + + +class Publisher: + + """ + A facade encapsulating the high-level logic of a Docutils system. + """ + + def __init__(self, reader=None, parser=None, writer=None, + source=None, source_class=io.FileInput, + destination=None, destination_class=io.FileOutput, + settings=None): + """ + Initial setup. If any of `reader`, `parser`, or `writer` are not + specified, ``set_components()`` or the corresponding ``set_...()`` + method should be called with component names + (`set_reader` sets the parser as well). + """ + + self.document = None + """The document tree (`docutils.nodes` objects).""" + + self.reader = reader + """A `docutils.readers.Reader` instance.""" + + self.parser = parser + """A `docutils.parsers.Parser` instance.""" + + self.writer = writer + """A `docutils.writers.Writer` instance.""" + + for component in 'reader', 'parser', 'writer': + assert not isinstance(getattr(self, component), str), ( + 'passed string "%s" as "%s" parameter; pass an instance, ' + 'or use the "%s_name" parameter instead (in ' + 'docutils.core.publish_* convenience functions).' + % (getattr(self, component), component, component)) + + self.source = source + """The source of input data, a `docutils.io.Input` instance.""" + + self.source_class = source_class + """The class for dynamically created source objects.""" + + self.destination = destination + """The destination for docutils output, a `docutils.io.Output` + instance.""" + + self.destination_class = destination_class + """The class for dynamically created destination objects.""" + + self.settings = settings + """An object containing Docutils settings as instance attributes. + Set by `self.process_command_line()` or `self.get_settings()`.""" + + self._stderr = io.ErrorOutput() + + def set_reader(self, reader_name, parser, parser_name): + """Set `self.reader` by name.""" + reader_class = readers.get_reader_class(reader_name) + self.reader = reader_class(parser, parser_name) + self.parser = self.reader.parser + + def set_writer(self, writer_name): + """Set `self.writer` by name.""" + writer_class = writers.get_writer_class(writer_name) + self.writer = writer_class() + + def set_components(self, reader_name, parser_name, writer_name): + if self.reader is None: + self.set_reader(reader_name, self.parser, parser_name) + if self.parser is None: + if self.reader.parser is None: + self.reader.set_parser(parser_name) + self.parser = self.reader.parser + if self.writer is None: + self.set_writer(writer_name) + + def setup_option_parser(self, usage=None, description=None, + settings_spec=None, config_section=None, + **defaults): + warnings.warn('Publisher.setup_option_parser is deprecated, ' + 'and will be removed in Docutils 0.21.', + DeprecationWarning, stacklevel=2) + if config_section: + if not settings_spec: + settings_spec = SettingsSpec() + settings_spec.config_section = config_section + parts = config_section.split() + if len(parts) > 1 and parts[-1] == 'application': + settings_spec.config_section_dependencies = ['applications'] + # @@@ Add self.source & self.destination to components in future? + return OptionParser( + components=(self.parser, self.reader, self.writer, settings_spec), + defaults=defaults, read_config_files=True, + usage=usage, description=description) + + def _setup_settings_parser(self, *args, **kwargs): + # Provisional: will change (docutils.frontend.OptionParser will + # be replaced by a parser based on arparse.ArgumentParser) + # and may be removed later. + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + return self.setup_option_parser(*args, **kwargs) + + def get_settings(self, usage=None, description=None, + settings_spec=None, config_section=None, **defaults): + """ + Return settings from components and config files. + + Please set components first (`self.set_reader` & `self.set_writer`). + Use keyword arguments to override component defaults + (before updating from configuration files). + + Calling this function also sets `self.settings` which makes + `self.publish()` skip parsing command line options. + """ + option_parser = self._setup_settings_parser( + usage, description, settings_spec, config_section, **defaults) + self.settings = option_parser.get_default_values() + return self.settings + + def process_programmatic_settings(self, settings_spec, + settings_overrides, + config_section): + if self.settings is None: + defaults = settings_overrides.copy() if settings_overrides else {} + # Propagate exceptions by default when used programmatically: + defaults.setdefault('traceback', True) + self.get_settings(settings_spec=settings_spec, + config_section=config_section, + **defaults) + + def process_command_line(self, argv=None, usage=None, description=None, + settings_spec=None, config_section=None, + **defaults): + """ + Parse command line arguments and set ``self.settings``. + + Pass an empty sequence to `argv` to avoid reading `sys.argv` + (the default behaviour). + + Set components first (`self.set_reader` & `self.set_writer`). + """ + option_parser = self._setup_settings_parser( + usage, description, settings_spec, config_section, **defaults) + if argv is None: + argv = sys.argv[1:] + self.settings = option_parser.parse_args(argv) + + def set_io(self, source_path=None, destination_path=None): + if self.source is None: + self.set_source(source_path=source_path) + if self.destination is None: + self.set_destination(destination_path=destination_path) + + def set_source(self, source=None, source_path=None): + if source_path is None: + source_path = self.settings._source + else: + self.settings._source = source_path + self.source = self.source_class( + source=source, source_path=source_path, + encoding=self.settings.input_encoding, + error_handler=self.settings.input_encoding_error_handler) + + def set_destination(self, destination=None, destination_path=None): + if destination_path is None: + if (self.settings.output and self.settings._destination + and self.settings.output != self.settings._destination): + raise SystemExit('The positional argument <destination> is ' + 'obsoleted by the --output option. ' + 'You cannot use them together.') + if self.settings.output == '-': # means stdout + self.settings.output = None + destination_path = (self.settings.output + or self.settings._destination) + self.settings._destination = destination_path + self.destination = self.destination_class( + destination=destination, + destination_path=destination_path, + encoding=self.settings.output_encoding, + error_handler=self.settings.output_encoding_error_handler) + + def apply_transforms(self): + self.document.transformer.populate_from_components( + (self.source, self.reader, self.reader.parser, self.writer, + self.destination)) + self.document.transformer.apply_transforms() + + def publish(self, argv=None, usage=None, description=None, + settings_spec=None, settings_overrides=None, + config_section=None, enable_exit_status=False): + """ + Process command line options and arguments (if `self.settings` not + already set), run `self.reader` and then `self.writer`. Return + `self.writer`'s output. + """ + exit = None + try: + if self.settings is None: + self.process_command_line( + argv, usage, description, settings_spec, config_section, + **(settings_overrides or {})) + self.set_io() + self.prompt() + self.document = self.reader.read(self.source, self.parser, + self.settings) + self.apply_transforms() + output = self.writer.write(self.document, self.destination) + self.writer.assemble_parts() + except SystemExit as error: + exit = True + exit_status = error.code + except Exception as error: + if not self.settings: # exception too early to report nicely + raise + if self.settings.traceback: # Propagate exceptions? + self.debugging_dumps() + raise + self.report_Exception(error) + exit = True + exit_status = 1 + self.debugging_dumps() + if (enable_exit_status and self.document + and (self.document.reporter.max_level + >= self.settings.exit_status_level)): + sys.exit(self.document.reporter.max_level + 10) + elif exit: + sys.exit(exit_status) + return output + + def debugging_dumps(self): + if not self.document: + return + if self.settings.dump_settings: + print('\n::: Runtime settings:', file=self._stderr) + print(pprint.pformat(self.settings.__dict__), file=self._stderr) + if self.settings.dump_internals: + print('\n::: Document internals:', file=self._stderr) + print(pprint.pformat(self.document.__dict__), file=self._stderr) + if self.settings.dump_transforms: + print('\n::: Transforms applied:', file=self._stderr) + print(' (priority, transform class, pending node details, ' + 'keyword args)', file=self._stderr) + print(pprint.pformat( + [(priority, '%s.%s' % (xclass.__module__, xclass.__name__), + pending and pending.details, kwargs) + for priority, xclass, pending, kwargs + in self.document.transformer.applied]), file=self._stderr) + if self.settings.dump_pseudo_xml: + print('\n::: Pseudo-XML:', file=self._stderr) + print(self.document.pformat().encode( + 'raw_unicode_escape'), file=self._stderr) + + def prompt(self): + """Print info and prompt when waiting for input from a terminal.""" + try: + if not (self.source.isatty() and self._stderr.isatty()): + return + except AttributeError: + return + eot_key = 'Ctrl+Z' if os.name == 'nt' else 'Ctrl+D' + in_format = '' + out_format = 'useful formats' + try: + in_format = self.parser.supported[0] + out_format = self.writer.supported[0] + except (AttributeError, IndexError): + pass + print(f'Docutils {__version__} <https://docutils.sourceforge.io>\n' + f'converting "{in_format}" into "{out_format}".\n' + f'Call with option "--help" for more info.\n' + f'.. Waiting for source text (finish with {eot_key} ' + 'on an empty line):', + file=self._stderr) + + def report_Exception(self, error): + if isinstance(error, utils.SystemMessage): + self.report_SystemMessage(error) + elif isinstance(error, UnicodeEncodeError): + self.report_UnicodeError(error) + elif isinstance(error, io.InputError): + self._stderr.write('Unable to open source file for reading:\n' + ' %s\n' % io.error_string(error)) + elif isinstance(error, io.OutputError): + self._stderr.write( + 'Unable to open destination file for writing:\n' + ' %s\n' % io.error_string(error)) + else: + print('%s' % io.error_string(error), file=self._stderr) + print(f"""\ +Exiting due to error. Use "--traceback" to diagnose. +Please report errors to <docutils-users@lists.sourceforge.net>. +Include "--traceback" output, Docutils version ({__version__}\ +{f' [{__version_details__}]' if __version_details__ else ''}), +Python version ({sys.version.split()[0]}), your OS type & version, \ +and the command line used.""", file=self._stderr) + + def report_SystemMessage(self, error): + print('Exiting due to level-%s (%s) system message.' % ( + error.level, utils.Reporter.levels[error.level]), + file=self._stderr) + + def report_UnicodeError(self, error): + data = error.object[error.start:error.end] + self._stderr.write( + '%s\n' + '\n' + 'The specified output encoding (%s) cannot\n' + 'handle all of the output.\n' + 'Try setting "--output-encoding-error-handler" to\n' + '\n' + '* "xmlcharrefreplace" (for HTML & XML output);\n' + ' the output will contain "%s" and should be usable.\n' + '* "backslashreplace" (for other output formats);\n' + ' look for "%s" in the output.\n' + '* "replace"; look for "?" in the output.\n' + '\n' + '"--output-encoding-error-handler" is currently set to "%s".\n' + '\n' + 'Exiting due to error. Use "--traceback" to diagnose.\n' + 'If the advice above doesn\'t eliminate the error,\n' + 'please report it to <docutils-users@lists.sourceforge.net>.\n' + 'Include "--traceback" output, Docutils version (%s),\n' + 'Python version (%s), your OS type & version, and the\n' + 'command line used.\n' + % (io.error_string(error), + self.settings.output_encoding, + data.encode('ascii', 'xmlcharrefreplace'), + data.encode('ascii', 'backslashreplace'), + self.settings.output_encoding_error_handler, + __version__, sys.version.split()[0])) + + +default_usage = '%prog [options] [<source> [<destination>]]' +default_description = ( + 'Reads from <source> (default is stdin) ' + 'and writes to <destination> (default is stdout). ' + 'See https://docutils.sourceforge.io/docs/user/config.html ' + 'for a detailed settings reference.') + + +# TODO: or not to do? cf. https://clig.dev/#help +# +# Display output on success, but keep it brief. +# Provide a -q option to suppress all non-essential output. +# +# Chain several args as input and use --output or redirection for output: +# argparser.add_argument('source', nargs='+') +# +def publish_cmdline(reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='pseudoxml', + settings=None, settings_spec=None, + settings_overrides=None, config_section=None, + enable_exit_status=True, argv=None, + usage=default_usage, description=default_description): + """ + Set up & run a `Publisher` for command-line-based file I/O (input and + output file paths taken automatically from the command line). + Also return the output as `str` or `bytes` (for binary output document + formats). + + Parameters: see `publish_programmatically()` for the remainder. + + - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``. + - `usage`: Usage string, output if there's a problem parsing the command + line. + - `description`: Program description, output for the "--help" option + (along with command-line option descriptions). + """ + publisher = Publisher(reader, parser, writer, settings=settings) + publisher.set_components(reader_name, parser_name, writer_name) + output = publisher.publish( + argv, usage, description, settings_spec, settings_overrides, + config_section=config_section, enable_exit_status=enable_exit_status) + return output + + +def publish_file(source=None, source_path=None, + destination=None, destination_path=None, + reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='pseudoxml', + settings=None, settings_spec=None, settings_overrides=None, + config_section=None, enable_exit_status=False): + """ + Set up & run a `Publisher` for programmatic use with file-like I/O. + Also return the output as `str` or `bytes` (for binary output document + formats). + + Parameters: see `publish_programmatically()`. + """ + output, publisher = publish_programmatically( + source_class=io.FileInput, source=source, source_path=source_path, + destination_class=io.FileOutput, + destination=destination, destination_path=destination_path, + reader=reader, reader_name=reader_name, + parser=parser, parser_name=parser_name, + writer=writer, writer_name=writer_name, + settings=settings, settings_spec=settings_spec, + settings_overrides=settings_overrides, + config_section=config_section, + enable_exit_status=enable_exit_status) + return output + + +def publish_string(source, source_path=None, destination_path=None, + reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='pseudoxml', + settings=None, settings_spec=None, + settings_overrides=None, config_section=None, + enable_exit_status=False): + """ + Set up & run a `Publisher` for programmatic use with string I/O. + + Accepts a `bytes` or `str` instance as `source`. + + The output is encoded according to the `output_encoding`_ setting; + the return value is a `bytes` instance (unless `output_encoding`_ is + "unicode", cf. `docutils.io.StringOutput.write()`). + + Parameters: see `publish_programmatically()`. + + This function is provisional because in Python 3 name and behaviour + no longer match. + + .. _output_encoding: + https://docutils.sourceforge.io/docs/user/config.html#output-encoding + """ + output, publisher = publish_programmatically( + source_class=io.StringInput, source=source, source_path=source_path, + destination_class=io.StringOutput, + destination=None, destination_path=destination_path, + reader=reader, reader_name=reader_name, + parser=parser, parser_name=parser_name, + writer=writer, writer_name=writer_name, + settings=settings, settings_spec=settings_spec, + settings_overrides=settings_overrides, + config_section=config_section, + enable_exit_status=enable_exit_status) + return output + + +def publish_parts(source, source_path=None, source_class=io.StringInput, + destination_path=None, + reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='pseudoxml', + settings=None, settings_spec=None, + settings_overrides=None, config_section=None, + enable_exit_status=False): + """ + Set up & run a `Publisher`, and return a dictionary of document parts. + + Dictionary keys are the names of parts. + Dictionary values are `str` instances; encoding is up to the client, + e.g.:: + + parts = publish_parts(...) + body = parts['body'].encode(parts['encoding'], parts['errors']) + + See the `API documentation`__ for details on the provided parts. + + Parameters: see `publish_programmatically()`. + + __ https://docutils.sourceforge.io/docs/api/publisher.html#publish-parts + """ + output, publisher = publish_programmatically( + source=source, source_path=source_path, source_class=source_class, + destination_class=io.StringOutput, + destination=None, destination_path=destination_path, + reader=reader, reader_name=reader_name, + parser=parser, parser_name=parser_name, + writer=writer, writer_name=writer_name, + settings=settings, settings_spec=settings_spec, + settings_overrides=settings_overrides, + config_section=config_section, + enable_exit_status=enable_exit_status) + return publisher.writer.parts + + +def publish_doctree(source, source_path=None, + source_class=io.StringInput, + reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + settings=None, settings_spec=None, + settings_overrides=None, config_section=None, + enable_exit_status=False): + """ + Set up & run a `Publisher` for programmatic use. Return a document tree. + + Parameters: see `publish_programmatically()`. + """ + _output, publisher = publish_programmatically( + source=source, source_path=source_path, + source_class=source_class, + destination=None, destination_path=None, + destination_class=io.NullOutput, + reader=reader, reader_name=reader_name, + parser=parser, parser_name=parser_name, + writer=None, writer_name='null', + settings=settings, settings_spec=settings_spec, + settings_overrides=settings_overrides, config_section=config_section, + enable_exit_status=enable_exit_status) + return publisher.document + + +def publish_from_doctree(document, destination_path=None, + writer=None, writer_name='pseudoxml', + settings=None, settings_spec=None, + settings_overrides=None, config_section=None, + enable_exit_status=False): + """ + Set up & run a `Publisher` to render from an existing document tree + data structure. For programmatic use with string output + (`bytes` or `str`, cf. `publish_string()`). + + Note that ``document.settings`` is overridden; if you want to use the + settings of the original `document`, pass ``settings=document.settings``. + + Also, new `document.transformer` and `document.reporter` objects are + generated. + + Parameters: `document` is a `docutils.nodes.document` object, an existing + document tree. + + Other parameters: see `publish_programmatically()`. + + This function is provisional because in Python 3 name and behaviour + of the `io.StringOutput` class no longer match. + """ + reader = doctree.Reader(parser_name='null') + publisher = Publisher(reader, None, writer, + source=io.DocTreeInput(document), + destination_class=io.StringOutput, + settings=settings) + if not writer and writer_name: + publisher.set_writer(writer_name) + publisher.process_programmatic_settings( + settings_spec, settings_overrides, config_section) + publisher.set_destination(None, destination_path) + return publisher.publish(enable_exit_status=enable_exit_status) + + +def publish_cmdline_to_binary(reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='pseudoxml', + settings=None, + settings_spec=None, + settings_overrides=None, + config_section=None, + enable_exit_status=True, + argv=None, + usage=default_usage, + description=default_description, + destination=None, + destination_class=io.BinaryFileOutput): + """ + Set up & run a `Publisher` for command-line-based file I/O (input and + output file paths taken automatically from the command line). + Also return the output as `bytes`. + + This is just like publish_cmdline, except that it uses + io.BinaryFileOutput instead of io.FileOutput. + + Parameters: see `publish_programmatically()` for the remainder. + + - `argv`: Command-line argument list to use instead of ``sys.argv[1:]``. + - `usage`: Usage string, output if there's a problem parsing the command + line. + - `description`: Program description, output for the "--help" option + (along with command-line option descriptions). + """ + publisher = Publisher(reader, parser, writer, settings=settings, + destination_class=destination_class) + publisher.set_components(reader_name, parser_name, writer_name) + output = publisher.publish( + argv, usage, description, settings_spec, settings_overrides, + config_section=config_section, enable_exit_status=enable_exit_status) + return output + + +def publish_programmatically(source_class, source, source_path, + destination_class, destination, destination_path, + reader, reader_name, + parser, parser_name, + writer, writer_name, + settings, settings_spec, + settings_overrides, config_section, + enable_exit_status): + """ + Set up & run a `Publisher` for custom programmatic use. + + Return the output (as `str` or `bytes`, depending on `destination_class`, + writer, and the "output_encoding" setting) and the Publisher object. + + Applications should not need to call this function directly. If it does + seem to be necessary to call this function directly, please write to the + Docutils-develop mailing list + <https://docutils.sourceforge.io/docs/user/mailing-lists.html#docutils-develop>. + + Parameters: + + * `source_class` **required**: The class for dynamically created source + objects. Typically `io.FileInput` or `io.StringInput`. + + * `source`: Type depends on `source_class`: + + - If `source_class` is `io.FileInput`: Either a file-like object + (must have 'read' and 'close' methods), or ``None`` + (`source_path` is opened). If neither `source` nor + `source_path` are supplied, `sys.stdin` is used. + + - If `source_class` is `io.StringInput` **required**: + The input as either a `bytes` object (ensure the 'input_encoding' + setting matches its encoding) or a `str` object. + + * `source_path`: Type depends on `source_class`: + + - `io.FileInput`: Path to the input file, opened if no `source` + supplied. + + - `io.StringInput`: Optional. Path to the file or name of the + object that produced `source`. Only used for diagnostic output. + + * `destination_class` **required**: The class for dynamically created + destination objects. Typically `io.FileOutput` or `io.StringOutput`. + + * `destination`: Type depends on `destination_class`: + + - `io.FileOutput`: Either a file-like object (must have 'write' and + 'close' methods), or ``None`` (`destination_path` is opened). If + neither `destination` nor `destination_path` are supplied, + `sys.stdout` is used. + + - `io.StringOutput`: Not used; pass ``None``. + + * `destination_path`: Type depends on `destination_class`: + + - `io.FileOutput`: Path to the output file. Opened if no `destination` + supplied. + + - `io.StringOutput`: Path to the file or object which will receive the + output; optional. Used for determining relative paths (stylesheets, + source links, etc.). + + * `reader`: A `docutils.readers.Reader` object. + + * `reader_name`: Name or alias of the Reader class to be instantiated if + no `reader` supplied. + + * `parser`: A `docutils.parsers.Parser` object. + + * `parser_name`: Name or alias of the Parser class to be instantiated if + no `parser` supplied. + + * `writer`: A `docutils.writers.Writer` object. + + * `writer_name`: Name or alias of the Writer class to be instantiated if + no `writer` supplied. + + * `settings`: A runtime settings (`docutils.frontend.Values`) object, for + dotted-attribute access to runtime settings. It's the end result of the + `SettingsSpec`, config file, and option processing. If `settings` is + passed, it's assumed to be complete and no further setting/config/option + processing is done. + + * `settings_spec`: A `docutils.SettingsSpec` subclass or object. Provides + extra application-specific settings definitions independently of + components. In other words, the application becomes a component, and + its settings data is processed along with that of the other components. + Used only if no `settings` specified. + + * `settings_overrides`: A dictionary containing application-specific + settings defaults that override the defaults of other components. + Used only if no `settings` specified. + + * `config_section`: A string, the name of the configuration file section + for this application. Overrides the ``config_section`` attribute + defined by `settings_spec`. Used only if no `settings` specified. + + * `enable_exit_status`: Boolean; enable exit status at end of processing? + """ + publisher = Publisher(reader, parser, writer, settings=settings, + source_class=source_class, + destination_class=destination_class) + publisher.set_components(reader_name, parser_name, writer_name) + publisher.process_programmatic_settings( + settings_spec, settings_overrides, config_section) + publisher.set_source(source, source_path) + publisher.set_destination(destination, destination_path) + output = publisher.publish(enable_exit_status=enable_exit_status) + return output, publisher + + +# "Entry points" with functionality of the "tools/rst2*.py" scripts +# cf. https://packaging.python.org/en/latest/specifications/entry-points/ + +def rst2something(writer, documenttype, doc_path=''): + # Helper function for the common parts of rst2... + # writer: writer name + # documenttype: output document type + # doc_path: documentation path (relative to the documentation root) + description = ( + f'Generate {documenttype} documents ' + 'from standalone reStructuredText sources ' + f'<https://docutils.sourceforge.io/docs/{doc_path}>. ' + + default_description) + locale.setlocale(locale.LC_ALL, '') + publish_cmdline(writer_name=writer, description=description) + + +def rst2html(): + rst2something('html', 'HTML', 'user/html.html#html') + + +def rst2html4(): + rst2something('html4', 'XHTML 1.1', 'user/html.html#html4css1') + + +def rst2html5(): + rst2something('html5', 'HTML5', 'user/html.html#html5-polyglot') + + +def rst2latex(): + rst2something('latex', 'LaTeX', 'user/latex.html') + + +def rst2man(): + rst2something('manpage', 'Unix manual (troff)', 'user/manpage.html') + + +def rst2odt(): + rst2something('odt', 'OpenDocument text (ODT)', 'user/odt.html') + + +def rst2pseudoxml(): + rst2something('pseudoxml', 'pseudo-XML (test)', 'ref/doctree.html') + + +def rst2s5(): + rst2something('s5', 'S5 HTML slideshow', 'user/slide-shows.html') + + +def rst2xetex(): + rst2something('xetex', 'LaTeX (XeLaTeX/LuaLaTeX)', 'user/latex.html') + + +def rst2xml(): + rst2something('xml', 'Docutils-native XML', 'ref/doctree.html') diff --git a/.venv/lib/python3.12/site-packages/docutils/docutils.conf b/.venv/lib/python3.12/site-packages/docutils/docutils.conf new file mode 100644 index 00000000..cdce8d62 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/docutils.conf @@ -0,0 +1,5 @@ +# This configuration file is to prevent tools/buildhtml.py from +# processing text files in and below this directory. + +[buildhtml application] +prune: . diff --git a/.venv/lib/python3.12/site-packages/docutils/examples.py b/.venv/lib/python3.12/site-packages/docutils/examples.py new file mode 100644 index 00000000..c27ab70d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/examples.py @@ -0,0 +1,99 @@ +# $Id: examples.py 9026 2022-03-04 15:57:13Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This module contains practical examples of Docutils client code. + +Importing this module from client code is not recommended; its contents are +subject to change in future Docutils releases. Instead, it is recommended +that you copy and paste the parts you need into your own code, modifying as +necessary. +""" + +from docutils import core, io + + +def html_parts(input_string, source_path=None, destination_path=None, + input_encoding='unicode', doctitle=True, + initial_header_level=1): + """ + Given an input string, returns a dictionary of HTML document parts. + + Dictionary keys are the names of parts, and values are Unicode strings; + encoding is up to the client. + + Parameters: + + - `input_string`: A multi-line text string; required. + - `source_path`: Path to the source file or object. Optional, but useful + for diagnostic output (system messages). + - `destination_path`: Path to the file or object which will receive the + output; optional. Used for determining relative paths (stylesheets, + source links, etc.). + - `input_encoding`: The encoding of `input_string`. If it is an encoded + 8-bit string, provide the correct encoding. If it is a Unicode string, + use "unicode", the default. + - `doctitle`: Disable the promotion of a lone top-level section title to + document title (and subsequent section title to document subtitle + promotion); enabled by default. + - `initial_header_level`: The initial level for header elements (e.g. 1 + for "<h1>"). + """ + overrides = {'input_encoding': input_encoding, + 'doctitle_xform': doctitle, + 'initial_header_level': initial_header_level} + parts = core.publish_parts( + source=input_string, source_path=source_path, + destination_path=destination_path, + writer_name='html', settings_overrides=overrides) + return parts + + +def html_body(input_string, source_path=None, destination_path=None, + input_encoding='unicode', output_encoding='unicode', + doctitle=True, initial_header_level=1): + """ + Given an input string, returns an HTML fragment as a string. + + The return value is the contents of the <body> element. + + Parameters (see `html_parts()` for the remainder): + + - `output_encoding`: The desired encoding of the output. If a Unicode + string is desired, use the default value of "unicode" . + """ + parts = html_parts( + input_string=input_string, source_path=source_path, + destination_path=destination_path, + input_encoding=input_encoding, doctitle=doctitle, + initial_header_level=initial_header_level) + fragment = parts['html_body'] + if output_encoding != 'unicode': + fragment = fragment.encode(output_encoding) + return fragment + + +def internals(input_string, source_path=None, destination_path=None, + input_encoding='unicode', settings_overrides=None): + """ + Return the document tree and publisher, for exploring Docutils internals. + + Parameters: see `html_parts()`. + """ + if settings_overrides: + overrides = settings_overrides.copy() + else: + overrides = {} + overrides['input_encoding'] = input_encoding + output, pub = core.publish_programmatically( + source_class=io.StringInput, source=input_string, + source_path=source_path, + destination_class=io.NullOutput, destination=None, + destination_path=destination_path, + reader=None, reader_name='standalone', + parser=None, parser_name='restructuredtext', + writer=None, writer_name='null', + settings=None, settings_spec=None, settings_overrides=overrides, + config_section=None, enable_exit_status=None) + return pub.writer.document, pub diff --git a/.venv/lib/python3.12/site-packages/docutils/frontend.py b/.venv/lib/python3.12/site-packages/docutils/frontend.py new file mode 100644 index 00000000..2499c628 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/frontend.py @@ -0,0 +1,1065 @@ +# $Id: frontend.py 9540 2024-02-17 10:36:59Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Command-line and common processing for Docutils front-end tools. + +This module is provisional. +Major changes will happen with the switch from the deprecated +"optparse" module to "arparse". + +Applications should use the high-level API provided by `docutils.core`. +See https://docutils.sourceforge.io/docs/api/runtime-settings.html. + +Exports the following classes: + +* `OptionParser`: Standard Docutils command-line processing. + Deprecated. Will be replaced by an ArgumentParser. +* `Option`: Customized version of `optparse.Option`; validation support. + Deprecated. Will be removed. +* `Values`: Runtime settings; objects are simple structs + (``object.attribute``). Supports cumulative list settings (attributes). + Deprecated. Will be removed. +* `ConfigParser`: Standard Docutils config file processing. + Provisional. Details will change. + +Also exports the following functions: + +Interface function: + `get_default_settings()`. New in 0.19. + +Option callbacks: + `store_multiple()`, `read_config_file()`. Deprecated. + +Setting validators: + `validate_encoding()`, `validate_encoding_error_handler()`, + `validate_encoding_and_error_handler()`, + `validate_boolean()`, `validate_ternary()`, + `validate_nonnegative_int()`, `validate_threshold()`, + `validate_colon_separated_string_list()`, + `validate_comma_separated_list()`, + `validate_url_trailing_slash()`, + `validate_dependency_file()`, + `validate_strip_class()` + `validate_smartquotes_locales()`. + + Provisional. + +Misc: + `make_paths_absolute()`, `filter_settings_spec()`. Provisional. +""" + +__docformat__ = 'reStructuredText' + + +import codecs +import configparser +import optparse +from optparse import SUPPRESS_HELP +import os +import os.path +from pathlib import Path +import sys +import warnings + +import docutils +from docutils import io, utils + + +def store_multiple(option, opt, value, parser, *args, **kwargs): + """ + Store multiple values in `parser.values`. (Option callback.) + + Store `None` for each attribute named in `args`, and store the value for + each key (attribute name) in `kwargs`. + """ + for attribute in args: + setattr(parser.values, attribute, None) + for key, value in kwargs.items(): + setattr(parser.values, key, value) + + +def read_config_file(option, opt, value, parser): + """ + Read a configuration file during option processing. (Option callback.) + """ + try: + new_settings = parser.get_config_file_settings(value) + except ValueError as err: + parser.error(err) + parser.values.update(new_settings, parser) + + +def validate_encoding(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + if value == '': + return None # allow overwriting a config file value + try: + codecs.lookup(value) + except LookupError: + raise LookupError('setting "%s": unknown encoding: "%s"' + % (setting, value)) + return value + + +def validate_encoding_error_handler(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + try: + codecs.lookup_error(value) + except LookupError: + raise LookupError( + 'unknown encoding error handler: "%s" (choices: ' + '"strict", "ignore", "replace", "backslashreplace", ' + '"xmlcharrefreplace", and possibly others; see documentation for ' + 'the Python ``codecs`` module)' % value) + return value + + +def validate_encoding_and_error_handler( + setting, value, option_parser, config_parser=None, config_section=None): + """ + Side-effect: if an error handler is included in the value, it is inserted + into the appropriate place as if it were a separate setting/option. + """ + if ':' in value: + encoding, handler = value.split(':') + validate_encoding_error_handler(handler) + if config_parser: + config_parser.set(config_section, setting + '_error_handler', + handler) + else: + setattr(option_parser.values, setting + '_error_handler', handler) + else: + encoding = value + return validate_encoding(encoding) + + +def validate_boolean(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + """Check/normalize boolean settings: + True: '1', 'on', 'yes', 'true' + False: '0', 'off', 'no','false', '' + + All arguments except `value` are ignored + (kept for compatibility with "optparse" module). + If there is only one positional argument, it is interpreted as `value`. + """ + if value is None: + value = setting + if isinstance(value, bool): + return value + try: + return OptionParser.booleans[value.strip().lower()] + except KeyError: + raise LookupError('unknown boolean value: "%s"' % value) + + +def validate_ternary(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + """Check/normalize three-value settings: + True: '1', 'on', 'yes', 'true' + False: '0', 'off', 'no','false', '' + any other value: returned as-is. + + All arguments except `value` are ignored + (kept for compatibility with "optparse" module). + If there is only one positional argument, it is interpreted as `value`. + """ + if value is None: + value = setting + if isinstance(value, bool) or value is None: + return value + try: + return OptionParser.booleans[value.strip().lower()] + except KeyError: + return value + + +def validate_nonnegative_int(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + value = int(value) + if value < 0: + raise ValueError('negative value; must be positive or zero') + return value + + +def validate_threshold(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + try: + return int(value) + except ValueError: + try: + return OptionParser.thresholds[value.lower()] + except (KeyError, AttributeError): + raise LookupError('unknown threshold: %r.' % value) + + +def validate_colon_separated_string_list( + setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + if not isinstance(value, list): + value = value.split(':') + else: + last = value.pop() + value.extend(last.split(':')) + return value + + +def validate_comma_separated_list(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + """Check/normalize list arguments (split at "," and strip whitespace). + + All arguments except `value` are ignored + (kept for compatibility with "optparse" module). + If there is only one positional argument, it is interpreted as `value`. + """ + if value is None: + value = setting + # `value` may be ``bytes``, ``str``, or a ``list`` (when given as + # command line option and "action" is "append"). + if not isinstance(value, list): + value = [value] + # this function is called for every option added to `value` + # -> split the last item and append the result: + last = value.pop() + items = [i.strip(' \t\n') for i in last.split(',') if i.strip(' \t\n')] + value.extend(items) + return value + + +def validate_math_output(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + """Check "math-output" setting, return list with "format" and "options". + + See also https://docutils.sourceforge.io/docs/user/config.html#math-output + + Argument list for compatibility with "optparse" module. + All arguments except `value` are ignored. + If there is only one positional argument, it is interpreted as `value`. + """ + if value is None: + value = setting + + formats = ('html', 'latex', 'mathml', 'mathjax') + tex2mathml_converters = ('', 'latexml', 'ttm', 'blahtexml', 'pandoc') + + if not value: + return [] + values = value.split(maxsplit=1) + format = values[0].lower() + try: + options = values[1] + except IndexError: + options = '' + if format not in formats: + raise LookupError(f'Unknown math output format: "{value}",\n' + f' choose from {formats}.') + if format == 'mathml': + converter = options.lower() + if converter not in tex2mathml_converters: + raise LookupError(f'MathML converter "{options}" not supported,\n' + f' choose from {tex2mathml_converters}.') + options = converter + return [format, options] + + +def validate_url_trailing_slash(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + if not value: + return './' + elif value.endswith('/'): + return value + else: + return value + '/' + + +def validate_dependency_file(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + try: + return utils.DependencyList(value) + except OSError: + # TODO: warn/info? + return utils.DependencyList(None) + + +def validate_strip_class(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + # All arguments except `value` are ignored + # (kept for compatibility with "optparse" module). + # If there is only one positional argument, it is interpreted as `value`. + if value is None: + value = setting + # value is a comma separated string list: + value = validate_comma_separated_list(value) + # validate list elements: + for cls in value: + normalized = docutils.nodes.make_id(cls) + if cls != normalized: + raise ValueError('Invalid class value %r (perhaps %r?)' + % (cls, normalized)) + return value + + +def validate_smartquotes_locales(setting, value=None, option_parser=None, + config_parser=None, config_section=None): + """Check/normalize a comma separated list of smart quote definitions. + + Return a list of (language-tag, quotes) string tuples. + + All arguments except `value` are ignored + (kept for compatibility with "optparse" module). + If there is only one positional argument, it is interpreted as `value`. + """ + if value is None: + value = setting + # value is a comma separated string list: + value = validate_comma_separated_list(value) + # validate list elements + lc_quotes = [] + for item in value: + try: + lang, quotes = item.split(':', 1) + except AttributeError: + # this function is called for every option added to `value` + # -> ignore if already a tuple: + lc_quotes.append(item) + continue + except ValueError: + raise ValueError('Invalid value "%s".' + ' Format is "<language>:<quotes>".' + % item.encode('ascii', 'backslashreplace')) + # parse colon separated string list: + quotes = quotes.strip() + multichar_quotes = quotes.split(':') + if len(multichar_quotes) == 4: + quotes = multichar_quotes + elif len(quotes) != 4: + raise ValueError('Invalid value "%s". Please specify 4 quotes\n' + ' (primary open/close; secondary open/close).' + % item.encode('ascii', 'backslashreplace')) + lc_quotes.append((lang, quotes)) + return lc_quotes + + +def make_paths_absolute(pathdict, keys, base_path=None): + """ + Interpret filesystem path settings relative to the `base_path` given. + + Paths are values in `pathdict` whose keys are in `keys`. Get `keys` from + `OptionParser.relative_path_settings`. + """ + if base_path is None: + base_path = Path.cwd() + else: + base_path = Path(base_path) + for key in keys: + if key in pathdict: + value = pathdict[key] + if isinstance(value, list): + value = [str((base_path/path).resolve()) for path in value] + elif value: + value = str((base_path/value).resolve()) + pathdict[key] = value + + +def make_one_path_absolute(base_path, path): + # deprecated, will be removed + warnings.warn('frontend.make_one_path_absolute() will be removed ' + 'in Docutils 0.23.', DeprecationWarning, stacklevel=2) + return os.path.abspath(os.path.join(base_path, path)) + + +def filter_settings_spec(settings_spec, *exclude, **replace): + """Return a copy of `settings_spec` excluding/replacing some settings. + + `settings_spec` is a tuple of configuration settings + (cf. `docutils.SettingsSpec.settings_spec`). + + Optional positional arguments are names of to-be-excluded settings. + Keyword arguments are option specification replacements. + (See the html4strict writer for an example.) + """ + settings = list(settings_spec) + # every third item is a sequence of option tuples + for i in range(2, len(settings), 3): + newopts = [] + for opt_spec in settings[i]: + # opt_spec is ("<help>", [<option strings>], {<keyword args>}) + opt_name = [opt_string[2:].replace('-', '_') + for opt_string in opt_spec[1] + if opt_string.startswith('--')][0] + if opt_name in exclude: + continue + if opt_name in replace.keys(): + newopts.append(replace[opt_name]) + else: + newopts.append(opt_spec) + settings[i] = tuple(newopts) + return tuple(settings) + + +class Values(optparse.Values): + """Storage for option values. + + Updates list attributes by extension rather than by replacement. + Works in conjunction with the `OptionParser.lists` instance attribute. + + Deprecated. Will be removed. + """ + + def __init__(self, *args, **kwargs): + warnings.warn('frontend.Values class will be removed ' + 'in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + super().__init__(*args, **kwargs) + if getattr(self, 'record_dependencies', None) is None: + # Set up dummy dependency list. + self.record_dependencies = utils.DependencyList() + + def update(self, other_dict, option_parser): + if isinstance(other_dict, Values): + other_dict = other_dict.__dict__ + other_dict = dict(other_dict) # also works with ConfigParser sections + for setting in option_parser.lists.keys(): + if hasattr(self, setting) and setting in other_dict: + value = getattr(self, setting) + if value: + value += other_dict[setting] + del other_dict[setting] + self._update_loose(other_dict) + + def copy(self): + """Return a shallow copy of `self`.""" + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + return self.__class__(defaults=self.__dict__) + + def setdefault(self, name, default): + """Return ``self.name`` or ``default``. + + If ``self.name`` is unset, set ``self.name = default``. + """ + if getattr(self, name, None) is None: + setattr(self, name, default) + return getattr(self, name) + + +class Option(optparse.Option): + """Add validation and override support to `optparse.Option`. + + Deprecated. Will be removed. + """ + + ATTRS = optparse.Option.ATTRS + ['validator', 'overrides'] + + def __init__(self, *args, **kwargs): + warnings.warn('The frontend.Option class will be removed ' + 'in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + super().__init__(*args, **kwargs) + + def process(self, opt, value, values, parser): + """ + Call the validator function on applicable settings and + evaluate the 'overrides' option. + Extends `optparse.Option.process`. + """ + result = super().process(opt, value, values, parser) + setting = self.dest + if setting: + if self.validator: + value = getattr(values, setting) + try: + new_value = self.validator(setting, value, parser) + except Exception as err: + raise optparse.OptionValueError( + 'Error in option "%s":\n %s' + % (opt, io.error_string(err))) + setattr(values, setting, new_value) + if self.overrides: + setattr(values, self.overrides, None) + return result + + +class OptionParser(optparse.OptionParser, docutils.SettingsSpec): + """ + Settings parser for command-line and library use. + + The `settings_spec` specification here and in other Docutils components + are merged to build the set of command-line options and runtime settings + for this process. + + Common settings (defined below) and component-specific settings must not + conflict. Short options are reserved for common settings, and components + are restricted to using long options. + + Deprecated. + Will be replaced by a subclass of `argparse.ArgumentParser`. + """ + + standard_config_files = [ + '/etc/docutils.conf', # system-wide + './docutils.conf', # project-specific + '~/.docutils'] # user-specific + """Docutils configuration files, using ConfigParser syntax. + + Filenames will be tilde-expanded later. Later files override earlier ones. + """ + + threshold_choices = 'info 1 warning 2 error 3 severe 4 none 5'.split() + """Possible inputs for for --report and --halt threshold values.""" + + thresholds = {'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5} + """Lookup table for --report and --halt threshold values.""" + + booleans = {'1': True, 'on': True, 'yes': True, 'true': True, '0': False, + 'off': False, 'no': False, 'false': False, '': False} + """Lookup table for boolean configuration file settings.""" + + default_error_encoding = (getattr(sys.stderr, 'encoding', None) + or io._locale_encoding # noqa + or 'ascii') + + default_error_encoding_error_handler = 'backslashreplace' + + settings_spec = ( + 'General Docutils Options', + None, + (('Output destination name. Obsoletes the <destination> ' + 'positional argument. Default: None (stdout).', + ['--output'], {'metavar': '<destination>'}), + ('Specify the document title as metadata.', + ['--title'], {'metavar': '<title>'}), + ('Include a "Generated by Docutils" credit and link.', + ['--generator', '-g'], {'action': 'store_true', + 'validator': validate_boolean}), + ('Do not include a generator credit.', + ['--no-generator'], {'action': 'store_false', 'dest': 'generator'}), + ('Include the date at the end of the document (UTC).', + ['--date', '-d'], {'action': 'store_const', 'const': '%Y-%m-%d', + 'dest': 'datestamp'}), + ('Include the time & date (UTC).', + ['--time', '-t'], {'action': 'store_const', + 'const': '%Y-%m-%d %H:%M UTC', + 'dest': 'datestamp'}), + ('Do not include a datestamp of any kind.', + ['--no-datestamp'], {'action': 'store_const', 'const': None, + 'dest': 'datestamp'}), + ('Base directory for absolute paths when reading ' + 'from the local filesystem. Default "/".', + ['--root-prefix'], + {'default': '/', 'metavar': '<path>'}), + ('Include a "View document source" link.', + ['--source-link', '-s'], {'action': 'store_true', + 'validator': validate_boolean}), + ('Use <URL> for a source link; implies --source-link.', + ['--source-url'], {'metavar': '<URL>'}), + ('Do not include a "View document source" link.', + ['--no-source-link'], + {'action': 'callback', 'callback': store_multiple, + 'callback_args': ('source_link', 'source_url')}), + ('Link from section headers to TOC entries. (default)', + ['--toc-entry-backlinks'], + {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'entry', + 'default': 'entry'}), + ('Link from section headers to the top of the TOC.', + ['--toc-top-backlinks'], + {'dest': 'toc_backlinks', 'action': 'store_const', 'const': 'top'}), + ('Disable backlinks to the table of contents.', + ['--no-toc-backlinks'], + {'dest': 'toc_backlinks', 'action': 'store_false'}), + ('Link from footnotes/citations to references. (default)', + ['--footnote-backlinks'], + {'action': 'store_true', 'default': True, + 'validator': validate_boolean}), + ('Disable backlinks from footnotes and citations.', + ['--no-footnote-backlinks'], + {'dest': 'footnote_backlinks', 'action': 'store_false'}), + ('Enable section numbering by Docutils. (default)', + ['--section-numbering'], + {'action': 'store_true', 'dest': 'sectnum_xform', + 'default': True, 'validator': validate_boolean}), + ('Disable section numbering by Docutils.', + ['--no-section-numbering'], + {'action': 'store_false', 'dest': 'sectnum_xform'}), + ('Remove comment elements from the document tree.', + ['--strip-comments'], + {'action': 'store_true', 'validator': validate_boolean}), + ('Leave comment elements in the document tree. (default)', + ['--leave-comments'], + {'action': 'store_false', 'dest': 'strip_comments'}), + ('Remove all elements with classes="<class>" from the document tree. ' + 'Warning: potentially dangerous; use with caution. ' + '(Multiple-use option.)', + ['--strip-elements-with-class'], + {'action': 'append', 'dest': 'strip_elements_with_classes', + 'metavar': '<class>', 'validator': validate_strip_class}), + ('Remove all classes="<class>" attributes from elements in the ' + 'document tree. Warning: potentially dangerous; use with caution. ' + '(Multiple-use option.)', + ['--strip-class'], + {'action': 'append', 'dest': 'strip_classes', + 'metavar': '<class>', 'validator': validate_strip_class}), + ('Report system messages at or higher than <level>: "info" or "1", ' + '"warning"/"2" (default), "error"/"3", "severe"/"4", "none"/"5"', + ['--report', '-r'], {'choices': threshold_choices, 'default': 2, + 'dest': 'report_level', 'metavar': '<level>', + 'validator': validate_threshold}), + ('Report all system messages. (Same as "--report=1".)', + ['--verbose', '-v'], {'action': 'store_const', 'const': 1, + 'dest': 'report_level'}), + ('Report no system messages. (Same as "--report=5".)', + ['--quiet', '-q'], {'action': 'store_const', 'const': 5, + 'dest': 'report_level'}), + ('Halt execution at system messages at or above <level>. ' + 'Levels as in --report. Default: 4 (severe).', + ['--halt'], {'choices': threshold_choices, 'dest': 'halt_level', + 'default': 4, 'metavar': '<level>', + 'validator': validate_threshold}), + ('Halt at the slightest problem. Same as "--halt=info".', + ['--strict'], {'action': 'store_const', 'const': 1, + 'dest': 'halt_level'}), + ('Enable a non-zero exit status for non-halting system messages at ' + 'or above <level>. Default: 5 (disabled).', + ['--exit-status'], {'choices': threshold_choices, + 'dest': 'exit_status_level', + 'default': 5, 'metavar': '<level>', + 'validator': validate_threshold}), + ('Enable debug-level system messages and diagnostics.', + ['--debug'], {'action': 'store_true', + 'validator': validate_boolean}), + ('Disable debug output. (default)', + ['--no-debug'], {'action': 'store_false', 'dest': 'debug'}), + ('Send the output of system messages to <file>.', + ['--warnings'], {'dest': 'warning_stream', 'metavar': '<file>'}), + ('Enable Python tracebacks when Docutils is halted.', + ['--traceback'], {'action': 'store_true', 'default': None, + 'validator': validate_boolean}), + ('Disable Python tracebacks. (default)', + ['--no-traceback'], {'dest': 'traceback', 'action': 'store_false'}), + ('Specify the encoding and optionally the ' + 'error handler of input text. Default: <auto-detect>:strict.', + ['--input-encoding', '-i'], + {'metavar': '<name[:handler]>', + 'validator': validate_encoding_and_error_handler}), + ('Specify the error handler for undecodable characters. ' + 'Choices: "strict" (default), "ignore", and "replace".', + ['--input-encoding-error-handler'], + {'default': 'strict', 'validator': validate_encoding_error_handler}), + ('Specify the text encoding and optionally the error handler for ' + 'output. Default: utf-8:strict.', + ['--output-encoding', '-o'], + {'metavar': '<name[:handler]>', 'default': 'utf-8', + 'validator': validate_encoding_and_error_handler}), + ('Specify error handler for unencodable output characters; ' + '"strict" (default), "ignore", "replace", ' + '"xmlcharrefreplace", "backslashreplace".', + ['--output-encoding-error-handler'], + {'default': 'strict', 'validator': validate_encoding_error_handler}), + ('Specify text encoding and optionally error handler ' + 'for error output. Default: %s:%s.' + % (default_error_encoding, default_error_encoding_error_handler), + ['--error-encoding', '-e'], + {'metavar': '<name[:handler]>', 'default': default_error_encoding, + 'validator': validate_encoding_and_error_handler}), + ('Specify the error handler for unencodable characters in ' + 'error output. Default: %s.' + % default_error_encoding_error_handler, + ['--error-encoding-error-handler'], + {'default': default_error_encoding_error_handler, + 'validator': validate_encoding_error_handler}), + ('Specify the language (as BCP 47 language tag). Default: en.', + ['--language', '-l'], {'dest': 'language_code', 'default': 'en', + 'metavar': '<name>'}), + ('Write output file dependencies to <file>.', + ['--record-dependencies'], + {'metavar': '<file>', 'validator': validate_dependency_file, + 'default': None}), # default set in Values class + ('Read configuration settings from <file>, if it exists.', + ['--config'], {'metavar': '<file>', 'type': 'string', + 'action': 'callback', 'callback': read_config_file}), + ("Show this program's version number and exit.", + ['--version', '-V'], {'action': 'version'}), + ('Show this help message and exit.', + ['--help', '-h'], {'action': 'help'}), + # Typically not useful for non-programmatical use: + (SUPPRESS_HELP, ['--id-prefix'], {'default': ''}), + (SUPPRESS_HELP, ['--auto-id-prefix'], {'default': '%'}), + # Hidden options, for development use only: + (SUPPRESS_HELP, ['--dump-settings'], {'action': 'store_true'}), + (SUPPRESS_HELP, ['--dump-internals'], {'action': 'store_true'}), + (SUPPRESS_HELP, ['--dump-transforms'], {'action': 'store_true'}), + (SUPPRESS_HELP, ['--dump-pseudo-xml'], {'action': 'store_true'}), + (SUPPRESS_HELP, ['--expose-internal-attribute'], + {'action': 'append', 'dest': 'expose_internals', + 'validator': validate_colon_separated_string_list}), + (SUPPRESS_HELP, ['--strict-visitor'], {'action': 'store_true'}), + )) + """Runtime settings and command-line options common to all Docutils front + ends. Setting specs specific to individual Docutils components are also + used (see `populate_from_components()`).""" + + settings_defaults = {'_disable_config': None, + '_source': None, + '_destination': None, + '_config_files': None} + """Defaults for settings without command-line option equivalents. + + See https://docutils.sourceforge.io/docs/user/config.html#internal-settings + """ + + config_section = 'general' + + version_template = ('%%prog (Docutils %s%s, Python %s, on %s)' + % (docutils.__version__, + docutils.__version_details__ + and ' [%s]'%docutils.__version_details__ or '', + sys.version.split()[0], sys.platform)) + """Default version message.""" + + def __init__(self, components=(), defaults=None, read_config_files=False, + *args, **kwargs): + """Set up OptionParser instance. + + `components` is a list of Docutils components each containing a + ``.settings_spec`` attribute. + `defaults` is a mapping of setting default overrides. + """ + + self.lists = {} + """Set of list-type settings.""" + + self.config_files = [] + """List of paths of applied configuration files.""" + + self.relative_path_settings = ['warning_stream'] # will be modified + + warnings.warn('The frontend.OptionParser class will be replaced ' + 'by a subclass of argparse.ArgumentParser ' + 'in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + super().__init__(option_class=Option, add_help_option=None, + formatter=optparse.TitledHelpFormatter(width=78), + *args, **kwargs) + if not self.version: + self.version = self.version_template + self.components = (self, *components) + self.populate_from_components(self.components) + self.defaults.update(defaults or {}) + if read_config_files and not self.defaults['_disable_config']: + try: + config_settings = self.get_standard_config_settings() + except ValueError as err: + self.error(err) + self.defaults.update(config_settings.__dict__) + + def populate_from_components(self, components): + """Collect settings specification from components. + + For each component, populate from the `SettingsSpec.settings_spec` + structure, then from the `SettingsSpec.settings_defaults` dictionary. + After all components have been processed, check for and populate from + each component's `SettingsSpec.settings_default_overrides` dictionary. + """ + for component in components: + if component is None: + continue + settings_spec = component.settings_spec + self.relative_path_settings.extend( + component.relative_path_settings) + for i in range(0, len(settings_spec), 3): + title, description, option_spec = settings_spec[i:i+3] + if title: + group = optparse.OptionGroup(self, title, description) + self.add_option_group(group) + else: + group = self # single options + for (help_text, option_strings, kwargs) in option_spec: + option = group.add_option(help=help_text, *option_strings, + **kwargs) + if kwargs.get('action') == 'append': + self.lists[option.dest] = True + if component.settings_defaults: + self.defaults.update(component.settings_defaults) + for component in components: + if component and component.settings_default_overrides: + self.defaults.update(component.settings_default_overrides) + + @classmethod + def get_standard_config_files(cls): + """Return list of config files, from environment or standard.""" + if 'DOCUTILSCONFIG' in os.environ: + config_files = os.environ['DOCUTILSCONFIG'].split(os.pathsep) + else: + config_files = cls.standard_config_files + return [os.path.expanduser(f) for f in config_files if f.strip()] + + def get_standard_config_settings(self): + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + settings = Values() + for filename in self.get_standard_config_files(): + settings.update(self.get_config_file_settings(filename), self) + return settings + + def get_config_file_settings(self, config_file): + """Returns a dictionary containing appropriate config file settings.""" + config_parser = ConfigParser() + # parse config file, add filename if found and successfully read. + applied = set() + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + self.config_files += config_parser.read(config_file, self) + settings = Values() + for component in self.components: + if not component: + continue + for section in (tuple(component.config_section_dependencies or ()) + + (component.config_section,)): + if section in applied: + continue + applied.add(section) + if config_parser.has_section(section): + settings.update(config_parser[section], self) + make_paths_absolute(settings.__dict__, + self.relative_path_settings, + os.path.dirname(config_file)) + return settings.__dict__ + + def check_values(self, values, args): + """Store positional arguments as runtime settings.""" + values._source, values._destination = self.check_args(args) + make_paths_absolute(values.__dict__, self.relative_path_settings) + values._config_files = self.config_files + return values + + def check_args(self, args): + source = destination = None + if args: + source = args.pop(0) + if source == '-': # means stdin + source = None + if args: + destination = args.pop(0) + if destination == '-': # means stdout + destination = None + if args: + self.error('Maximum 2 arguments allowed.') + if source and source == destination: + self.error('Do not specify the same file for both source and ' + 'destination. It will clobber the source file.') + return source, destination + + def set_defaults_from_dict(self, defaults): + # deprecated, will be removed + warnings.warn('OptionParser.set_defaults_from_dict() will be removed ' + 'in Docutils 0.22 or with the switch to ArgumentParser.', + DeprecationWarning, stacklevel=2) + self.defaults.update(defaults) + + def get_default_values(self): + """Needed to get custom `Values` instances.""" + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + defaults = Values(self.defaults) + defaults._config_files = self.config_files + return defaults + + def get_option_by_dest(self, dest): + """ + Get an option by its dest. + + If you're supplying a dest which is shared by several options, + it is undefined which option of those is returned. + + A KeyError is raised if there is no option with the supplied + dest. + """ + for group in self.option_groups + [self]: + for option in group.option_list: + if option.dest == dest: + return option + raise KeyError('No option with dest == %r.' % dest) + + +class ConfigParser(configparser.RawConfigParser): + """Parser for Docutils configuration files. + + See https://docutils.sourceforge.io/docs/user/config.html. + + Option key normalization includes conversion of '-' to '_'. + + Config file encoding is "utf-8". Encoding errors are reported + and the affected file(s) skipped. + + This class is provisional and will change in future versions. + """ + + old_settings = { + 'pep_stylesheet': ('pep_html writer', 'stylesheet'), + 'pep_stylesheet_path': ('pep_html writer', 'stylesheet_path'), + 'pep_template': ('pep_html writer', 'template')} + """{old setting: (new section, new setting)} mapping, used by + `handle_old_config`, to convert settings from the old [options] section. + """ + + old_warning = ( + 'The "[option]" section is deprecated.\n' + 'Support for old-format configuration files will be removed in ' + 'Docutils 2.0. Please revise your configuration files. ' + 'See <https://docutils.sourceforge.io/docs/user/config.html>, ' + 'section "Old-Format Configuration Files".') + + not_utf8_error = """\ +Unable to read configuration file "%s": content not encoded as UTF-8. +Skipping "%s" configuration file. +""" + + def read(self, filenames, option_parser=None): + # Currently, if a `docutils.frontend.OptionParser` instance is + # supplied, setting values are validated. + if option_parser is not None: + warnings.warn('frontend.ConfigParser.read(): parameter ' + '"option_parser" will be removed ' + 'in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + read_ok = [] + if isinstance(filenames, str): + filenames = [filenames] + for filename in filenames: + # Config files are UTF-8-encoded: + try: + read_ok += super().read(filename, encoding='utf-8') + except UnicodeDecodeError: + sys.stderr.write(self.not_utf8_error % (filename, filename)) + continue + if 'options' in self: + self.handle_old_config(filename) + if option_parser is not None: + self.validate_settings(filename, option_parser) + return read_ok + + def handle_old_config(self, filename): + warnings.warn_explicit(self.old_warning, ConfigDeprecationWarning, + filename, 0) + options = self.get_section('options') + if not self.has_section('general'): + self.add_section('general') + for key, value in options.items(): + if key in self.old_settings: + section, setting = self.old_settings[key] + if not self.has_section(section): + self.add_section(section) + else: + section = 'general' + setting = key + if not self.has_option(section, setting): + self.set(section, setting, value) + self.remove_section('options') + + def validate_settings(self, filename, option_parser): + """ + Call the validator function and implement overrides on all applicable + settings. + """ + for section in self.sections(): + for setting in self.options(section): + try: + option = option_parser.get_option_by_dest(setting) + except KeyError: + continue + if option.validator: + value = self.get(section, setting) + try: + new_value = option.validator( + setting, value, option_parser, + config_parser=self, config_section=section) + except Exception as err: + raise ValueError(f'Error in config file "{filename}", ' + f'section "[{section}]":\n' + f' {io.error_string(err)}\n' + f' {setting} = {value}') + self.set(section, setting, new_value) + if option.overrides: + self.set(section, option.overrides, None) + + def optionxform(self, optionstr): + """ + Lowercase and transform '-' to '_'. + + So the cmdline form of option names can be used in config files. + """ + return optionstr.lower().replace('-', '_') + + def get_section(self, section): + """ + Return a given section as a dictionary. + + Return empty dictionary if the section doesn't exist. + + Deprecated. Use the configparser "Mapping Protocol Access" and + catch KeyError. + """ + warnings.warn('frontend.OptionParser.get_section() ' + 'will be removed in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + try: + return dict(self[section]) + except KeyError: + return {} + + +class ConfigDeprecationWarning(FutureWarning): + """Warning for deprecated configuration file features.""" + + +def get_default_settings(*components): + """Return default runtime settings for `components`. + + Return a `frontend.Values` instance with defaults for generic Docutils + settings and settings from the `components` (`SettingsSpec` instances). + + This corresponds to steps 1 and 2 in the `runtime settings priority`__. + + __ https://docutils.sourceforge.io/docs/api/runtime-settings.html + #settings-priority + """ + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + return OptionParser(components).get_default_values() diff --git a/.venv/lib/python3.12/site-packages/docutils/io.py b/.venv/lib/python3.12/site-packages/docutils/io.py new file mode 100644 index 00000000..6237c66a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/io.py @@ -0,0 +1,637 @@ +# $Id: io.py 9427 2023-07-07 06:50:09Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +I/O classes provide a uniform API for low-level input and output. Subclasses +exist for a variety of input/output mechanisms. +""" + +__docformat__ = 'reStructuredText' + +import codecs +import locale +import os +import re +import sys +import warnings + +from docutils import TransformSpec + + +# Guess the locale's preferred encoding. +# If no valid guess can be made, _locale_encoding is set to `None`: +# +# TODO: check whether this is set correctly with every OS and Python version +# or whether front-end tools need to call `locale.setlocale()` +# before importing this module +try: + # Return locale encoding also in UTF-8 mode + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + _locale_encoding = (locale.getlocale()[1] + or locale.getdefaultlocale()[1]) + _locale_encoding = _locale_encoding.lower() +except: # noqa any other problems determining the locale -> use None + _locale_encoding = None +try: + codecs.lookup(_locale_encoding) +except (LookupError, TypeError): + _locale_encoding = None + + +class InputError(OSError): pass +class OutputError(OSError): pass + + +def check_encoding(stream, encoding): + """Test, whether the encoding of `stream` matches `encoding`. + + Returns + + :None: if `encoding` or `stream.encoding` are not a valid encoding + argument (e.g. ``None``) or `stream.encoding is missing. + :True: if the encoding argument resolves to the same value as `encoding`, + :False: if the encodings differ. + """ + try: + return codecs.lookup(stream.encoding) == codecs.lookup(encoding) + except (LookupError, AttributeError, TypeError): + return None + + +def error_string(err): + """Return string representation of Exception `err`. + """ + return f'{err.__class__.__name__}: {err}' + + +class Input(TransformSpec): + """ + Abstract base class for input wrappers. + + Docutils input objects must provide a `read()` method that + returns the source, typically as `str` instance. + + Inheriting `TransformSpec` allows input objects to add + "transforms" and "unknown_reference_resolvers" to the "Transformer". + (Optional for custom input objects since Docutils 0.19.) + """ + + component_type = 'input' + + default_source_path = None + + def __init__(self, source=None, source_path=None, encoding=None, + error_handler='strict'): + self.encoding = encoding + """Text encoding for the input source.""" + + self.error_handler = error_handler + """Text decoding error handler.""" + + self.source = source + """The source of input data.""" + + self.source_path = source_path + """A text reference to the source.""" + + if not source_path: + self.source_path = self.default_source_path + + self.successful_encoding = None + """The encoding that successfully decoded the source data.""" + + def __repr__(self): + return '%s: source=%r, source_path=%r' % (self.__class__, self.source, + self.source_path) + + def read(self): + """Return input as `str`. Define in subclasses.""" + raise NotImplementedError + + def decode(self, data): + """ + Decode `data` if required. + + Return Unicode `str` instances unchanged (nothing to decode). + + If `self.encoding` is None, determine encoding from data + or try UTF-8 and the locale's preferred encoding. + The client application should call ``locale.setlocale()`` at the + beginning of processing:: + + locale.setlocale(locale.LC_ALL, '') + + Raise UnicodeError if unsuccessful. + + Provisional: encoding detection will be removed in Docutils 1.0. + """ + if self.encoding and self.encoding.lower() == 'unicode': + assert isinstance(data, str), ('input encoding is "unicode" ' + 'but `data` is no `str` instance') + if isinstance(data, str): + # nothing to decode + return data + if self.encoding: + # We believe the user/application when the encoding is + # explicitly given. + encoding_candidates = [self.encoding] + else: + data_encoding = self.determine_encoding_from_data(data) + if data_encoding: + # `data` declares its encoding with "magic comment" or BOM, + encoding_candidates = [data_encoding] + else: + # Apply heuristics if the encoding is not specified. + # Start with UTF-8, because that only matches + # data that *IS* UTF-8: + encoding_candidates = ['utf-8'] + # If UTF-8 fails, fall back to the locale's preferred encoding: + fallback = locale.getpreferredencoding(do_setlocale=False) + if fallback and fallback.lower() != 'utf-8': + encoding_candidates.append(fallback) + for enc in encoding_candidates: + try: + decoded = str(data, enc, self.error_handler) + self.successful_encoding = enc + return decoded + except (UnicodeError, LookupError) as err: + # keep exception instance for use outside of the "for" loop. + error = err + raise UnicodeError( + 'Unable to decode input data. Tried the following encodings: ' + f'{", ".join(repr(enc) for enc in encoding_candidates)}.\n' + f'({error_string(error)})') + + coding_slug = re.compile(br"coding[:=]\s*([-\w.]+)") + """Encoding declaration pattern.""" + + byte_order_marks = ((codecs.BOM_UTF32_BE, 'utf-32'), + (codecs.BOM_UTF32_LE, 'utf-32'), + (codecs.BOM_UTF8, 'utf-8-sig'), + (codecs.BOM_UTF16_BE, 'utf-16'), + (codecs.BOM_UTF16_LE, 'utf-16'), + ) + """Sequence of (start_bytes, encoding) tuples for encoding detection. + The first bytes of input data are checked against the start_bytes strings. + A match indicates the given encoding.""" + + def determine_encoding_from_data(self, data): + """ + Try to determine the encoding of `data` by looking *in* `data`. + Check for a byte order mark (BOM) or an encoding declaration. + """ + # check for a byte order mark: + for start_bytes, encoding in self.byte_order_marks: + if data.startswith(start_bytes): + return encoding + # check for an encoding declaration pattern in first 2 lines of file: + for line in data.splitlines()[:2]: + match = self.coding_slug.search(line) + if match: + return match.group(1).decode('ascii') + return None + + def isatty(self): + """Return True, if the input source is connected to a TTY device.""" + try: + return self.source.isatty() + except AttributeError: + return False + + +class Output(TransformSpec): + """ + Abstract base class for output wrappers. + + Docutils output objects must provide a `write()` method that + expects and handles one argument (the output). + + Inheriting `TransformSpec` allows output objects to add + "transforms" and "unknown_reference_resolvers" to the "Transformer". + (Optional for custom output objects since Docutils 0.19.) + """ + + component_type = 'output' + + default_destination_path = None + + def __init__(self, destination=None, destination_path=None, + encoding=None, error_handler='strict'): + self.encoding = encoding + """Text encoding for the output destination.""" + + self.error_handler = error_handler or 'strict' + """Text encoding error handler.""" + + self.destination = destination + """The destination for output data.""" + + self.destination_path = destination_path + """A text reference to the destination.""" + + if not destination_path: + self.destination_path = self.default_destination_path + + def __repr__(self): + return ('%s: destination=%r, destination_path=%r' + % (self.__class__, self.destination, self.destination_path)) + + def write(self, data): + """Write `data`. Define in subclasses.""" + raise NotImplementedError + + def encode(self, data): + """ + Encode and return `data`. + + If `data` is a `bytes` instance, it is returned unchanged. + Otherwise it is encoded with `self.encoding`. + + Provisional: If `self.encoding` is set to the pseudo encoding name + "unicode", `data` must be a `str` instance and is returned unchanged. + """ + if self.encoding and self.encoding.lower() == 'unicode': + assert isinstance(data, str), ('output encoding is "unicode" ' + 'but `data` is no `str` instance') + return data + if not isinstance(data, str): + # Non-unicode (e.g. bytes) output. + return data + else: + return data.encode(self.encoding, self.error_handler) + + +class ErrorOutput: + """ + Wrapper class for file-like error streams with + failsafe de- and encoding of `str`, `bytes`, `unicode` and + `Exception` instances. + """ + + def __init__(self, destination=None, encoding=None, + encoding_errors='backslashreplace', + decoding_errors='replace'): + """ + :Parameters: + - `destination`: a file-like object, + a string (path to a file), + `None` (write to `sys.stderr`, default), or + evaluating to `False` (write() requests are ignored). + - `encoding`: `destination` text encoding. Guessed if None. + - `encoding_errors`: how to treat encoding errors. + """ + if destination is None: + destination = sys.stderr + elif not destination: + destination = False + # if `destination` is a file name, open it + elif isinstance(destination, str): + destination = open(destination, 'w') + + self.destination = destination + """Where warning output is sent.""" + + self.encoding = (encoding or getattr(destination, 'encoding', None) + or _locale_encoding or 'ascii') + """The output character encoding.""" + + self.encoding_errors = encoding_errors + """Encoding error handler.""" + + self.decoding_errors = decoding_errors + """Decoding error handler.""" + + def write(self, data): + """ + Write `data` to self.destination. Ignore, if self.destination is False. + + `data` can be a `bytes`, `str`, or `Exception` instance. + """ + if not self.destination: + return + if isinstance(data, Exception): + data = str(data) + try: + self.destination.write(data) + except UnicodeEncodeError: + self.destination.write(data.encode(self.encoding, + self.encoding_errors)) + except TypeError: + if isinstance(data, str): # destination may expect bytes + self.destination.write(data.encode(self.encoding, + self.encoding_errors)) + elif self.destination in (sys.stderr, sys.stdout): + # write bytes to raw stream + self.destination.buffer.write(data) + else: + self.destination.write(str(data, self.encoding, + self.decoding_errors)) + + def close(self): + """ + Close the error-output stream. + + Ignored if the destination is` sys.stderr` or `sys.stdout` or has no + close() method. + """ + if self.destination in (sys.stdout, sys.stderr): + return + try: + self.destination.close() + except AttributeError: + pass + + def isatty(self): + """Return True, if the destination is connected to a TTY device.""" + try: + return self.destination.isatty() + except AttributeError: + return False + + +class FileInput(Input): + + """ + Input for single, simple file-like objects. + """ + def __init__(self, source=None, source_path=None, + encoding=None, error_handler='strict', + autoclose=True, mode='r'): + """ + :Parameters: + - `source`: either a file-like object (which is read directly), or + `None` (which implies `sys.stdin` if no `source_path` given). + - `source_path`: a path to a file, which is opened for reading. + - `encoding`: the expected text encoding of the input file. + - `error_handler`: the encoding error handler to use. + - `autoclose`: close automatically after read (except when + `sys.stdin` is the source). + - `mode`: how the file is to be opened (see standard function + `open`). The default is read only ('r'). + """ + Input.__init__(self, source, source_path, encoding, error_handler) + self.autoclose = autoclose + self._stderr = ErrorOutput() + + if source is None: + if source_path: + try: + self.source = open(source_path, mode, + encoding=self.encoding, + errors=self.error_handler) + except OSError as error: + raise InputError(error.errno, error.strerror, source_path) + else: + self.source = sys.stdin + elif check_encoding(self.source, self.encoding) is False: + # TODO: re-open, warn or raise error? + raise UnicodeError('Encoding clash: encoding given is "%s" ' + 'but source is opened with encoding "%s".' % + (self.encoding, self.source.encoding)) + if not source_path: + try: + self.source_path = self.source.name + except AttributeError: + pass + + def read(self): + """ + Read and decode a single file, return as `str`. + """ + try: + if not self.encoding and hasattr(self.source, 'buffer'): + # read as binary data + data = self.source.buffer.read() + # decode with heuristics + data = self.decode(data) + # normalize newlines + data = '\n'.join(data.splitlines()+['']) + else: + data = self.source.read() + finally: + if self.autoclose: + self.close() + return data + + def readlines(self): + """ + Return lines of a single file as list of strings. + """ + return self.read().splitlines(True) + + def close(self): + if self.source is not sys.stdin: + self.source.close() + + +class FileOutput(Output): + + """Output for single, simple file-like objects.""" + + default_destination_path = '<file>' + + mode = 'w' + """The mode argument for `open()`.""" + # 'wb' for binary (e.g. OpenOffice) files (see also `BinaryFileOutput`). + # (Do not use binary mode ('wb') for text files, as this prevents the + # conversion of newlines to the system specific default.) + + def __init__(self, destination=None, destination_path=None, + encoding=None, error_handler='strict', autoclose=True, + handle_io_errors=None, mode=None): + """ + :Parameters: + - `destination`: either a file-like object (which is written + directly) or `None` (which implies `sys.stdout` if no + `destination_path` given). + - `destination_path`: a path to a file, which is opened and then + written. + - `encoding`: the text encoding of the output file. + - `error_handler`: the encoding error handler to use. + - `autoclose`: close automatically after write (except when + `sys.stdout` or `sys.stderr` is the destination). + - `handle_io_errors`: ignored, deprecated, will be removed. + - `mode`: how the file is to be opened (see standard function + `open`). The default is 'w', providing universal newline + support for text files. + """ + Output.__init__(self, destination, destination_path, + encoding, error_handler) + self.opened = True + self.autoclose = autoclose + if handle_io_errors is not None: + warnings.warn('io.FileOutput: init argument "handle_io_errors" ' + 'is ignored and will be removed in ' + 'Docutils 2.0.', DeprecationWarning, stacklevel=2) + if mode is not None: + self.mode = mode + self._stderr = ErrorOutput() + if destination is None: + if destination_path: + self.opened = False + else: + self.destination = sys.stdout + elif ( # destination is file-type object -> check mode: + mode and hasattr(self.destination, 'mode') + and mode != self.destination.mode): + print('Warning: Destination mode "%s" differs from specified ' + 'mode "%s"' % (self.destination.mode, mode), + file=self._stderr) + if not destination_path: + try: + self.destination_path = self.destination.name + except AttributeError: + pass + + def open(self): + # Specify encoding + if 'b' not in self.mode: + kwargs = {'encoding': self.encoding, + 'errors': self.error_handler} + else: + kwargs = {} + try: + self.destination = open(self.destination_path, self.mode, **kwargs) + except OSError as error: + raise OutputError(error.errno, error.strerror, + self.destination_path) + self.opened = True + + def write(self, data): + """Write `data` to a single file, also return it. + + `data` can be a `str` or `bytes` instance. + If writing `bytes` fails, an attempt is made to write to + the low-level interface ``self.destination.buffer``. + + If `data` is a `str` instance and `self.encoding` and + `self.destination.encoding` are set to different values, `data` + is encoded to a `bytes` instance using `self.encoding`. + + Provisional: future versions may raise an error if `self.encoding` + and `self.destination.encoding` are set to different values. + """ + if not self.opened: + self.open() + if (isinstance(data, str) + and check_encoding(self.destination, self.encoding) is False): + if os.linesep != '\n': + data = data.replace('\n', os.linesep) # fix endings + data = self.encode(data) + + try: + self.destination.write(data) + except TypeError as err: + if isinstance(data, bytes): + try: + self.destination.buffer.write(data) + except AttributeError: + if check_encoding(self.destination, + self.encoding) is False: + raise ValueError( + f'Encoding of {self.destination_path} ' + f'({self.destination.encoding}) differs \n' + f' from specified encoding ({self.encoding})') + else: + raise err + except (UnicodeError, LookupError) as err: + raise UnicodeError( + 'Unable to encode output data. output-encoding is: ' + f'{self.encoding}.\n({error_string(err)})') + finally: + if self.autoclose: + self.close() + return data + + def close(self): + if self.destination not in (sys.stdout, sys.stderr): + self.destination.close() + self.opened = False + + +class BinaryFileOutput(FileOutput): + """ + A version of docutils.io.FileOutput which writes to a binary file. + """ + # Used by core.publish_cmdline_to_binary() which in turn is used by + # tools/rst2odt.py but not by core.rst2odt(). + mode = 'wb' + + +class StringInput(Input): + """Input from a `str` or `bytes` instance.""" + + default_source_path = '<string>' + + def read(self): + """Return the source as `str` instance. + + Decode, if required (see `Input.decode`). + """ + return self.decode(self.source) + + +class StringOutput(Output): + """Output to a `bytes` or `str` instance. + + Provisional. + """ + + default_destination_path = '<string>' + + def write(self, data): + """Store `data` in `self.destination`, and return it. + + If `self.encoding` is set to the pseudo encoding name "unicode", + `data` must be a `str` instance and is stored/returned unchanged + (cf. `Output.encode`). + + Otherwise, `data` can be a `bytes` or `str` instance and is + stored/returned as a `bytes` instance + (`str` data is encoded with `self.encode()`). + + Attention: the `output_encoding`_ setting may affect the content + of the output (e.g. an encoding declaration in HTML or XML or the + representation of characters as LaTeX macro vs. literal character). + """ + self.destination = self.encode(data) + return self.destination + + +class NullInput(Input): + + """Degenerate input: read nothing.""" + + default_source_path = 'null input' + + def read(self): + """Return an empty string.""" + return '' + + +class NullOutput(Output): + + """Degenerate output: write nothing.""" + + default_destination_path = 'null output' + + def write(self, data): + """Do nothing, return None.""" + pass + + +class DocTreeInput(Input): + + """ + Adapter for document tree input. + + The document tree must be passed in the ``source`` parameter. + """ + + default_source_path = 'doctree input' + + def read(self): + """Return the document tree.""" + return self.source diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/__init__.py b/.venv/lib/python3.12/site-packages/docutils/languages/__init__.py new file mode 100644 index 00000000..1bf43129 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/__init__.py @@ -0,0 +1,83 @@ +# $Id: __init__.py 9030 2022-03-05 23:28:32Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# Internationalization details are documented in +# <https://docutils.sourceforge.io/docs/howto/i18n.html>. + +""" +This package contains modules for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +from importlib import import_module + +from docutils.utils import normalize_language_tag + + +class LanguageImporter: + """Import language modules. + + When called with a BCP 47 language tag, instances return a module + with localisations from `docutils.languages` or the PYTHONPATH. + + If there is no matching module, warn (if a `reporter` is passed) + and fall back to English. + """ + packages = ('docutils.languages.', '') + warn_msg = ('Language "%s" not supported: ' + 'Docutils-generated text will be in English.') + fallback = 'en' + # TODO: use a dummy module returning empty strings?, configurable? + + def __init__(self): + self.cache = {} + + def import_from_packages(self, name, reporter=None): + """Try loading module `name` from `self.packages`.""" + module = None + for package in self.packages: + try: + module = import_module(package+name) + self.check_content(module) + except (ImportError, AttributeError): + if reporter and module: + reporter.info(f'{module} is no complete ' + 'Docutils language module.') + elif reporter: + reporter.info(f'Module "{package+name}" not found.') + continue + break + return module + + def check_content(self, module): + """Check if we got a Docutils language module.""" + if not (isinstance(module.labels, dict) + and isinstance(module.bibliographic_fields, dict) + and isinstance(module.author_separators, list)): + raise ImportError + + def __call__(self, language_code, reporter=None): + try: + return self.cache[language_code] + except KeyError: + pass + for tag in normalize_language_tag(language_code): + tag = tag.replace('-', '_') # '-' not valid in module names + module = self.import_from_packages(tag, reporter) + if module is not None: + break + else: + if reporter: + reporter.warning(self.warn_msg % language_code) + if self.fallback: + module = self.import_from_packages(self.fallback) + if reporter and (language_code != 'en'): + reporter.info('Using %s for language "%s".' + % (module, language_code)) + self.cache[language_code] = module + return module + + +get_language = LanguageImporter() diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/af.py b/.venv/lib/python3.12/site-packages/docutils/languages/af.py new file mode 100644 index 00000000..b78f3f7d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/af.py @@ -0,0 +1,58 @@ +# $Id: af.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Jannie Hofmeyr <jhsh@sun.ac.za> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Afrikaans-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Auteur', + 'authors': 'Auteurs', + 'organization': 'Organisasie', + 'address': 'Adres', + 'contact': 'Kontak', + 'version': 'Weergawe', + 'revision': 'Revisie', + 'status': 'Status', + 'date': 'Datum', + 'copyright': 'Kopiereg', + 'dedication': 'Opdrag', + 'abstract': 'Opsomming', + 'attention': 'Aandag!', + 'caution': 'Wees versigtig!', + 'danger': '!GEVAAR!', + 'error': 'Fout', + 'hint': 'Wenk', + 'important': 'Belangrik', + 'note': 'Nota', + 'tip': 'Tip', # hint and tip both have the same translation: wenk + 'warning': 'Waarskuwing', + 'contents': 'Inhoud'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'auteur': 'author', + 'auteurs': 'authors', + 'organisasie': 'organization', + 'adres': 'address', + 'kontak': 'contact', + 'weergawe': 'version', + 'revisie': 'revision', + 'status': 'status', + 'datum': 'date', + 'kopiereg': 'copyright', + 'opdrag': 'dedication', + 'opsomming': 'abstract'} +"""Afrikaans (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/ar.py b/.venv/lib/python3.12/site-packages/docutils/languages/ar.py new file mode 100644 index 00000000..d6aebd0b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/ar.py @@ -0,0 +1,60 @@ +# $Id: fa.py 4564 2016-08-10 11:48:42Z +# Author: Shahin <me@5hah.in> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Arabic-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'المؤلف', + 'authors': 'المؤلفون', + 'organization': 'التنظيم', + 'address': 'العنوان', + 'contact': 'اتصل', + 'version': 'نسخة', + 'revision': 'مراجعة', + 'status': 'الحالة', + 'date': 'تاریخ', + 'copyright': 'الحقوق', + 'dedication': 'إهداء', + 'abstract': 'ملخص', + 'attention': 'تنبيه', + 'caution': 'احتیاط', + 'danger': 'خطر', + 'error': 'خطأ', + 'hint': 'تلميح', + 'important': 'مهم', + 'note': 'ملاحظة', + 'tip': 'نصيحة', + 'warning': 'تحذير', + 'contents': 'المحتوى'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'مؤلف': 'author', + 'مؤلفون': 'authors', + 'التنظيم': 'organization', + 'العنوان': 'address', + 'اتصل': 'contact', + 'نسخة': 'version', + 'مراجعة': 'revision', + 'الحالة': 'status', + 'تاریخ': 'date', + 'الحقوق': 'copyright', + 'إهداء': 'dedication', + 'ملخص': 'abstract'} +"""Arabic (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = ['؛', '،'] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/ca.py b/.venv/lib/python3.12/site-packages/docutils/languages/ca.py new file mode 100644 index 00000000..d5faf39a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/ca.py @@ -0,0 +1,65 @@ +# $Id: ca.py 9457 2023-10-02 16:25:50Z milde $ +# Authors: Ivan Vilata i Balaguer <ivan@selidor.net>; +# Antoni Bella Pérez <antonibella5@yahoo.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, +# please read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +# These translations can be used without changes for +# Valencian variant of Catalan (use language tag "ca-valencia"). +# Checked by a native speaker of Valentian. + +""" +Catalan-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autor', + 'authors': 'Autors', + 'organization': 'Organització', + 'address': 'Adreça', + 'contact': 'Contacte', + 'version': 'Versió', + 'revision': 'Revisió', + 'status': 'Estat', + 'date': 'Data', + 'copyright': 'Copyright', + 'dedication': 'Dedicatòria', + 'abstract': 'Resum', + 'attention': 'Atenció!', + 'caution': 'Compte!', + 'danger': 'PERILL!', + 'error': 'Error', + 'hint': 'Suggeriment', + 'important': 'Important', + 'note': 'Nota', + 'tip': 'Consell', + 'warning': 'Avís', + 'contents': 'Contingut'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autor': 'author', + 'autors': 'authors', + 'organització': 'organization', + 'adreça': 'address', + 'contacte': 'contact', + 'versió': 'version', + 'revisió': 'revision', + 'estat': 'status', + 'data': 'date', + 'copyright': 'copyright', + 'dedicatòria': 'dedication', + 'resum': 'abstract'} +"""Catalan (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/cs.py b/.venv/lib/python3.12/site-packages/docutils/languages/cs.py new file mode 100644 index 00000000..7ca0ff58 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/cs.py @@ -0,0 +1,60 @@ +# $Id: cs.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Marek Blaha <mb@dat.cz> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Czech-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autor', + 'authors': 'Autoři', + 'organization': 'Organizace', + 'address': 'Adresa', + 'contact': 'Kontakt', + 'version': 'Verze', + 'revision': 'Revize', + 'status': 'Stav', + 'date': 'Datum', + 'copyright': 'Copyright', + 'dedication': 'Věnování', + 'abstract': 'Abstrakt', + 'attention': 'Pozor!', + 'caution': 'Opatrně!', + 'danger': '!NEBEZPEČÍ!', + 'error': 'Chyba', + 'hint': 'Rada', + 'important': 'Důležité', + 'note': 'Poznámka', + 'tip': 'Tip', + 'warning': 'Varování', + 'contents': 'Obsah'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autor': 'author', + 'autoři': 'authors', + 'organizace': 'organization', + 'adresa': 'address', + 'kontakt': 'contact', + 'verze': 'version', + 'revize': 'revision', + 'stav': 'status', + 'datum': 'date', + 'copyright': 'copyright', + 'věnování': 'dedication', + 'abstrakt': 'abstract'} +"""Czech (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/da.py b/.venv/lib/python3.12/site-packages/docutils/languages/da.py new file mode 100644 index 00000000..d683b732 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/da.py @@ -0,0 +1,61 @@ +# $Id: da.py 9030 2022-03-05 23:28:32Z milde $ +# Author: E D +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Danish-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Forfatter', + 'authors': 'Forfattere', + 'organization': 'Organisation', + 'address': 'Adresse', + 'contact': 'Kontakt', + 'version': 'Version', + 'revision': 'Revision', + 'status': 'Status', + 'date': 'Dato', + 'copyright': 'Copyright', + 'dedication': 'Dedikation', + 'abstract': 'Resumé', + 'attention': 'Giv agt!', + 'caution': 'Pas på!', + 'danger': '!FARE!', + 'error': 'Fejl', + 'hint': 'Vink', + 'important': 'Vigtigt', + 'note': 'Bemærk', + 'tip': 'Tips', + 'warning': 'Advarsel', + 'contents': 'Indhold'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'forfatter': 'author', + 'forfattere': 'authors', + 'organisation': 'organization', + 'adresse': 'address', + 'kontakt': 'contact', + 'version': 'version', + 'revision': 'revision', + 'status': 'status', + 'dato': 'date', + 'copyright': 'copyright', + 'dedikation': 'dedication', + 'resume': 'abstract', + 'resumé': 'abstract'} +"""Danish (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/de.py b/.venv/lib/python3.12/site-packages/docutils/languages/de.py new file mode 100644 index 00000000..0d6c82e7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/de.py @@ -0,0 +1,58 @@ +# $Id: de.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Gunnar Schwant <g.schwant@gmx.de> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +German language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Autor', + 'authors': 'Autoren', + 'organization': 'Organisation', + 'address': 'Adresse', + 'contact': 'Kontakt', + 'version': 'Version', + 'revision': 'Revision', + 'status': 'Status', + 'date': 'Datum', + 'dedication': 'Widmung', + 'copyright': 'Copyright', + 'abstract': 'Zusammenfassung', + 'attention': 'Achtung!', + 'caution': 'Vorsicht!', + 'danger': '!GEFAHR!', + 'error': 'Fehler', + 'hint': 'Hinweis', + 'important': 'Wichtig', + 'note': 'Bemerkung', + 'tip': 'Tipp', + 'warning': 'Warnung', + 'contents': 'Inhalt'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'autor': 'author', + 'autoren': 'authors', + 'organisation': 'organization', + 'adresse': 'address', + 'kontakt': 'contact', + 'version': 'version', + 'revision': 'revision', + 'status': 'status', + 'datum': 'date', + 'copyright': 'copyright', + 'widmung': 'dedication', + 'zusammenfassung': 'abstract'} +"""German (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/en.py b/.venv/lib/python3.12/site-packages/docutils/languages/en.py new file mode 100644 index 00000000..683411d1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/en.py @@ -0,0 +1,60 @@ +# $Id: en.py 9030 2022-03-05 23:28:32Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +English-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Author', + 'authors': 'Authors', + 'organization': 'Organization', + 'address': 'Address', + 'contact': 'Contact', + 'version': 'Version', + 'revision': 'Revision', + 'status': 'Status', + 'date': 'Date', + 'copyright': 'Copyright', + 'dedication': 'Dedication', + 'abstract': 'Abstract', + 'attention': 'Attention!', + 'caution': 'Caution!', + 'danger': '!DANGER!', + 'error': 'Error', + 'hint': 'Hint', + 'important': 'Important', + 'note': 'Note', + 'tip': 'Tip', + 'warning': 'Warning', + 'contents': 'Contents'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'author': 'author', + 'authors': 'authors', + 'organization': 'organization', + 'address': 'address', + 'contact': 'contact', + 'version': 'version', + 'revision': 'revision', + 'status': 'status', + 'date': 'date', + 'copyright': 'copyright', + 'dedication': 'dedication', + 'abstract': 'abstract'} +"""English (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/eo.py b/.venv/lib/python3.12/site-packages/docutils/languages/eo.py new file mode 100644 index 00000000..32ea482e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/eo.py @@ -0,0 +1,61 @@ +# $Id: eo.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Marcelo Huerta San Martin <richieadler@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Esperanto-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Aŭtoro', + 'authors': 'Aŭtoroj', + 'organization': 'Organizo', + 'address': 'Adreso', + 'contact': 'Kontakto', + 'version': 'Versio', + 'revision': 'Revido', + 'status': 'Stato', + 'date': 'Dato', + # 'copyright': 'Kopirajto', + 'copyright': 'Aŭtorrajto', + 'dedication': 'Dediĉo', + 'abstract': 'Resumo', + 'attention': 'Atentu!', + 'caution': 'Zorgu!', + 'danger': 'DANĜERO!', + 'error': 'Eraro', + 'hint': 'Spuro', + 'important': 'Grava', + 'note': 'Noto', + 'tip': 'Helpeto', + 'warning': 'Averto', + 'contents': 'Enhavo'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'aŭtoro': 'author', + 'aŭtoroj': 'authors', + 'organizo': 'organization', + 'adreso': 'address', + 'kontakto': 'contact', + 'versio': 'version', + 'revido': 'revision', + 'stato': 'status', + 'dato': 'date', + 'aŭtorrajto': 'copyright', + 'dediĉo': 'dedication', + 'resumo': 'abstract'} +"""Esperanto (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/es.py b/.venv/lib/python3.12/site-packages/docutils/languages/es.py new file mode 100644 index 00000000..2b66e7cd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/es.py @@ -0,0 +1,58 @@ +# $Id: es.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Marcelo Huerta San Martín <richieadler@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Spanish-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Autor', + 'authors': 'Autores', + 'organization': 'Organización', + 'address': 'Dirección', + 'contact': 'Contacto', + 'version': 'Versión', + 'revision': 'Revisión', + 'status': 'Estado', + 'date': 'Fecha', + 'copyright': 'Copyright', + 'dedication': 'Dedicatoria', + 'abstract': 'Resumen', + 'attention': '¡Atención!', + 'caution': '¡Precaución!', + 'danger': '¡PELIGRO!', + 'error': 'Error', + 'hint': 'Sugerencia', + 'important': 'Importante', + 'note': 'Nota', + 'tip': 'Consejo', + 'warning': 'Advertencia', + 'contents': 'Contenido'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'autor': 'author', + 'autores': 'authors', + 'organización': 'organization', + 'dirección': 'address', + 'contacto': 'contact', + 'versión': 'version', + 'revisión': 'revision', + 'estado': 'status', + 'fecha': 'date', + 'copyright': 'copyright', + 'dedicatoria': 'dedication', + 'resumen': 'abstract'} +"""Spanish (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/fa.py b/.venv/lib/python3.12/site-packages/docutils/languages/fa.py new file mode 100644 index 00000000..f25814d2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/fa.py @@ -0,0 +1,60 @@ +# $Id: fa.py 4564 2016-08-10 11:48:42Z +# Author: Shahin <me@5hah.in> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Persian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'نویسنده', + 'authors': 'نویسندگان', + 'organization': 'سازمان', + 'address': 'آدرس', + 'contact': 'تماس', + 'version': 'نسخه', + 'revision': 'بازبینی', + 'status': 'وضعیت', + 'date': 'تاریخ', + 'copyright': 'کپیرایت', + 'dedication': 'تخصیص', + 'abstract': 'چکیده', + 'attention': 'توجه!', + 'caution': 'احتیاط!', + 'danger': 'خطر!', + 'error': 'خطا', + 'hint': 'راهنما', + 'important': 'مهم', + 'note': 'یادداشت', + 'tip': 'نکته', + 'warning': 'اخطار', + 'contents': 'محتوا'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'نویسنده': 'author', + 'نویسندگان': 'authors', + 'سازمان': 'organization', + 'آدرس': 'address', + 'تماس': 'contact', + 'نسخه': 'version', + 'بازبینی': 'revision', + 'وضعیت': 'status', + 'تاریخ': 'date', + 'کپیرایت': 'copyright', + 'تخصیص': 'dedication', + 'چکیده': 'abstract'} +"""Persian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = ['؛', '،'] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/fi.py b/.venv/lib/python3.12/site-packages/docutils/languages/fi.py new file mode 100644 index 00000000..2b401dba --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/fi.py @@ -0,0 +1,60 @@ +# $Id: fi.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Asko Soukka <asko.soukka@iki.fi> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Finnish-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Tekijä', + 'authors': 'Tekijät', + 'organization': 'Yhteisö', + 'address': 'Osoite', + 'contact': 'Yhteystiedot', + 'version': 'Versio', + 'revision': 'Vedos', + 'status': 'Tila', + 'date': 'Päiväys', + 'copyright': 'Tekijänoikeudet', + 'dedication': 'Omistuskirjoitus', + 'abstract': 'Tiivistelmä', + 'attention': 'Huomio!', + 'caution': 'Varo!', + 'danger': '!VAARA!', + 'error': 'Virhe', + 'hint': 'Vihje', + 'important': 'Tärkeää', + 'note': 'Huomautus', + 'tip': 'Neuvo', + 'warning': 'Varoitus', + 'contents': 'Sisällys'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'tekijä': 'author', + 'tekijät': 'authors', + 'yhteisö': 'organization', + 'osoite': 'address', + 'yhteystiedot': 'contact', + 'versio': 'version', + 'vedos': 'revision', + 'tila': 'status', + 'päiväys': 'date', + 'tekijänoikeudet': 'copyright', + 'omistuskirjoitus': 'dedication', + 'tiivistelmä': 'abstract'} +"""Finnish (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/fr.py b/.venv/lib/python3.12/site-packages/docutils/languages/fr.py new file mode 100644 index 00000000..926455bc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/fr.py @@ -0,0 +1,58 @@ +# $Id: fr.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Stefane Fermigier <sf@fermigier.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +French-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Auteur', + 'authors': 'Auteurs', + 'organization': 'Organisation', + 'address': 'Adresse', + 'contact': 'Contact', + 'version': 'Version', + 'revision': 'Révision', + 'status': 'Statut', + 'date': 'Date', + 'copyright': 'Copyright', + 'dedication': 'Dédicace', + 'abstract': 'Résumé', + 'attention': 'Attention!', + 'caution': 'Avertissement!', + 'danger': '!DANGER!', + 'error': 'Erreur', + 'hint': 'Indication', + 'important': 'Important', + 'note': 'Note', + 'tip': 'Astuce', + 'warning': 'Avis', + 'contents': 'Sommaire'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'auteur': 'author', + 'auteurs': 'authors', + 'organisation': 'organization', + 'adresse': 'address', + 'contact': 'contact', + 'version': 'version', + 'révision': 'revision', + 'statut': 'status', + 'date': 'date', + 'copyright': 'copyright', + 'dédicace': 'dedication', + 'résumé': 'abstract'} +"""French (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/gl.py b/.venv/lib/python3.12/site-packages/docutils/languages/gl.py new file mode 100644 index 00000000..f3864abf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/gl.py @@ -0,0 +1,62 @@ +# Author: David Goodger +# Contact: goodger@users.sourceforge.net +# Revision: $Revision: 2224 $ +# Date: $Date: 2004-06-05 21:40:46 +0200 (Sat, 05 Jun 2004) $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Galician-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autor', + 'authors': 'Autores', + 'organization': 'Organización', + 'address': 'Enderezo', + 'contact': 'Contacto', + 'version': 'Versión', + 'revision': 'Revisión', + 'status': 'Estado', + 'date': 'Data', + 'copyright': 'Dereitos de copia', + 'dedication': 'Dedicatoria', + 'abstract': 'Abstract', + 'attention': 'Atención!', + 'caution': 'Advertencia!', + 'danger': 'PERIGO!', + 'error': 'Erro', + 'hint': 'Consello', + 'important': 'Importante', + 'note': 'Nota', + 'tip': 'Suxestión', + 'warning': 'Aviso', + 'contents': 'Contido'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autor': 'author', + 'autores': 'authors', + 'organización': 'organization', + 'enderezo': 'address', + 'contacto': 'contact', + 'versión': 'version', + 'revisión': 'revision', + 'estado': 'status', + 'data': 'date', + 'dereitos de copia': 'copyright', + 'dedicatoria': 'dedication', + 'abstract': 'abstract'} +"""Galician (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/he.py b/.venv/lib/python3.12/site-packages/docutils/languages/he.py new file mode 100644 index 00000000..018cc01a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/he.py @@ -0,0 +1,62 @@ +# Author: Meir Kriheli +# Id: $Id: he.py 9452 2023-09-27 00:11:54Z milde $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Hebrew-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'מחבר', + 'authors': 'מחברי', + 'organization': 'ארגון', + 'address': 'כתובת', + 'contact': 'איש קשר', + 'version': 'גרסה', + 'revision': 'מהדורה', + 'status': 'סטטוס', + 'date': 'תאריך', + 'copyright': 'זכויות שמורות', + 'dedication': 'הקדשה', + 'abstract': 'תקציר', + 'attention': 'תשומת לב', + 'caution': 'זהירות', + 'danger': 'סכנה', + 'error': 'שגיאה', + 'hint': 'רמז', + 'important': 'חשוב', + 'note': 'הערה', + 'tip': 'טיפ', + 'warning': 'אזהרה', + 'contents': 'תוכן', + } +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'מחבר': 'author', + 'מחברי': 'authors', + 'ארגון': 'organization', + 'כתובת': 'address', + 'איש קשר': 'contact', + 'גרסה': 'version', + 'מהדורה': 'revision', + 'סטטוס': 'status', + 'תאריך': 'date', + 'זכויות שמורות': 'copyright', + 'הקדשה': 'dedication', + 'תקציר': 'abstract', + } +"""Hebrew to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/it.py b/.venv/lib/python3.12/site-packages/docutils/languages/it.py new file mode 100644 index 00000000..798ccf95 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/it.py @@ -0,0 +1,58 @@ +# $Id: it.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Nicola Larosa <docutils@tekNico.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Italian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Autore', + 'authors': 'Autori', + 'organization': 'Organizzazione', + 'address': 'Indirizzo', + 'contact': 'Contatti', + 'version': 'Versione', + 'revision': 'Revisione', + 'status': 'Status', + 'date': 'Data', + 'copyright': 'Copyright', + 'dedication': 'Dedica', + 'abstract': 'Riassunto', + 'attention': 'Attenzione!', + 'caution': 'Cautela!', + 'danger': '!PERICOLO!', + 'error': 'Errore', + 'hint': 'Suggerimento', + 'important': 'Importante', + 'note': 'Nota', + 'tip': 'Consiglio', + 'warning': 'Avvertenza', + 'contents': 'Indice'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'autore': 'author', + 'autori': 'authors', + 'organizzazione': 'organization', + 'indirizzo': 'address', + 'contatto': 'contact', + 'versione': 'version', + 'revisione': 'revision', + 'status': 'status', + 'data': 'date', + 'copyright': 'copyright', + 'dedica': 'dedication', + 'riassunto': 'abstract'} +"""Italian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/ja.py b/.venv/lib/python3.12/site-packages/docutils/languages/ja.py new file mode 100644 index 00000000..b936e220 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/ja.py @@ -0,0 +1,60 @@ +# $Id: ja.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Hisashi Morita <hisashim@kt.rim.or.jp> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Japanese-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': '著者', + 'authors': '著者', + 'organization': '組織', + 'address': '住所', + 'contact': '連絡先', + 'version': 'バージョン', + 'revision': 'リビジョン', + 'status': 'ステータス', + 'date': '日付', + 'copyright': '著作権', + 'dedication': '献辞', + 'abstract': '概要', + 'attention': '注目!', + 'caution': '注意!', + 'danger': '!危険!', + 'error': 'エラー', + 'hint': 'ヒント', + 'important': '重要', + 'note': '備考', + 'tip': '通報', + 'warning': '警告', + 'contents': '目次'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + '著者': 'author', + ' n/a': 'authors', + '組織': 'organization', + '住所': 'address', + '連絡先': 'contact', + 'バージョン': 'version', + 'リビジョン': 'revision', + 'ステータス': 'status', + '日付': 'date', + '著作権': 'copyright', + '献辞': 'dedication', + '概要': 'abstract'} +"""Japanese (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/ka.py b/.venv/lib/python3.12/site-packages/docutils/languages/ka.py new file mode 100644 index 00000000..352388d9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/ka.py @@ -0,0 +1,58 @@ +# $Id: ka.py 9444 2023-08-23 12:02:41Z grubert $ +# Author: Temuri Doghonadze <temuri dot doghonadze at gmail dot com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Georgian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'abstract': 'ანოტაცია', + 'address': 'მისამართი', + 'attention': 'ყურადღება!', + 'author': 'ავტორი', + 'authors': 'ავტორები', + 'caution': 'ფრთხილად!', + 'contact': 'კონტაქტი', + 'contents': 'შემცველობა', + 'copyright': 'საავტორო უფლებები', + 'danger': 'საშიშია!', + 'date': 'თარიღი', + 'dedication': 'მიძღვნა', + 'error': 'შეცდომა', + 'hint': 'რჩევა', + 'important': 'მნიშვნელოვანია', + 'note': 'შენიშვნა', + 'organization': 'ორგანიზაცია', + 'revision': 'რევიზია', + 'status': 'სტატუსი', + 'tip': 'მინიშნება', + 'version': 'ვერსია', + 'warning': 'გაფრთხილება'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'ანოტაცია': 'abstract', + 'მისამართი': 'address', + 'ავტორი': 'author', + 'ავტორები': 'authors', + 'კონტაქტი': 'contact', + 'საავტორო უფლებები': 'copyright', + 'თარიღი': 'date', + 'მიძღვნა': 'dedication', + 'ორგანიზაცია': 'organization', + 'რევიზია': 'revision', + 'სტატუსი': 'status', + 'ვერსია': 'version'} +"""Georgian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/ko.py b/.venv/lib/python3.12/site-packages/docutils/languages/ko.py new file mode 100644 index 00000000..c7521d1f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/ko.py @@ -0,0 +1,60 @@ +# $Id: ko.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Thomas SJ Kang <thomas.kangsj@ujuc.kr> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Korean-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': '저자', + 'authors': '저자들', + 'organization': '조직', + 'address': '주소', + 'contact': '연락처', + 'version': '버전', + 'revision': '리비전', + 'status': '상태', + 'date': '날짜', + 'copyright': '저작권', + 'dedication': '헌정', + 'abstract': '요약', + 'attention': '집중!', + 'caution': '주의!', + 'danger': '!위험!', + 'error': '오류', + 'hint': '실마리', + 'important': '중요한', + 'note': '비고', + 'tip': '팁', + 'warning': '경고', + 'contents': '목차'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + '저자': 'author', + '저자들': 'authors', + '조직': 'organization', + '주소': 'address', + '연락처': 'contact', + '버전': 'version', + '리비전': 'revision', + '상태': 'status', + '날짜': 'date', + '저작권': 'copyright', + '헌정': 'dedication', + '요약': 'abstract'} +"""Korean to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/lt.py b/.venv/lib/python3.12/site-packages/docutils/languages/lt.py new file mode 100644 index 00000000..14c90f26 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/lt.py @@ -0,0 +1,60 @@ +# $Id: lt.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Dalius Dobravolskas <dalius.do...@gmail.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Lithuanian language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autorius', + 'authors': 'Autoriai', + 'organization': 'Organizacija', + 'address': 'Adresas', + 'contact': 'Kontaktas', + 'version': 'Versija', + 'revision': 'Revizija', + 'status': 'Būsena', + 'date': 'Data', + 'copyright': 'Autoriaus teisės', + 'dedication': 'Dedikacija', + 'abstract': 'Santrauka', + 'attention': 'Dėmesio!', + 'caution': 'Atsargiai!', + 'danger': '!PAVOJINGA!', + 'error': 'Klaida', + 'hint': 'Užuomina', + 'important': 'Svarbu', + 'note': 'Pastaba', + 'tip': 'Patarimas', + 'warning': 'Įspėjimas', + 'contents': 'Turinys'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autorius': 'author', + 'autoriai': 'authors', + 'organizacija': 'organization', + 'adresas': 'address', + 'kontaktas': 'contact', + 'versija': 'version', + 'revizija': 'revision', + 'būsena': 'status', + 'data': 'date', + 'autoriaus teisės': 'copyright', + 'dedikacija': 'dedication', + 'santrauka': 'abstract'} +"""Lithuanian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/lv.py b/.venv/lib/python3.12/site-packages/docutils/languages/lv.py new file mode 100644 index 00000000..812e95af --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/lv.py @@ -0,0 +1,59 @@ +# $Id: lv.py 9030 2022-03-05 23:28:32Z milde $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Latvian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autors', + 'authors': 'Autori', + 'organization': 'Organizācija', + 'address': 'Adrese', + 'contact': 'Kontakti', + 'version': 'Versija', + 'revision': 'Revīzija', + 'status': 'Statuss', + 'date': 'Datums', + 'copyright': 'Copyright', + 'dedication': 'Veltījums', + 'abstract': 'Atreferējums', + 'attention': 'Uzmanību!', + 'caution': 'Piesardzību!', + 'danger': '!BĪSTAMI!', + 'error': 'Kļūda', + 'hint': 'Ieteikums', + 'important': 'Svarīgi', + 'note': 'Piezīme', + 'tip': 'Padoms', + 'warning': 'Brīdinājums', + 'contents': 'Saturs'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autors': 'author', + 'autori': 'authors', + 'organizācija': 'organization', + 'adrese': 'address', + 'kontakti': 'contact', + 'versija': 'version', + 'revīzija': 'revision', + 'statuss': 'status', + 'datums': 'date', + 'copyright': 'copyright', + 'veltījums': 'dedication', + 'atreferējums': 'abstract'} +"""English (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/nl.py b/.venv/lib/python3.12/site-packages/docutils/languages/nl.py new file mode 100644 index 00000000..53540e02 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/nl.py @@ -0,0 +1,60 @@ +# $Id: nl.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Martijn Pieters <mjpieters@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Dutch-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Auteur', + 'authors': 'Auteurs', + 'organization': 'Organisatie', + 'address': 'Adres', + 'contact': 'Contact', + 'version': 'Versie', + 'revision': 'Revisie', + 'status': 'Status', + 'date': 'Datum', + 'copyright': 'Copyright', + 'dedication': 'Toewijding', + 'abstract': 'Samenvatting', + 'attention': 'Attentie!', + 'caution': 'Let op!', + 'danger': '!GEVAAR!', + 'error': 'Fout', + 'hint': 'Hint', + 'important': 'Belangrijk', + 'note': 'Opmerking', + 'tip': 'Tip', + 'warning': 'Waarschuwing', + 'contents': 'Inhoud'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'auteur': 'author', + 'auteurs': 'authors', + 'organisatie': 'organization', + 'adres': 'address', + 'contact': 'contact', + 'versie': 'version', + 'revisie': 'revision', + 'status': 'status', + 'datum': 'date', + 'copyright': 'copyright', + 'toewijding': 'dedication', + 'samenvatting': 'abstract'} +"""Dutch (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/pl.py b/.venv/lib/python3.12/site-packages/docutils/languages/pl.py new file mode 100644 index 00000000..606a4014 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/pl.py @@ -0,0 +1,60 @@ +# $Id$ +# Author: Robert Wojciechowicz <rw@smsnet.pl> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Polish-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autor', + 'authors': 'Autorzy', + 'organization': 'Organizacja', + 'address': 'Adres', + 'contact': 'Kontakt', + 'version': 'Wersja', + 'revision': 'Korekta', + 'status': 'Status', + 'date': 'Data', + 'copyright': 'Copyright', + 'dedication': 'Dedykacja', + 'abstract': 'Streszczenie', + 'attention': 'Uwaga!', + 'caution': 'Ostrożnie!', + 'danger': '!Niebezpieczeństwo!', + 'error': 'Błąd', + 'hint': 'Wskazówka', + 'important': 'Ważne', + 'note': 'Przypis', + 'tip': 'Rada', + 'warning': 'Ostrzeżenie', + 'contents': 'Treść'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autor': 'author', + 'autorzy': 'authors', + 'organizacja': 'organization', + 'adres': 'address', + 'kontakt': 'contact', + 'wersja': 'version', + 'korekta': 'revision', + 'status': 'status', + 'data': 'date', + 'copyright': 'copyright', + 'dedykacja': 'dedication', + 'streszczenie': 'abstract'} +"""Polish (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/pt_br.py b/.venv/lib/python3.12/site-packages/docutils/languages/pt_br.py new file mode 100644 index 00000000..195a671b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/pt_br.py @@ -0,0 +1,60 @@ +# $Id: pt_br.py 9452 2023-09-27 00:11:54Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Brazilian Portuguese-language mappings for language-dependent features. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autor', + 'authors': 'Autores', + 'organization': 'Organização', + 'address': 'Endereço', + 'contact': 'Contato', + 'version': 'Versão', + 'revision': 'Revisão', + 'status': 'Estado', + 'date': 'Data', + 'copyright': 'Copyright', + 'dedication': 'Dedicatória', + 'abstract': 'Resumo', + 'attention': 'Atenção!', + 'caution': 'Cuidado!', + 'danger': 'PERIGO!', + 'error': 'Erro', + 'hint': 'Sugestão', + 'important': 'Importante', + 'note': 'Nota', + 'tip': 'Dica', + 'warning': 'Aviso', + 'contents': 'Sumário'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autor': 'author', + 'autores': 'authors', + 'organização': 'organization', + 'endereço': 'address', + 'contato': 'contact', + 'versão': 'version', + 'revisão': 'revision', + 'estado': 'status', + 'data': 'date', + 'copyright': 'copyright', + 'dedicatória': 'dedication', + 'resumo': 'abstract'} +"""Brazilian Portuguese (lowcased) name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/ru.py b/.venv/lib/python3.12/site-packages/docutils/languages/ru.py new file mode 100644 index 00000000..3741bd86 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/ru.py @@ -0,0 +1,58 @@ +# $Id: ru.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Roman Suzi <rnd@onego.ru> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Russian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'abstract': 'Аннотация', + 'address': 'Адрес', + 'attention': 'Внимание!', + 'author': 'Автор', + 'authors': 'Авторы', + 'caution': 'Осторожно!', + 'contact': 'Контакт', + 'contents': 'Содержание', + 'copyright': 'Права копирования', + 'danger': 'ОПАСНО!', + 'date': 'Дата', + 'dedication': 'Посвящение', + 'error': 'Ошибка', + 'hint': 'Совет', + 'important': 'Важно', + 'note': 'Примечание', + 'organization': 'Организация', + 'revision': 'Редакция', + 'status': 'Статус', + 'tip': 'Подсказка', + 'version': 'Версия', + 'warning': 'Предупреждение'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'аннотация': 'abstract', + 'адрес': 'address', + 'автор': 'author', + 'авторы': 'authors', + 'контакт': 'contact', + 'права копирования': 'copyright', + 'дата': 'date', + 'посвящение': 'dedication', + 'организация': 'organization', + 'редакция': 'revision', + 'статус': 'status', + 'версия': 'version'} +"""Russian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/sk.py b/.venv/lib/python3.12/site-packages/docutils/languages/sk.py new file mode 100644 index 00000000..bb8c8109 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/sk.py @@ -0,0 +1,58 @@ +# $Id: sk.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Miroslav Vasko <zemiak@zoznam.sk> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Slovak-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Autor', + 'authors': 'Autori', + 'organization': 'Organizácia', + 'address': 'Adresa', + 'contact': 'Kontakt', + 'version': 'Verzia', + 'revision': 'Revízia', + 'status': 'Stav', + 'date': 'Dátum', + 'copyright': 'Copyright', + 'dedication': 'Venovanie', + 'abstract': 'Abstraktne', + 'attention': 'Pozor!', + 'caution': 'Opatrne!', + 'danger': '!NEBEZPEČENSTVO!', + 'error': 'Chyba', + 'hint': 'Rada', + 'important': 'Dôležité', + 'note': 'Poznámka', + 'tip': 'Tip', + 'warning': 'Varovanie', + 'contents': 'Obsah'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'autor': 'author', + 'autori': 'authors', + 'organizácia': 'organization', + 'adresa': 'address', + 'kontakt': 'contact', + 'verzia': 'version', + 'revízia': 'revision', + 'stav': 'status', + 'dátum': 'date', + 'copyright': 'copyright', + 'venovanie': 'dedication', + 'abstraktne': 'abstract'} +"""Slovak (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/sv.py b/.venv/lib/python3.12/site-packages/docutils/languages/sv.py new file mode 100644 index 00000000..a47ede58 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/sv.py @@ -0,0 +1,59 @@ +# $Id: sv.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Adam Chodorowski <chodorowski@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Swedish language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'author': 'Författare', + 'authors': 'Författare', + 'organization': 'Organisation', + 'address': 'Adress', + 'contact': 'Kontakt', + 'version': 'Version', + 'revision': 'Revision', + 'status': 'Status', + 'date': 'Datum', + 'copyright': 'Copyright', + 'dedication': 'Dedikation', + 'abstract': 'Sammanfattning', + 'attention': 'Observera!', + 'caution': 'Akta!', # 'Varning' already used for 'warning' + 'danger': 'FARA!', + 'error': 'Fel', + 'hint': 'Vink', + 'important': 'Viktigt', + 'note': 'Notera', + 'tip': 'Tips', + 'warning': 'Varning', + 'contents': 'Innehåll'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # 'Author' and 'Authors' identical in Swedish; assume the plural: + 'författare': 'authors', + ' n/a': 'author', # removing leads to (spurious) test failure + 'organisation': 'organization', + 'adress': 'address', + 'kontakt': 'contact', + 'version': 'version', + 'revision': 'revision', + 'status': 'status', + 'datum': 'date', + 'copyright': 'copyright', + 'dedikation': 'dedication', + 'sammanfattning': 'abstract'} +"""Swedish (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/uk.py b/.venv/lib/python3.12/site-packages/docutils/languages/uk.py new file mode 100644 index 00000000..b591dacb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/uk.py @@ -0,0 +1,58 @@ +# $Id: uk.py 9114 2022-07-28 17:06:10Z milde $ +# Author: Dmytro Kazanzhy <dkazanzhy@gmail.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be +# translated for each language: one in docutils/languages, the other in +# docutils/parsers/rst/languages. + +""" +Ukrainian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + 'abstract': 'Анотація', + 'address': 'Адреса', + 'attention': 'Увага!', + 'author': 'Автор', + 'authors': 'Автори', + 'caution': 'Обережно!', + 'contact': 'Контакт', + 'contents': 'Зміст', + 'copyright': 'Права копіювання', + 'danger': 'НЕБЕЗПЕЧНО!', + 'date': 'Дата', + 'dedication': 'Посвячення', + 'error': 'Помилка', + 'hint': 'Порада', + 'important': 'Важливо', + 'note': 'Примітка', + 'organization': 'Організація', + 'revision': 'Редакція', + 'status': 'Статус', + 'tip': 'Підказка', + 'version': 'Версія', + 'warning': 'Попередження'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + 'анотація': 'abstract', + 'адреса': 'address', + 'автор': 'author', + 'автори': 'authors', + 'контакт': 'contact', + 'права копіювання': 'copyright', + 'дата': 'date', + 'посвячення': 'dedication', + 'організація': 'organization', + 'редакція': 'revision', + 'статус': 'status', + 'версія': 'version'} +"""Ukrainian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/zh_cn.py b/.venv/lib/python3.12/site-packages/docutils/languages/zh_cn.py new file mode 100644 index 00000000..c2ff2e6a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/zh_cn.py @@ -0,0 +1,62 @@ +# $Id: zh_cn.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Pan Junyong <panjy@zopechina.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Simplified Chinese language mappings for language-dependent features +of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': '作者', + 'authors': '作者群', + 'organization': '组织', + 'address': '地址', + 'contact': '联系', + 'version': '版本', + 'revision': '修订', + 'status': '状态', + 'date': '日期', + 'copyright': '版权', + 'dedication': '献辞', + 'abstract': '摘要', + 'attention': '注意', + 'caution': '小心', + 'danger': '危险', + 'error': '错误', + 'hint': '提示', + 'important': '重要', + 'note': '注解', + 'tip': '技巧', + 'warning': '警告', + 'contents': '目录', +} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + '作者': 'author', + '作者群': 'authors', + '组织': 'organization', + '地址': 'address', + '联系': 'contact', + '版本': 'version', + '修订': 'revision', + '状态': 'status', + '时间': 'date', + '版权': 'copyright', + '献辞': 'dedication', + '摘要': 'abstract'} +"""Simplified Chinese to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ',', ';', ',', '、'] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/languages/zh_tw.py b/.venv/lib/python3.12/site-packages/docutils/languages/zh_tw.py new file mode 100644 index 00000000..cb59c50b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/languages/zh_tw.py @@ -0,0 +1,61 @@ +# $Id: zh_tw.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Joe YS Jaw <joeysj@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Traditional Chinese language mappings for language-dependent features. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': '作者', + 'authors': '作者群', + 'organization': '組織', + 'address': '地址', + 'contact': '連絡', + 'version': '版本', + 'revision': '修訂', + 'status': '狀態', + 'date': '日期', + 'copyright': '版權', + 'dedication': '題獻', + 'abstract': '摘要', + 'attention': '注意!', + 'caution': '小心!', + 'danger': '!危險!', + 'error': '錯誤', + 'hint': '提示', + 'important': '重要', + 'note': '註釋', + 'tip': '秘訣', + 'warning': '警告', + 'contents': '目錄', + } +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'author (translation required)': 'author', + 'authors (translation required)': 'authors', + 'organization (translation required)': 'organization', + 'address (translation required)': 'address', + 'contact (translation required)': 'contact', + 'version (translation required)': 'version', + 'revision (translation required)': 'revision', + 'status (translation required)': 'status', + 'date (translation required)': 'date', + 'copyright (translation required)': 'copyright', + 'dedication (translation required)': 'dedication', + 'abstract (translation required)': 'abstract'} +"""Traditional Chinese to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ',', ';', ',', '、'] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/nodes.py b/.venv/lib/python3.12/site-packages/docutils/nodes.py new file mode 100644 index 00000000..63a38981 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/nodes.py @@ -0,0 +1,2301 @@ +# $Id: nodes.py 9612 2024-04-05 23:07:13Z milde $ +# Author: David Goodger <goodger@python.org> +# Maintainer: docutils-develop@lists.sourceforge.net +# Copyright: This module has been placed in the public domain. + +""" +Docutils document tree element class library. + +Classes in CamelCase are abstract base classes or auxiliary classes. The one +exception is `Text`, for a text (PCDATA) node; uppercase is used to +differentiate from element classes. Classes in lower_case_with_underscores +are element classes, matching the XML element generic identifiers in the DTD_. + +The position of each node (the level at which it can occur) is significant and +is represented by abstract base classes (`Root`, `Structural`, `Body`, +`Inline`, etc.). Certain transformations will be easier because we can use +``isinstance(node, base_class)`` to determine the position of the node in the +hierarchy. + +.. _DTD: https://docutils.sourceforge.io/docs/ref/docutils.dtd +""" + +__docformat__ = 'reStructuredText' + +from collections import Counter +import re +import sys +import warnings +import unicodedata +# import xml.dom.minidom as dom # -> conditional import in Node.asdom() +# and document.asdom() + +# import docutils.transforms # -> conditional import in document.__init__() + + +# ============================== +# Functional Node Base Classes +# ============================== + +class Node: + """Abstract base class of nodes in a document tree.""" + + parent = None + """Back-reference to the Node immediately containing this Node.""" + + source = None + """Path or description of the input source which generated this Node.""" + + line = None + """The line number (1-based) of the beginning of this Node in `source`.""" + + _document = None + + @property + def document(self): + """Return the `document` root node of the tree containing this Node. + """ + try: + return self._document or self.parent.document + except AttributeError: + return None + + @document.setter + def document(self, value): + self._document = value + + def __bool__(self): + """ + Node instances are always true, even if they're empty. A node is more + than a simple container. Its boolean "truth" does not depend on + having one or more subnodes in the doctree. + + Use `len()` to check node length. + """ + return True + + def asdom(self, dom=None): + """Return a DOM **fragment** representation of this Node.""" + if dom is None: + import xml.dom.minidom as dom + domroot = dom.Document() + return self._dom_node(domroot) + + def pformat(self, indent=' ', level=0): + """ + Return an indented pseudo-XML representation, for test purposes. + + Override in subclasses. + """ + raise NotImplementedError + + def copy(self): + """Return a copy of self.""" + raise NotImplementedError + + def deepcopy(self): + """Return a deep copy of self (also copying children).""" + raise NotImplementedError + + def astext(self): + """Return a string representation of this Node.""" + raise NotImplementedError + + def setup_child(self, child): + child.parent = self + if self.document: + child.document = self.document + if child.source is None: + child.source = self.document.current_source + if child.line is None: + child.line = self.document.current_line + + def walk(self, visitor): + """ + Traverse a tree of `Node` objects, calling the + `dispatch_visit()` method of `visitor` when entering each + node. (The `walkabout()` method is similar, except it also + calls the `dispatch_departure()` method before exiting each + node.) + + This tree traversal supports limited in-place tree + modifications. Replacing one node with one or more nodes is + OK, as is removing an element. However, if the node removed + or replaced occurs after the current node, the old node will + still be traversed, and any new nodes will not. + + Within ``visit`` methods (and ``depart`` methods for + `walkabout()`), `TreePruningException` subclasses may be raised + (`SkipChildren`, `SkipSiblings`, `SkipNode`, `SkipDeparture`). + + Parameter `visitor`: A `NodeVisitor` object, containing a + ``visit`` implementation for each `Node` subclass encountered. + + Return true if we should stop the traversal. + """ + stop = False + visitor.document.reporter.debug( + 'docutils.nodes.Node.walk calling dispatch_visit for %s' + % self.__class__.__name__) + try: + try: + visitor.dispatch_visit(self) + except (SkipChildren, SkipNode): + return stop + except SkipDeparture: # not applicable; ignore + pass + children = self.children + try: + for child in children[:]: + if child.walk(visitor): + stop = True + break + except SkipSiblings: + pass + except StopTraversal: + stop = True + return stop + + def walkabout(self, visitor): + """ + Perform a tree traversal similarly to `Node.walk()` (which + see), except also call the `dispatch_departure()` method + before exiting each node. + + Parameter `visitor`: A `NodeVisitor` object, containing a + ``visit`` and ``depart`` implementation for each `Node` + subclass encountered. + + Return true if we should stop the traversal. + """ + call_depart = True + stop = False + visitor.document.reporter.debug( + 'docutils.nodes.Node.walkabout calling dispatch_visit for %s' + % self.__class__.__name__) + try: + try: + visitor.dispatch_visit(self) + except SkipNode: + return stop + except SkipDeparture: + call_depart = False + children = self.children + try: + for child in children[:]: + if child.walkabout(visitor): + stop = True + break + except SkipSiblings: + pass + except SkipChildren: + pass + except StopTraversal: + stop = True + if call_depart: + visitor.document.reporter.debug( + 'docutils.nodes.Node.walkabout calling dispatch_departure ' + 'for %s' % self.__class__.__name__) + visitor.dispatch_departure(self) + return stop + + def _fast_findall(self, cls): + """Return iterator that only supports instance checks.""" + if isinstance(self, cls): + yield self + for child in self.children: + yield from child._fast_findall(cls) + + def _superfast_findall(self): + """Return iterator that doesn't check for a condition.""" + # This is different from ``iter(self)`` implemented via + # __getitem__() and __len__() in the Element subclass, + # which yields only the direct children. + yield self + for child in self.children: + yield from child._superfast_findall() + + def traverse(self, condition=None, include_self=True, descend=True, + siblings=False, ascend=False): + """Return list of nodes following `self`. + + For looping, Node.findall() is faster and more memory efficient. + """ + # traverse() may be eventually removed: + warnings.warn('nodes.Node.traverse() is obsoleted by Node.findall().', + PendingDeprecationWarning, stacklevel=2) + return list(self.findall(condition, include_self, descend, + siblings, ascend)) + + def findall(self, condition=None, include_self=True, descend=True, + siblings=False, ascend=False): + """ + Return an iterator yielding nodes following `self`: + + * self (if `include_self` is true) + * all descendants in tree traversal order (if `descend` is true) + * the following siblings (if `siblings` is true) and their + descendants (if also `descend` is true) + * the following siblings of the parent (if `ascend` is true) and + their descendants (if also `descend` is true), and so on. + + If `condition` is not None, the iterator yields only nodes + for which ``condition(node)`` is true. If `condition` is a + node class ``cls``, it is equivalent to a function consisting + of ``return isinstance(node, cls)``. + + If `ascend` is true, assume `siblings` to be true as well. + + If the tree structure is modified during iteration, the result + is undefined. + + For example, given the following tree:: + + <paragraph> + <emphasis> <--- emphasis.traverse() and + <strong> <--- strong.traverse() are called. + Foo + Bar + <reference name="Baz" refid="baz"> + Baz + + Then tuple(emphasis.traverse()) equals :: + + (<emphasis>, <strong>, <#text: Foo>, <#text: Bar>) + + and list(strong.traverse(ascend=True) equals :: + + [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>] + """ + if ascend: + siblings = True + # Check for special argument combinations that allow using an + # optimized version of traverse() + if include_self and descend and not siblings: + if condition is None: + yield from self._superfast_findall() + return + elif isinstance(condition, type): + yield from self._fast_findall(condition) + return + # Check if `condition` is a class (check for TypeType for Python + # implementations that use only new-style classes, like PyPy). + if isinstance(condition, type): + node_class = condition + + def condition(node, node_class=node_class): + return isinstance(node, node_class) + + if include_self and (condition is None or condition(self)): + yield self + if descend and len(self.children): + for child in self: + yield from child.findall(condition=condition, + include_self=True, descend=True, + siblings=False, ascend=False) + if siblings or ascend: + node = self + while node.parent: + index = node.parent.index(node) + # extra check since Text nodes have value-equality + while node.parent[index] is not node: + index = node.parent.index(node, index + 1) + for sibling in node.parent[index+1:]: + yield from sibling.findall( + condition=condition, + include_self=True, descend=descend, + siblings=False, ascend=False) + if not ascend: + break + else: + node = node.parent + + def next_node(self, condition=None, include_self=False, descend=True, + siblings=False, ascend=False): + """ + Return the first node in the iterator returned by findall(), + or None if the iterable is empty. + + Parameter list is the same as of `findall()`. Note that `include_self` + defaults to False, though. + """ + try: + return next(self.findall(condition, include_self, + descend, siblings, ascend)) + except StopIteration: + return None + + +# definition moved here from `utils` to avoid circular import dependency +def unescape(text, restore_backslashes=False, respect_whitespace=False): + """ + Return a string with nulls removed or restored to backslashes. + Backslash-escaped spaces are also removed. + """ + # `respect_whitespace` is ignored (since introduction 2016-12-16) + if restore_backslashes: + return text.replace('\x00', '\\') + else: + for sep in ['\x00 ', '\x00\n', '\x00']: + text = ''.join(text.split(sep)) + return text + + +class Text(Node, str): + + """ + Instances are terminal nodes (leaves) containing text only; no child + nodes or attributes. Initialize by passing a string to the constructor. + + Access the raw (null-escaped) text with ``str(<instance>)`` + and unescaped text with ``<instance>.astext()``. + """ + + tagname = '#text' + + children = () + """Text nodes have no children, and cannot have children.""" + + def __new__(cls, data, rawsource=None): + """Assert that `data` is not an array of bytes + and warn if the deprecated `rawsource` argument is used. + """ + if isinstance(data, bytes): + raise TypeError('expecting str data, not bytes') + if rawsource is not None: + warnings.warn('nodes.Text: initialization argument "rawsource" ' + 'is ignored and will be removed in Docutils 2.0.', + DeprecationWarning, stacklevel=2) + return str.__new__(cls, data) + + def shortrepr(self, maxlen=18): + data = self + if len(data) > maxlen: + data = data[:maxlen-4] + ' ...' + return '<%s: %r>' % (self.tagname, str(data)) + + def __repr__(self): + return self.shortrepr(maxlen=68) + + def _dom_node(self, domroot): + return domroot.createTextNode(str(self)) + + def astext(self): + return str(unescape(self)) + + def copy(self): + return self.__class__(str(self)) + + def deepcopy(self): + return self.copy() + + def pformat(self, indent=' ', level=0): + try: + if self.document.settings.detailed: + tag = '%s%s' % (indent*level, '<#text>') + lines = (indent*(level+1) + repr(line) + for line in self.splitlines(True)) + return '\n'.join((tag, *lines)) + '\n' + except AttributeError: + pass + indent = indent * level + lines = [indent+line for line in self.astext().splitlines()] + if not lines: + return '' + return '\n'.join(lines) + '\n' + + # rstrip and lstrip are used by substitution definitions where + # they are expected to return a Text instance, this was formerly + # taken care of by UserString. + + def rstrip(self, chars=None): + return self.__class__(str.rstrip(self, chars)) + + def lstrip(self, chars=None): + return self.__class__(str.lstrip(self, chars)) + + +class Element(Node): + + """ + `Element` is the superclass to all specific elements. + + Elements contain attributes and child nodes. + They can be described as a cross between a list and a dictionary. + + Elements emulate dictionaries for external [#]_ attributes, indexing by + attribute name (a string). To set the attribute 'att' to 'value', do:: + + element['att'] = 'value' + + .. [#] External attributes correspond to the XML element attributes. + From its `Node` superclass, Element also inherits "internal" + class attributes that are accessed using the standard syntax, e.g. + ``element.parent``. + + There are two special attributes: 'ids' and 'names'. Both are + lists of unique identifiers: 'ids' conform to the regular expression + ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function for rationale and + details). 'names' serve as user-friendly interfaces to IDs; they are + case- and whitespace-normalized (see the fully_normalize_name() function). + + Elements emulate lists for child nodes (element nodes and/or text + nodes), indexing by integer. To get the first child node, use:: + + element[0] + + to iterate over the child nodes (without descending), use:: + + for child in element: + ... + + Elements may be constructed using the ``+=`` operator. To add one new + child node to element, do:: + + element += node + + This is equivalent to ``element.append(node)``. + + To add a list of multiple child nodes at once, use the same ``+=`` + operator:: + + element += [node1, node2] + + This is equivalent to ``element.extend([node1, node2])``. + """ + + basic_attributes = ('ids', 'classes', 'names', 'dupnames') + """Tuple of attributes which are defined for every Element-derived class + instance and can be safely transferred to a different node.""" + + local_attributes = ('backrefs',) + """Tuple of class-specific attributes that should not be copied with the + standard attributes when replacing a node. + + NOTE: Derived classes should override this value to prevent any of its + attributes being copied by adding to the value in its parent class.""" + + list_attributes = basic_attributes + local_attributes + """Tuple of attributes that are automatically initialized to empty lists + for all nodes.""" + + known_attributes = list_attributes + ('source',) + """Tuple of attributes that are known to the Element base class.""" + + tagname = None + """The element generic identifier. If None, it is set as an instance + attribute to the name of the class.""" + + child_text_separator = '\n\n' + """Separator for child nodes, used by `astext()` method.""" + + def __init__(self, rawsource='', *children, **attributes): + self.rawsource = rawsource + """The raw text from which this element was constructed. + + NOTE: some elements do not set this value (default ''). + """ + + self.children = [] + """List of child nodes (elements and/or `Text`).""" + + self.extend(children) # maintain parent info + + self.attributes = {} + """Dictionary of attribute {name: value}.""" + + # Initialize list attributes. + for att in self.list_attributes: + self.attributes[att] = [] + + for att, value in attributes.items(): + att = att.lower() + if att in self.list_attributes: + # mutable list; make a copy for this node + self.attributes[att] = value[:] + else: + self.attributes[att] = value + + if self.tagname is None: + self.tagname = self.__class__.__name__ + + def _dom_node(self, domroot): + element = domroot.createElement(self.tagname) + for attribute, value in self.attlist(): + if isinstance(value, list): + value = ' '.join(serial_escape('%s' % (v,)) for v in value) + element.setAttribute(attribute, '%s' % value) + for child in self.children: + element.appendChild(child._dom_node(domroot)) + return element + + def __repr__(self): + data = '' + for c in self.children: + data += c.shortrepr() + if len(data) > 60: + data = data[:56] + ' ...' + break + if self['names']: + return '<%s "%s": %s>' % (self.__class__.__name__, + '; '.join(self['names']), data) + else: + return '<%s: %s>' % (self.__class__.__name__, data) + + def shortrepr(self): + if self['names']: + return '<%s "%s"...>' % (self.__class__.__name__, + '; '.join(self['names'])) + else: + return '<%s...>' % self.tagname + + def __str__(self): + if self.children: + return '%s%s%s' % (self.starttag(), + ''.join(str(c) for c in self.children), + self.endtag()) + else: + return self.emptytag() + + def starttag(self, quoteattr=None): + # the optional arg is used by the docutils_xml writer + if quoteattr is None: + quoteattr = pseudo_quoteattr + parts = [self.tagname] + for name, value in self.attlist(): + if value is None: # boolean attribute + parts.append('%s="True"' % name) + continue + if isinstance(value, list): + values = [serial_escape('%s' % (v,)) for v in value] + value = ' '.join(values) + else: + value = str(value) + value = quoteattr(value) + parts.append('%s=%s' % (name, value)) + return '<%s>' % ' '.join(parts) + + def endtag(self): + return '</%s>' % self.tagname + + def emptytag(self): + attributes = ('%s="%s"' % (n, v) for n, v in self.attlist()) + return '<%s/>' % ' '.join((self.tagname, *attributes)) + + def __len__(self): + return len(self.children) + + def __contains__(self, key): + # Test for both, children and attributes with operator ``in``. + if isinstance(key, str): + return key in self.attributes + return key in self.children + + def __getitem__(self, key): + if isinstance(key, str): + return self.attributes[key] + elif isinstance(key, int): + return self.children[key] + elif isinstance(key, slice): + assert key.step in (None, 1), 'cannot handle slice with stride' + return self.children[key.start:key.stop] + else: + raise TypeError('element index must be an integer, a slice, or ' + 'an attribute name string') + + def __setitem__(self, key, item): + if isinstance(key, str): + self.attributes[str(key)] = item + elif isinstance(key, int): + self.setup_child(item) + self.children[key] = item + elif isinstance(key, slice): + assert key.step in (None, 1), 'cannot handle slice with stride' + for node in item: + self.setup_child(node) + self.children[key.start:key.stop] = item + else: + raise TypeError('element index must be an integer, a slice, or ' + 'an attribute name string') + + def __delitem__(self, key): + if isinstance(key, str): + del self.attributes[key] + elif isinstance(key, int): + del self.children[key] + elif isinstance(key, slice): + assert key.step in (None, 1), 'cannot handle slice with stride' + del self.children[key.start:key.stop] + else: + raise TypeError('element index must be an integer, a simple ' + 'slice, or an attribute name string') + + def __add__(self, other): + return self.children + other + + def __radd__(self, other): + return other + self.children + + def __iadd__(self, other): + """Append a node or a list of nodes to `self.children`.""" + if isinstance(other, Node): + self.append(other) + elif other is not None: + self.extend(other) + return self + + def astext(self): + return self.child_text_separator.join( + [child.astext() for child in self.children]) + + def non_default_attributes(self): + atts = {} + for key, value in self.attributes.items(): + if self.is_not_default(key): + atts[key] = value + return atts + + def attlist(self): + return sorted(self.non_default_attributes().items()) + + def get(self, key, failobj=None): + return self.attributes.get(key, failobj) + + def hasattr(self, attr): + return attr in self.attributes + + def delattr(self, attr): + if attr in self.attributes: + del self.attributes[attr] + + def setdefault(self, key, failobj=None): + return self.attributes.setdefault(key, failobj) + + has_key = hasattr + + def get_language_code(self, fallback=''): + """Return node's language tag. + + Look iteratively in self and parents for a class argument + starting with ``language-`` and return the remainder of it + (which should be a `BCP49` language tag) or the `fallback`. + """ + for cls in self.get('classes', []): + if cls.startswith('language-'): + return cls[9:] + try: + return self.parent.get_language(fallback) + except AttributeError: + return fallback + + def append(self, item): + self.setup_child(item) + self.children.append(item) + + def extend(self, item): + for node in item: + self.append(node) + + def insert(self, index, item): + if isinstance(item, Node): + self.setup_child(item) + self.children.insert(index, item) + elif item is not None: + self[index:index] = item + + def pop(self, i=-1): + return self.children.pop(i) + + def remove(self, item): + self.children.remove(item) + + def index(self, item, start=0, stop=sys.maxsize): + return self.children.index(item, start, stop) + + def previous_sibling(self): + """Return preceding sibling node or ``None``.""" + try: + i = self.parent.index(self) + except (AttributeError): + return None + return self.parent[i-1] if i > 0 else None + + def is_not_default(self, key): + if self[key] == [] and key in self.list_attributes: + return 0 + else: + return 1 + + def update_basic_atts(self, dict_): + """ + Update basic attributes ('ids', 'names', 'classes', + 'dupnames', but not 'source') from node or dictionary `dict_`. + """ + if isinstance(dict_, Node): + dict_ = dict_.attributes + for att in self.basic_attributes: + self.append_attr_list(att, dict_.get(att, [])) + + def append_attr_list(self, attr, values): + """ + For each element in values, if it does not exist in self[attr], append + it. + + NOTE: Requires self[attr] and values to be sequence type and the + former should specifically be a list. + """ + # List Concatenation + for value in values: + if value not in self[attr]: + self[attr].append(value) + + def coerce_append_attr_list(self, attr, value): + """ + First, convert both self[attr] and value to a non-string sequence + type; if either is not already a sequence, convert it to a list of one + element. Then call append_attr_list. + + NOTE: self[attr] and value both must not be None. + """ + # List Concatenation + if not isinstance(self.get(attr), list): + self[attr] = [self[attr]] + if not isinstance(value, list): + value = [value] + self.append_attr_list(attr, value) + + def replace_attr(self, attr, value, force=True): + """ + If self[attr] does not exist or force is True or omitted, set + self[attr] to value, otherwise do nothing. + """ + # One or the other + if force or self.get(attr) is None: + self[attr] = value + + def copy_attr_convert(self, attr, value, replace=True): + """ + If attr is an attribute of self, set self[attr] to + [self[attr], value], otherwise set self[attr] to value. + + NOTE: replace is not used by this function and is kept only for + compatibility with the other copy functions. + """ + if self.get(attr) is not value: + self.coerce_append_attr_list(attr, value) + + def copy_attr_coerce(self, attr, value, replace): + """ + If attr is an attribute of self and either self[attr] or value is a + list, convert all non-sequence values to a sequence of 1 element and + then concatenate the two sequence, setting the result to self[attr]. + If both self[attr] and value are non-sequences and replace is True or + self[attr] is None, replace self[attr] with value. Otherwise, do + nothing. + """ + if self.get(attr) is not value: + if isinstance(self.get(attr), list) or \ + isinstance(value, list): + self.coerce_append_attr_list(attr, value) + else: + self.replace_attr(attr, value, replace) + + def copy_attr_concatenate(self, attr, value, replace): + """ + If attr is an attribute of self and both self[attr] and value are + lists, concatenate the two sequences, setting the result to + self[attr]. If either self[attr] or value are non-sequences and + replace is True or self[attr] is None, replace self[attr] with value. + Otherwise, do nothing. + """ + if self.get(attr) is not value: + if isinstance(self.get(attr), list) and \ + isinstance(value, list): + self.append_attr_list(attr, value) + else: + self.replace_attr(attr, value, replace) + + def copy_attr_consistent(self, attr, value, replace): + """ + If replace is True or self[attr] is None, replace self[attr] with + value. Otherwise, do nothing. + """ + if self.get(attr) is not value: + self.replace_attr(attr, value, replace) + + def update_all_atts(self, dict_, update_fun=copy_attr_consistent, + replace=True, and_source=False): + """ + Updates all attributes from node or dictionary `dict_`. + + Appends the basic attributes ('ids', 'names', 'classes', + 'dupnames', but not 'source') and then, for all other attributes in + dict_, updates the same attribute in self. When attributes with the + same identifier appear in both self and dict_, the two values are + merged based on the value of update_fun. Generally, when replace is + True, the values in self are replaced or merged with the values in + dict_; otherwise, the values in self may be preserved or merged. When + and_source is True, the 'source' attribute is included in the copy. + + NOTE: When replace is False, and self contains a 'source' attribute, + 'source' is not replaced even when dict_ has a 'source' + attribute, though it may still be merged into a list depending + on the value of update_fun. + NOTE: It is easier to call the update-specific methods then to pass + the update_fun method to this function. + """ + if isinstance(dict_, Node): + dict_ = dict_.attributes + + # Include the source attribute when copying? + if and_source: + filter_fun = self.is_not_list_attribute + else: + filter_fun = self.is_not_known_attribute + + # Copy the basic attributes + self.update_basic_atts(dict_) + + # Grab other attributes in dict_ not in self except the + # (All basic attributes should be copied already) + for att in filter(filter_fun, dict_): + update_fun(self, att, dict_[att], replace) + + def update_all_atts_consistantly(self, dict_, replace=True, + and_source=False): + """ + Updates all attributes from node or dictionary `dict_`. + + Appends the basic attributes ('ids', 'names', 'classes', + 'dupnames', but not 'source') and then, for all other attributes in + dict_, updates the same attribute in self. When attributes with the + same identifier appear in both self and dict_ and replace is True, the + values in self are replaced with the values in dict_; otherwise, the + values in self are preserved. When and_source is True, the 'source' + attribute is included in the copy. + + NOTE: When replace is False, and self contains a 'source' attribute, + 'source' is not replaced even when dict_ has a 'source' + attribute, though it may still be merged into a list depending + on the value of update_fun. + """ + self.update_all_atts(dict_, Element.copy_attr_consistent, replace, + and_source) + + def update_all_atts_concatenating(self, dict_, replace=True, + and_source=False): + """ + Updates all attributes from node or dictionary `dict_`. + + Appends the basic attributes ('ids', 'names', 'classes', + 'dupnames', but not 'source') and then, for all other attributes in + dict_, updates the same attribute in self. When attributes with the + same identifier appear in both self and dict_ whose values aren't each + lists and replace is True, the values in self are replaced with the + values in dict_; if the values from self and dict_ for the given + identifier are both of list type, then the two lists are concatenated + and the result stored in self; otherwise, the values in self are + preserved. When and_source is True, the 'source' attribute is + included in the copy. + + NOTE: When replace is False, and self contains a 'source' attribute, + 'source' is not replaced even when dict_ has a 'source' + attribute, though it may still be merged into a list depending + on the value of update_fun. + """ + self.update_all_atts(dict_, Element.copy_attr_concatenate, replace, + and_source) + + def update_all_atts_coercion(self, dict_, replace=True, + and_source=False): + """ + Updates all attributes from node or dictionary `dict_`. + + Appends the basic attributes ('ids', 'names', 'classes', + 'dupnames', but not 'source') and then, for all other attributes in + dict_, updates the same attribute in self. When attributes with the + same identifier appear in both self and dict_ whose values are both + not lists and replace is True, the values in self are replaced with + the values in dict_; if either of the values from self and dict_ for + the given identifier are of list type, then first any non-lists are + converted to 1-element lists and then the two lists are concatenated + and the result stored in self; otherwise, the values in self are + preserved. When and_source is True, the 'source' attribute is + included in the copy. + + NOTE: When replace is False, and self contains a 'source' attribute, + 'source' is not replaced even when dict_ has a 'source' + attribute, though it may still be merged into a list depending + on the value of update_fun. + """ + self.update_all_atts(dict_, Element.copy_attr_coerce, replace, + and_source) + + def update_all_atts_convert(self, dict_, and_source=False): + """ + Updates all attributes from node or dictionary `dict_`. + + Appends the basic attributes ('ids', 'names', 'classes', + 'dupnames', but not 'source') and then, for all other attributes in + dict_, updates the same attribute in self. When attributes with the + same identifier appear in both self and dict_ then first any non-lists + are converted to 1-element lists and then the two lists are + concatenated and the result stored in self; otherwise, the values in + self are preserved. When and_source is True, the 'source' attribute + is included in the copy. + + NOTE: When replace is False, and self contains a 'source' attribute, + 'source' is not replaced even when dict_ has a 'source' + attribute, though it may still be merged into a list depending + on the value of update_fun. + """ + self.update_all_atts(dict_, Element.copy_attr_convert, + and_source=and_source) + + def clear(self): + self.children = [] + + def replace(self, old, new): + """Replace one child `Node` with another child or children.""" + index = self.index(old) + if isinstance(new, Node): + self.setup_child(new) + self[index] = new + elif new is not None: + self[index:index+1] = new + + def replace_self(self, new): + """ + Replace `self` node with `new`, where `new` is a node or a + list of nodes. + """ + update = new + if not isinstance(new, Node): + # `new` is a list; update first child. + try: + update = new[0] + except IndexError: + update = None + if isinstance(update, Element): + update.update_basic_atts(self) + else: + # `update` is a Text node or `new` is an empty list. + # Assert that we aren't losing any attributes. + for att in self.basic_attributes: + assert not self[att], \ + 'Losing "%s" attribute: %s' % (att, self[att]) + self.parent.replace(self, new) + + def first_child_matching_class(self, childclass, start=0, end=sys.maxsize): + """ + Return the index of the first child whose class exactly matches. + + Parameters: + + - `childclass`: A `Node` subclass to search for, or a tuple of `Node` + classes. If a tuple, any of the classes may match. + - `start`: Initial index to check. + - `end`: Initial index to *not* check. + """ + if not isinstance(childclass, tuple): + childclass = (childclass,) + for index in range(start, min(len(self), end)): + for c in childclass: + if isinstance(self[index], c): + return index + return None + + def first_child_not_matching_class(self, childclass, start=0, + end=sys.maxsize): + """ + Return the index of the first child whose class does *not* match. + + Parameters: + + - `childclass`: A `Node` subclass to skip, or a tuple of `Node` + classes. If a tuple, none of the classes may match. + - `start`: Initial index to check. + - `end`: Initial index to *not* check. + """ + if not isinstance(childclass, tuple): + childclass = (childclass,) + for index in range(start, min(len(self), end)): + for c in childclass: + if isinstance(self.children[index], c): + break + else: + return index + return None + + def pformat(self, indent=' ', level=0): + tagline = '%s%s\n' % (indent*level, self.starttag()) + childreps = (c.pformat(indent, level+1) for c in self.children) + return ''.join((tagline, *childreps)) + + def copy(self): + obj = self.__class__(rawsource=self.rawsource, **self.attributes) + obj._document = self._document + obj.source = self.source + obj.line = self.line + return obj + + def deepcopy(self): + copy = self.copy() + copy.extend([child.deepcopy() for child in self.children]) + return copy + + def set_class(self, name): + """Add a new class to the "classes" attribute.""" + warnings.warn('docutils.nodes.Element.set_class() is deprecated; ' + ' and will be removed in Docutils 0.21 or later.' + "Append to Element['classes'] list attribute directly", + DeprecationWarning, stacklevel=2) + assert ' ' not in name + self['classes'].append(name.lower()) + + def note_referenced_by(self, name=None, id=None): + """Note that this Element has been referenced by its name + `name` or id `id`.""" + self.referenced = True + # Element.expect_referenced_by_* dictionaries map names or ids + # to nodes whose ``referenced`` attribute is set to true as + # soon as this node is referenced by the given name or id. + # Needed for target propagation. + by_name = getattr(self, 'expect_referenced_by_name', {}).get(name) + by_id = getattr(self, 'expect_referenced_by_id', {}).get(id) + if by_name: + assert name is not None + by_name.referenced = True + if by_id: + assert id is not None + by_id.referenced = True + + @classmethod + def is_not_list_attribute(cls, attr): + """ + Returns True if and only if the given attribute is NOT one of the + basic list attributes defined for all Elements. + """ + return attr not in cls.list_attributes + + @classmethod + def is_not_known_attribute(cls, attr): + """ + Returns True if and only if the given attribute is NOT recognized by + this class. + """ + return attr not in cls.known_attributes + + +class TextElement(Element): + + """ + An element which directly contains text. + + Its children are all `Text` or `Inline` subclass nodes. You can + check whether an element's context is inline simply by checking whether + its immediate parent is a `TextElement` instance (including subclasses). + This is handy for nodes like `image` that can appear both inline and as + standalone body elements. + + If passing children to `__init__()`, make sure to set `text` to + ``''`` or some other suitable value. + """ + + child_text_separator = '' + """Separator for child nodes, used by `astext()` method.""" + + def __init__(self, rawsource='', text='', *children, **attributes): + if text != '': + textnode = Text(text) + Element.__init__(self, rawsource, textnode, *children, + **attributes) + else: + Element.__init__(self, rawsource, *children, **attributes) + + +class FixedTextElement(TextElement): + + """An element which directly contains preformatted text.""" + + def __init__(self, rawsource='', text='', *children, **attributes): + super().__init__(rawsource, text, *children, **attributes) + self.attributes['xml:space'] = 'preserve' + + +# TODO: PureTextElement(TextElement): +# """An element which only contains text, no children.""" +# For elements in the DTD that directly employ #PCDATA in their definition: +# citation_reference, comment, footnote_reference, label, math, math_block, +# option_argument, option_string, raw, + + +# ======== +# Mixins +# ======== + +class Resolvable: + + resolved = 0 + + +class BackLinkable: + + def add_backref(self, refid): + self['backrefs'].append(refid) + + +# ==================== +# Element Categories +# ==================== + +class Root: + pass + + +class Titular: + pass + + +class PreBibliographic: + """Category of Node which may occur before Bibliographic Nodes.""" + + +class Bibliographic: + pass + + +class Decorative(PreBibliographic): + pass + + +class Structural: + pass + + +class Body: + pass + + +class General(Body): + pass + + +class Sequential(Body): + """List-like elements.""" + + +class Admonition(Body): pass + + +class Special(Body): + """Special internal body elements.""" + + +class Invisible(PreBibliographic): + """Internal elements that don't appear in output.""" + + +class Part: + pass + + +class Inline: + pass + + +class Referential(Resolvable): + pass + + +class Targetable(Resolvable): + + referenced = 0 + + indirect_reference_name = None + """Holds the whitespace_normalized_name (contains mixed case) of a target. + Required for MoinMoin/reST compatibility.""" + + +class Labeled: + """Contains a `label` as its first element.""" + + +# ============== +# Root Element +# ============== + +class document(Root, Structural, Element): + + """ + The document root element. + + Do not instantiate this class directly; use + `docutils.utils.new_document()` instead. + """ + + def __init__(self, settings, reporter, *args, **kwargs): + Element.__init__(self, *args, **kwargs) + + self.current_source = None + """Path to or description of the input source being processed.""" + + self.current_line = None + """Line number (1-based) of `current_source`.""" + + self.settings = settings + """Runtime settings data record.""" + + self.reporter = reporter + """System message generator.""" + + self.indirect_targets = [] + """List of indirect target nodes.""" + + self.substitution_defs = {} + """Mapping of substitution names to substitution_definition nodes.""" + + self.substitution_names = {} + """Mapping of case-normalized substitution names to case-sensitive + names.""" + + self.refnames = {} + """Mapping of names to lists of referencing nodes.""" + + self.refids = {} + """Mapping of ids to lists of referencing nodes.""" + + self.nameids = {} + """Mapping of names to unique id's.""" + + self.nametypes = {} + """Mapping of names to hyperlink type (boolean: True => explicit, + False => implicit.""" + + self.ids = {} + """Mapping of ids to nodes.""" + + self.footnote_refs = {} + """Mapping of footnote labels to lists of footnote_reference nodes.""" + + self.citation_refs = {} + """Mapping of citation labels to lists of citation_reference nodes.""" + + self.autofootnotes = [] + """List of auto-numbered footnote nodes.""" + + self.autofootnote_refs = [] + """List of auto-numbered footnote_reference nodes.""" + + self.symbol_footnotes = [] + """List of symbol footnote nodes.""" + + self.symbol_footnote_refs = [] + """List of symbol footnote_reference nodes.""" + + self.footnotes = [] + """List of manually-numbered footnote nodes.""" + + self.citations = [] + """List of citation nodes.""" + + self.autofootnote_start = 1 + """Initial auto-numbered footnote number.""" + + self.symbol_footnote_start = 0 + """Initial symbol footnote symbol index.""" + + self.id_counter = Counter() + """Numbers added to otherwise identical IDs.""" + + self.parse_messages = [] + """System messages generated while parsing.""" + + self.transform_messages = [] + """System messages generated while applying transforms.""" + + import docutils.transforms + self.transformer = docutils.transforms.Transformer(self) + """Storage for transforms to be applied to this document.""" + + self.include_log = [] + """The current source's parents (to detect inclusion loops).""" + + self.decoration = None + """Document's `decoration` node.""" + + self._document = self + + def __getstate__(self): + """ + Return dict with unpicklable references removed. + """ + state = self.__dict__.copy() + state['reporter'] = None + state['transformer'] = None + return state + + def asdom(self, dom=None): + """Return a DOM representation of this document.""" + if dom is None: + import xml.dom.minidom as dom + domroot = dom.Document() + domroot.appendChild(self._dom_node(domroot)) + return domroot + + def set_id(self, node, msgnode=None, suggested_prefix=''): + if node['ids']: + # register and check for duplicates + for id in node['ids']: + self.ids.setdefault(id, node) + if self.ids[id] is not node: + msg = self.reporter.severe('Duplicate ID: "%s".' % id) + if msgnode is not None: + msgnode += msg + return id + # generate and set id + id_prefix = self.settings.id_prefix + auto_id_prefix = self.settings.auto_id_prefix + base_id = '' + id = '' + for name in node['names']: + if id_prefix: + # allow names starting with numbers if `id_prefix` + base_id = make_id('x'+name)[1:] + else: + base_id = make_id(name) + # TODO: normalize id-prefix? (would make code simpler) + id = id_prefix + base_id + if base_id and id not in self.ids: + break + else: + if base_id and auto_id_prefix.endswith('%'): + # disambiguate name-derived ID + # TODO: remove second condition after announcing change + prefix = id + '-' + else: + prefix = id_prefix + auto_id_prefix + if prefix.endswith('%'): + prefix = '%s%s-' % (prefix[:-1], + suggested_prefix + or make_id(node.tagname)) + while True: + self.id_counter[prefix] += 1 + id = '%s%d' % (prefix, self.id_counter[prefix]) + if id not in self.ids: + break + node['ids'].append(id) + self.ids[id] = node + return id + + def set_name_id_map(self, node, id, msgnode=None, explicit=None): + """ + `self.nameids` maps names to IDs, while `self.nametypes` maps names to + booleans representing hyperlink type (True==explicit, + False==implicit). This method updates the mappings. + + The following state transition table shows how `self.nameids` items + ("id") and `self.nametypes` items ("type") change with new input + (a call to this method), and what actions are performed + ("implicit"-type system messages are INFO/1, and + "explicit"-type system messages are ERROR/3): + + ==== ===== ======== ======== ======= ==== ===== ===== + Old State Input Action New State Notes + ----------- -------- ----------------- ----------- ----- + id type new type sys.msg. dupname id type + ==== ===== ======== ======== ======= ==== ===== ===== + - - explicit - - new True + - - implicit - - new False + - False explicit - - new True + old False explicit implicit old new True + - True explicit explicit new - True + old True explicit explicit new,old - True [#]_ + - False implicit implicit new - False + old False implicit implicit new,old - False + - True implicit implicit new - True + old True implicit implicit new old True + ==== ===== ======== ======== ======= ==== ===== ===== + + .. [#] Do not clear the name-to-id map or invalidate the old target if + both old and new targets are external and refer to identical URIs. + The new target is invalidated regardless. + """ + for name in tuple(node['names']): + if name in self.nameids: + self.set_duplicate_name_id(node, id, name, msgnode, explicit) + # attention: modifies node['names'] + else: + self.nameids[name] = id + self.nametypes[name] = explicit + + def set_duplicate_name_id(self, node, id, name, msgnode, explicit): + old_id = self.nameids[name] + old_explicit = self.nametypes[name] + self.nametypes[name] = old_explicit or explicit + if explicit: + if old_explicit: + level = 2 + if old_id is not None: + old_node = self.ids[old_id] + if 'refuri' in node: + refuri = node['refuri'] + if (old_node['names'] + and 'refuri' in old_node + and old_node['refuri'] == refuri): + level = 1 # just inform if refuri's identical + if level > 1: + dupname(old_node, name) + self.nameids[name] = None + msg = self.reporter.system_message( + level, 'Duplicate explicit target name: "%s".' % name, + backrefs=[id], base_node=node) + if msgnode is not None: + msgnode += msg + dupname(node, name) + else: + self.nameids[name] = id + if old_id is not None: + old_node = self.ids[old_id] + dupname(old_node, name) + else: + if old_id is not None and not old_explicit: + self.nameids[name] = None + old_node = self.ids[old_id] + dupname(old_node, name) + dupname(node, name) + if not explicit or (not old_explicit and old_id is not None): + msg = self.reporter.info( + 'Duplicate implicit target name: "%s".' % name, + backrefs=[id], base_node=node) + if msgnode is not None: + msgnode += msg + + def has_name(self, name): + return name in self.nameids + + # "note" here is an imperative verb: "take note of". + def note_implicit_target(self, target, msgnode=None): + id = self.set_id(target, msgnode) + self.set_name_id_map(target, id, msgnode, explicit=False) + + def note_explicit_target(self, target, msgnode=None): + id = self.set_id(target, msgnode) + self.set_name_id_map(target, id, msgnode, explicit=True) + + def note_refname(self, node): + self.refnames.setdefault(node['refname'], []).append(node) + + def note_refid(self, node): + self.refids.setdefault(node['refid'], []).append(node) + + def note_indirect_target(self, target): + self.indirect_targets.append(target) + if target['names']: + self.note_refname(target) + + def note_anonymous_target(self, target): + self.set_id(target) + + def note_autofootnote(self, footnote): + self.set_id(footnote) + self.autofootnotes.append(footnote) + + def note_autofootnote_ref(self, ref): + self.set_id(ref) + self.autofootnote_refs.append(ref) + + def note_symbol_footnote(self, footnote): + self.set_id(footnote) + self.symbol_footnotes.append(footnote) + + def note_symbol_footnote_ref(self, ref): + self.set_id(ref) + self.symbol_footnote_refs.append(ref) + + def note_footnote(self, footnote): + self.set_id(footnote) + self.footnotes.append(footnote) + + def note_footnote_ref(self, ref): + self.set_id(ref) + self.footnote_refs.setdefault(ref['refname'], []).append(ref) + self.note_refname(ref) + + def note_citation(self, citation): + self.citations.append(citation) + + def note_citation_ref(self, ref): + self.set_id(ref) + self.citation_refs.setdefault(ref['refname'], []).append(ref) + self.note_refname(ref) + + def note_substitution_def(self, subdef, def_name, msgnode=None): + name = whitespace_normalize_name(def_name) + if name in self.substitution_defs: + msg = self.reporter.error( + 'Duplicate substitution definition name: "%s".' % name, + base_node=subdef) + if msgnode is not None: + msgnode += msg + oldnode = self.substitution_defs[name] + dupname(oldnode, name) + # keep only the last definition: + self.substitution_defs[name] = subdef + # case-insensitive mapping: + self.substitution_names[fully_normalize_name(name)] = name + + def note_substitution_ref(self, subref, refname): + subref['refname'] = whitespace_normalize_name(refname) + + def note_pending(self, pending, priority=None): + self.transformer.add_pending(pending, priority) + + def note_parse_message(self, message): + self.parse_messages.append(message) + + def note_transform_message(self, message): + self.transform_messages.append(message) + + def note_source(self, source, offset): + self.current_source = source + if offset is None: + self.current_line = offset + else: + self.current_line = offset + 1 + + def copy(self): + obj = self.__class__(self.settings, self.reporter, + **self.attributes) + obj.source = self.source + obj.line = self.line + return obj + + def get_decoration(self): + if not self.decoration: + self.decoration = decoration() + index = self.first_child_not_matching_class((Titular, meta)) + if index is None: + self.append(self.decoration) + else: + self.insert(index, self.decoration) + return self.decoration + + +# ================ +# Title Elements +# ================ + +class title(Titular, PreBibliographic, TextElement): pass +class subtitle(Titular, PreBibliographic, TextElement): pass +class rubric(Titular, TextElement): pass + + +# ================== +# Meta-Data Element +# ================== + +class meta(PreBibliographic, Element): + """Container for "invisible" bibliographic data, or meta-data.""" + + +# ======================== +# Bibliographic Elements +# ======================== + +class docinfo(Bibliographic, Element): pass +class author(Bibliographic, TextElement): pass +class authors(Bibliographic, Element): pass +class organization(Bibliographic, TextElement): pass +class address(Bibliographic, FixedTextElement): pass +class contact(Bibliographic, TextElement): pass +class version(Bibliographic, TextElement): pass +class revision(Bibliographic, TextElement): pass +class status(Bibliographic, TextElement): pass +class date(Bibliographic, TextElement): pass +class copyright(Bibliographic, TextElement): pass + + +# ===================== +# Decorative Elements +# ===================== + +class decoration(Decorative, Element): + + def get_header(self): + if not len(self.children) or not isinstance(self.children[0], header): + self.insert(0, header()) + return self.children[0] + + def get_footer(self): + if not len(self.children) or not isinstance(self.children[-1], footer): + self.append(footer()) + return self.children[-1] + + +class header(Decorative, Element): pass +class footer(Decorative, Element): pass + + +# ===================== +# Structural Elements +# ===================== + +class section(Structural, Element): pass + + +class topic(Structural, Element): + + """ + Topics are terminal, "leaf" mini-sections, like block quotes with titles, + or textual figures. A topic is just like a section, except that it has no + subsections, and it doesn't have to conform to section placement rules. + + Topics are allowed wherever body elements (list, table, etc.) are allowed, + but only at the top level of a section or document. Topics cannot nest + inside topics, sidebars, or body elements; you can't have a topic inside a + table, list, block quote, etc. + """ + + +class sidebar(Structural, Element): + + """ + Sidebars are like miniature, parallel documents that occur inside other + documents, providing related or reference material. A sidebar is + typically offset by a border and "floats" to the side of the page; the + document's main text may flow around it. Sidebars can also be likened to + super-footnotes; their content is outside of the flow of the document's + main text. + + Sidebars are allowed wherever body elements (list, table, etc.) are + allowed, but only at the top level of a section or document. Sidebars + cannot nest inside sidebars, topics, or body elements; you can't have a + sidebar inside a table, list, block quote, etc. + """ + + +class transition(Structural, Element): pass + + +# =============== +# Body Elements +# =============== + +class paragraph(General, TextElement): pass +class compound(General, Element): pass +class container(General, Element): pass +class bullet_list(Sequential, Element): pass +class enumerated_list(Sequential, Element): pass +class list_item(Part, Element): pass +class definition_list(Sequential, Element): pass +class definition_list_item(Part, Element): pass +class term(Part, TextElement): pass +class classifier(Part, TextElement): pass +class definition(Part, Element): pass +class field_list(Sequential, Element): pass +class field(Part, Element): pass +class field_name(Part, TextElement): pass +class field_body(Part, Element): pass + + +class option(Part, Element): + + child_text_separator = '' + + +class option_argument(Part, TextElement): + + def astext(self): + return self.get('delimiter', ' ') + TextElement.astext(self) + + +class option_group(Part, Element): + + child_text_separator = ', ' + + +class option_list(Sequential, Element): pass + + +class option_list_item(Part, Element): + + child_text_separator = ' ' + + +class option_string(Part, TextElement): pass +class description(Part, Element): pass +class literal_block(General, FixedTextElement): pass +class doctest_block(General, FixedTextElement): pass +class math_block(General, FixedTextElement): pass +class line_block(General, Element): pass + + +class line(Part, TextElement): + + indent = None + + +class block_quote(General, Element): pass +class attribution(Part, TextElement): pass +class attention(Admonition, Element): pass +class caution(Admonition, Element): pass +class danger(Admonition, Element): pass +class error(Admonition, Element): pass +class important(Admonition, Element): pass +class note(Admonition, Element): pass +class tip(Admonition, Element): pass +class hint(Admonition, Element): pass +class warning(Admonition, Element): pass +class admonition(Admonition, Element): pass +class comment(Special, Invisible, FixedTextElement): pass +class substitution_definition(Special, Invisible, TextElement): pass +class target(Special, Invisible, Inline, TextElement, Targetable): pass +class footnote(General, BackLinkable, Element, Labeled, Targetable): pass +class citation(General, BackLinkable, Element, Labeled, Targetable): pass +class label(Part, TextElement): pass +class figure(General, Element): pass +class caption(Part, TextElement): pass +class legend(Part, Element): pass +class table(General, Element): pass +class tgroup(Part, Element): pass +class colspec(Part, Element): pass +class thead(Part, Element): pass +class tbody(Part, Element): pass +class row(Part, Element): pass +class entry(Part, Element): pass + + +class system_message(Special, BackLinkable, PreBibliographic, Element): + + """ + System message element. + + Do not instantiate this class directly; use + ``document.reporter.info/warning/error/severe()`` instead. + """ + + def __init__(self, message=None, *children, **attributes): + rawsource = attributes.pop('rawsource', '') + if message: + p = paragraph('', message) + children = (p,) + children + try: + Element.__init__(self, rawsource, *children, **attributes) + except: # noqa catchall + print('system_message: children=%r' % (children,)) + raise + + def astext(self): + line = self.get('line', '') + return '%s:%s: (%s/%s) %s' % (self['source'], line, self['type'], + self['level'], Element.astext(self)) + + +class pending(Special, Invisible, Element): + + """ + The "pending" element is used to encapsulate a pending operation: the + operation (transform), the point at which to apply it, and any data it + requires. Only the pending operation's location within the document is + stored in the public document tree (by the "pending" object itself); the + operation and its data are stored in the "pending" object's internal + instance attributes. + + For example, say you want a table of contents in your reStructuredText + document. The easiest way to specify where to put it is from within the + document, with a directive:: + + .. contents:: + + But the "contents" directive can't do its work until the entire document + has been parsed and possibly transformed to some extent. So the directive + code leaves a placeholder behind that will trigger the second phase of its + processing, something like this:: + + <pending ...public attributes...> + internal attributes + + Use `document.note_pending()` so that the + `docutils.transforms.Transformer` stage of processing can run all pending + transforms. + """ + + def __init__(self, transform, details=None, + rawsource='', *children, **attributes): + Element.__init__(self, rawsource, *children, **attributes) + + self.transform = transform + """The `docutils.transforms.Transform` class implementing the pending + operation.""" + + self.details = details or {} + """Detail data (dictionary) required by the pending operation.""" + + def pformat(self, indent=' ', level=0): + internals = ['.. internal attributes:', + ' .transform: %s.%s' % (self.transform.__module__, + self.transform.__name__), + ' .details:'] + details = sorted(self.details.items()) + for key, value in details: + if isinstance(value, Node): + internals.append('%7s%s:' % ('', key)) + internals.extend(['%9s%s' % ('', line) + for line in value.pformat().splitlines()]) + elif (value + and isinstance(value, list) + and isinstance(value[0], Node)): + internals.append('%7s%s:' % ('', key)) + for v in value: + internals.extend(['%9s%s' % ('', line) + for line in v.pformat().splitlines()]) + else: + internals.append('%7s%s: %r' % ('', key, value)) + return (Element.pformat(self, indent, level) + + ''.join((' %s%s\n' % (indent * level, line)) + for line in internals)) + + def copy(self): + obj = self.__class__(self.transform, self.details, self.rawsource, + **self.attributes) + obj._document = self._document + obj.source = self.source + obj.line = self.line + return obj + + +class raw(Special, Inline, PreBibliographic, FixedTextElement): + + """ + Raw data that is to be passed untouched to the Writer. + """ + + +# ================= +# Inline Elements +# ================= + +class emphasis(Inline, TextElement): pass +class strong(Inline, TextElement): pass +class literal(Inline, TextElement): pass +class reference(General, Inline, Referential, TextElement): pass +class footnote_reference(Inline, Referential, TextElement): pass +class citation_reference(Inline, Referential, TextElement): pass +class substitution_reference(Inline, TextElement): pass +class title_reference(Inline, TextElement): pass +class abbreviation(Inline, TextElement): pass +class acronym(Inline, TextElement): pass +class superscript(Inline, TextElement): pass +class subscript(Inline, TextElement): pass +class math(Inline, TextElement): pass + + +class image(General, Inline, Element): + + def astext(self): + return self.get('alt', '') + + +class inline(Inline, TextElement): pass +class problematic(Inline, TextElement): pass +class generated(Inline, TextElement): pass + + +# ======================================== +# Auxiliary Classes, Functions, and Data +# ======================================== + +node_class_names = """ + Text + abbreviation acronym address admonition attention attribution author + authors + block_quote bullet_list + caption caution citation citation_reference classifier colspec comment + compound contact container copyright + danger date decoration definition definition_list definition_list_item + description docinfo doctest_block document + emphasis entry enumerated_list error + field field_body field_list field_name figure footer + footnote footnote_reference + generated + header hint + image important inline + label legend line line_block list_item literal literal_block + math math_block meta + note + option option_argument option_group option_list option_list_item + option_string organization + paragraph pending problematic + raw reference revision row rubric + section sidebar status strong subscript substitution_definition + substitution_reference subtitle superscript system_message + table target tbody term tgroup thead tip title title_reference topic + transition + version + warning""".split() +"""A list of names of all concrete Node subclasses.""" + + +class NodeVisitor: + + """ + "Visitor" pattern [GoF95]_ abstract superclass implementation for + document tree traversals. + + Each node class has corresponding methods, doing nothing by + default; override individual methods for specific and useful + behaviour. The `dispatch_visit()` method is called by + `Node.walk()` upon entering a node. `Node.walkabout()` also calls + the `dispatch_departure()` method before exiting a node. + + The dispatch methods call "``visit_`` + node class name" or + "``depart_`` + node class name", resp. + + This is a base class for visitors whose ``visit_...`` & ``depart_...`` + methods must be implemented for *all* compulsory node types encountered + (such as for `docutils.writers.Writer` subclasses). + Unimplemented methods will raise exceptions (except for optional nodes). + + For sparse traversals, where only certain node types are of interest, use + subclass `SparseNodeVisitor` instead. When (mostly or entirely) uniform + processing is desired, subclass `GenericNodeVisitor`. + + .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of + Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA, + 1995. + """ + + optional = ('meta',) + """ + Tuple containing node class names (as strings). + + No exception will be raised if writers do not implement visit + or departure functions for these node classes. + + Used to ensure transitional compatibility with existing 3rd-party writers. + """ + + def __init__(self, document): + self.document = document + + def dispatch_visit(self, node): + """ + Call self."``visit_`` + node class name" with `node` as + parameter. If the ``visit_...`` method does not exist, call + self.unknown_visit. + """ + node_name = node.__class__.__name__ + method = getattr(self, 'visit_' + node_name, self.unknown_visit) + self.document.reporter.debug( + 'docutils.nodes.NodeVisitor.dispatch_visit calling %s for %s' + % (method.__name__, node_name)) + return method(node) + + def dispatch_departure(self, node): + """ + Call self."``depart_`` + node class name" with `node` as + parameter. If the ``depart_...`` method does not exist, call + self.unknown_departure. + """ + node_name = node.__class__.__name__ + method = getattr(self, 'depart_' + node_name, self.unknown_departure) + self.document.reporter.debug( + 'docutils.nodes.NodeVisitor.dispatch_departure calling %s for %s' + % (method.__name__, node_name)) + return method(node) + + def unknown_visit(self, node): + """ + Called when entering unknown `Node` types. + + Raise an exception unless overridden. + """ + if (self.document.settings.strict_visitor + or node.__class__.__name__ not in self.optional): + raise NotImplementedError( + '%s visiting unknown node type: %s' + % (self.__class__, node.__class__.__name__)) + + def unknown_departure(self, node): + """ + Called before exiting unknown `Node` types. + + Raise exception unless overridden. + """ + if (self.document.settings.strict_visitor + or node.__class__.__name__ not in self.optional): + raise NotImplementedError( + '%s departing unknown node type: %s' + % (self.__class__, node.__class__.__name__)) + + +class SparseNodeVisitor(NodeVisitor): + + """ + Base class for sparse traversals, where only certain node types are of + interest. When ``visit_...`` & ``depart_...`` methods should be + implemented for *all* node types (such as for `docutils.writers.Writer` + subclasses), subclass `NodeVisitor` instead. + """ + + +class GenericNodeVisitor(NodeVisitor): + + """ + Generic "Visitor" abstract superclass, for simple traversals. + + Unless overridden, each ``visit_...`` method calls `default_visit()`, and + each ``depart_...`` method (when using `Node.walkabout()`) calls + `default_departure()`. `default_visit()` (and `default_departure()`) must + be overridden in subclasses. + + Define fully generic visitors by overriding `default_visit()` (and + `default_departure()`) only. Define semi-generic visitors by overriding + individual ``visit_...()`` (and ``depart_...()``) methods also. + + `NodeVisitor.unknown_visit()` (`NodeVisitor.unknown_departure()`) should + be overridden for default behavior. + """ + + def default_visit(self, node): + """Override for generic, uniform traversals.""" + raise NotImplementedError + + def default_departure(self, node): + """Override for generic, uniform traversals.""" + raise NotImplementedError + + +def _call_default_visit(self, node): + self.default_visit(node) + + +def _call_default_departure(self, node): + self.default_departure(node) + + +def _nop(self, node): + pass + + +def _add_node_class_names(names): + """Save typing with dynamic assignments:""" + for _name in names: + setattr(GenericNodeVisitor, "visit_" + _name, _call_default_visit) + setattr(GenericNodeVisitor, "depart_" + _name, _call_default_departure) + setattr(SparseNodeVisitor, 'visit_' + _name, _nop) + setattr(SparseNodeVisitor, 'depart_' + _name, _nop) + + +_add_node_class_names(node_class_names) + + +class TreeCopyVisitor(GenericNodeVisitor): + + """ + Make a complete copy of a tree or branch, including element attributes. + """ + + def __init__(self, document): + GenericNodeVisitor.__init__(self, document) + self.parent_stack = [] + self.parent = [] + + def get_tree_copy(self): + return self.parent[0] + + def default_visit(self, node): + """Copy the current node, and make it the new acting parent.""" + newnode = node.copy() + self.parent.append(newnode) + self.parent_stack.append(self.parent) + self.parent = newnode + + def default_departure(self, node): + """Restore the previous acting parent.""" + self.parent = self.parent_stack.pop() + + +class TreePruningException(Exception): + + """ + Base class for `NodeVisitor`-related tree pruning exceptions. + + Raise subclasses from within ``visit_...`` or ``depart_...`` methods + called from `Node.walk()` and `Node.walkabout()` tree traversals to prune + the tree traversed. + """ + + +class SkipChildren(TreePruningException): + + """ + Do not visit any children of the current node. The current node's + siblings and ``depart_...`` method are not affected. + """ + + +class SkipSiblings(TreePruningException): + + """ + Do not visit any more siblings (to the right) of the current node. The + current node's children and its ``depart_...`` method are not affected. + """ + + +class SkipNode(TreePruningException): + + """ + Do not visit the current node's children, and do not call the current + node's ``depart_...`` method. + """ + + +class SkipDeparture(TreePruningException): + + """ + Do not call the current node's ``depart_...`` method. The current node's + children and siblings are not affected. + """ + + +class NodeFound(TreePruningException): + + """ + Raise to indicate that the target of a search has been found. This + exception must be caught by the client; it is not caught by the traversal + code. + """ + + +class StopTraversal(TreePruningException): + + """ + Stop the traversal altogether. The current node's ``depart_...`` method + is not affected. The parent nodes ``depart_...`` methods are also called + as usual. No other nodes are visited. This is an alternative to + NodeFound that does not cause exception handling to trickle up to the + caller. + """ + + +def make_id(string): + """ + Convert `string` into an identifier and return it. + + Docutils identifiers will conform to the regular expression + ``[a-z](-?[a-z0-9]+)*``. For CSS compatibility, identifiers (the "class" + and "id" attributes) should have no underscores, colons, or periods. + Hyphens may be used. + + - The `HTML 4.01 spec`_ defines identifiers based on SGML tokens: + + ID and NAME tokens must begin with a letter ([A-Za-z]) and may be + followed by any number of letters, digits ([0-9]), hyphens ("-"), + underscores ("_"), colons (":"), and periods ("."). + + - However the `CSS1 spec`_ defines identifiers based on the "name" token, + a tighter interpretation ("flex" tokenizer notation; "latin1" and + "escape" 8-bit characters have been replaced with entities):: + + unicode \\[0-9a-f]{1,4} + latin1 [¡-ÿ] + escape {unicode}|\\[ -~¡-ÿ] + nmchar [-a-z0-9]|{latin1}|{escape} + name {nmchar}+ + + The CSS1 "nmchar" rule does not include underscores ("_"), colons (":"), + or periods ("."), therefore "class" and "id" attributes should not contain + these characters. They should be replaced with hyphens ("-"). Combined + with HTML's requirements (the first character must be a letter; no + "unicode", "latin1", or "escape" characters), this results in the + ``[a-z](-?[a-z0-9]+)*`` pattern. + + .. _HTML 4.01 spec: https://www.w3.org/TR/html401 + .. _CSS1 spec: https://www.w3.org/TR/REC-CSS1 + """ + id = string.lower() + id = id.translate(_non_id_translate_digraphs) + id = id.translate(_non_id_translate) + # get rid of non-ascii characters. + # 'ascii' lowercase to prevent problems with turkish locale. + id = unicodedata.normalize( + 'NFKD', id).encode('ascii', 'ignore').decode('ascii') + # shrink runs of whitespace and replace by hyphen + id = _non_id_chars.sub('-', ' '.join(id.split())) + id = _non_id_at_ends.sub('', id) + return str(id) + + +_non_id_chars = re.compile('[^a-z0-9]+') +_non_id_at_ends = re.compile('^[-0-9]+|-+$') +_non_id_translate = { + 0x00f8: 'o', # o with stroke + 0x0111: 'd', # d with stroke + 0x0127: 'h', # h with stroke + 0x0131: 'i', # dotless i + 0x0142: 'l', # l with stroke + 0x0167: 't', # t with stroke + 0x0180: 'b', # b with stroke + 0x0183: 'b', # b with topbar + 0x0188: 'c', # c with hook + 0x018c: 'd', # d with topbar + 0x0192: 'f', # f with hook + 0x0199: 'k', # k with hook + 0x019a: 'l', # l with bar + 0x019e: 'n', # n with long right leg + 0x01a5: 'p', # p with hook + 0x01ab: 't', # t with palatal hook + 0x01ad: 't', # t with hook + 0x01b4: 'y', # y with hook + 0x01b6: 'z', # z with stroke + 0x01e5: 'g', # g with stroke + 0x0225: 'z', # z with hook + 0x0234: 'l', # l with curl + 0x0235: 'n', # n with curl + 0x0236: 't', # t with curl + 0x0237: 'j', # dotless j + 0x023c: 'c', # c with stroke + 0x023f: 's', # s with swash tail + 0x0240: 'z', # z with swash tail + 0x0247: 'e', # e with stroke + 0x0249: 'j', # j with stroke + 0x024b: 'q', # q with hook tail + 0x024d: 'r', # r with stroke + 0x024f: 'y', # y with stroke +} +_non_id_translate_digraphs = { + 0x00df: 'sz', # ligature sz + 0x00e6: 'ae', # ae + 0x0153: 'oe', # ligature oe + 0x0238: 'db', # db digraph + 0x0239: 'qp', # qp digraph +} + + +def dupname(node, name): + node['dupnames'].append(name) + node['names'].remove(name) + # Assume that `node` is referenced, even though it isn't; + # we don't want to throw unnecessary system_messages. + node.referenced = True + + +def fully_normalize_name(name): + """Return a case- and whitespace-normalized name.""" + return ' '.join(name.lower().split()) + + +def whitespace_normalize_name(name): + """Return a whitespace-normalized name.""" + return ' '.join(name.split()) + + +def serial_escape(value): + """Escape string values that are elements of a list, for serialization.""" + return value.replace('\\', r'\\').replace(' ', r'\ ') + + +def pseudo_quoteattr(value): + """Quote attributes for pseudo-xml""" + return '"%s"' % value diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/__init__.py b/.venv/lib/python3.12/site-packages/docutils/parsers/__init__.py new file mode 100644 index 00000000..f1bb268e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/__init__.py @@ -0,0 +1,92 @@ +# $Id: __init__.py 9048 2022-03-29 21:50:15Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This package contains Docutils parser modules. +""" + +__docformat__ = 'reStructuredText' + +from importlib import import_module + +from docutils import Component, frontend + + +class Parser(Component): + settings_spec = ( + 'Generic Parser Options', + None, + (('Disable directives that insert the contents of an external file; ' + 'replaced with a "warning" system message.', + ['--no-file-insertion'], + {'action': 'store_false', 'default': 1, + 'dest': 'file_insertion_enabled', + 'validator': frontend.validate_boolean}), + ('Enable directives that insert the contents ' + 'of an external file. (default)', + ['--file-insertion-enabled'], + {'action': 'store_true'}), + ('Disable the "raw" directive; ' + 'replaced with a "warning" system message.', + ['--no-raw'], + {'action': 'store_false', 'default': 1, 'dest': 'raw_enabled', + 'validator': frontend.validate_boolean}), + ('Enable the "raw" directive. (default)', + ['--raw-enabled'], + {'action': 'store_true'}), + ('Maximal number of characters in an input line. Default 10 000.', + ['--line-length-limit'], + {'metavar': '<length>', 'type': 'int', 'default': 10000, + 'validator': frontend.validate_nonnegative_int}), + ) + ) + component_type = 'parser' + config_section = 'parsers' + + def parse(self, inputstring, document): + """Override to parse `inputstring` into document tree `document`.""" + raise NotImplementedError('subclass must override this method') + + def setup_parse(self, inputstring, document): + """Initial parse setup. Call at start of `self.parse()`.""" + self.inputstring = inputstring + # provide fallbacks in case the document has only generic settings + document.settings.setdefault('file_insertion_enabled', False) + document.settings.setdefault('raw_enabled', False) + document.settings.setdefault('line_length_limit', 10000) + self.document = document + document.reporter.attach_observer(document.note_parse_message) + + def finish_parse(self): + """Finalize parse details. Call at end of `self.parse()`.""" + self.document.reporter.detach_observer( + self.document.note_parse_message) + + +_parser_aliases = { # short names for known parsers + 'null': 'docutils.parsers.null', + # reStructuredText + 'rst': 'docutils.parsers.rst', + 'restructuredtext': 'docutils.parsers.rst', + 'rest': 'docutils.parsers.rst', + 'restx': 'docutils.parsers.rst', + 'rtxt': 'docutils.parsers.rst', + # 3rd-party Markdown parsers + 'recommonmark': 'docutils.parsers.recommonmark_wrapper', + 'myst': 'myst_parser.docutils_', + # 'pycmark': works out of the box + # dispatcher for 3rd-party Markdown parsers + 'commonmark': 'docutils.parsers.commonmark_wrapper', + 'markdown': 'docutils.parsers.commonmark_wrapper', + } + + +def get_parser_class(parser_name): + """Return the Parser class from the `parser_name` module.""" + name = parser_name.lower() + try: + module = import_module(_parser_aliases.get(name, name)) + except ImportError as err: + raise ImportError(f'Parser "{parser_name}" not found. {err}') + return module.Parser diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/commonmark_wrapper.py b/.venv/lib/python3.12/site-packages/docutils/parsers/commonmark_wrapper.py new file mode 100644 index 00000000..ea538a6b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/commonmark_wrapper.py @@ -0,0 +1,56 @@ +#! /usr/bin/env python3 +# :Copyright: © 2022 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 +# +# Revision: $Revision: 9561 $ +# Date: $Date: 2024-03-14 17:34:48 +0100 (Do, 14. Mär 2024) $ +""" +An interface for parsing CommonMark input. + +Select a locally installed parser from the following 3rd-party +parser packages: + +:pycmark: https://pypi.org/project/pycmark/ +:myst: https://pypi.org/project/myst-docutils/ +:recommonmark: https://pypi.org/project/recommonmark/ (deprecated) + +The first parser class that can be successfully imported is mapped to +`commonmark_wrapper.Parser`. + +This module is provisional: +the API is not settled and may change with any minor Docutils version. +""" + +import docutils.parsers + + +commonmark_parser_names = ('pycmark', 'myst', 'recommonmark') +"""Names of compatible drop-in CommonMark parsers""" + +Parser = None +parser_name = '' + +for name in commonmark_parser_names: + try: + Parser = docutils.parsers.get_parser_class(name) + except ImportError: + continue + parser_name = name + break + +if Parser is None: + raise ImportError( + 'Parsing "CommonMark" requires one of the packages\n' + f'{commonmark_parser_names} available at https://pypi.org') + +if parser_name == 'myst': + if not Parser.settings_defaults: + Parser.settings_defaults = {} + Parser.settings_defaults['myst_commonmark_only'] = True diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/null.py b/.venv/lib/python3.12/site-packages/docutils/parsers/null.py new file mode 100644 index 00000000..238c4502 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/null.py @@ -0,0 +1,20 @@ +# $Id: null.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: Martin Blais <blais@furius.ca> +# Copyright: This module has been placed in the public domain. + +"""A do-nothing parser.""" + +from docutils import parsers + + +class Parser(parsers.Parser): + + """A do-nothing parser.""" + + supported = ('null',) + + config_section = 'null parser' + config_section_dependencies = ('parsers',) + + def parse(self, inputstring, document): + pass diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/recommonmark_wrapper.py b/.venv/lib/python3.12/site-packages/docutils/parsers/recommonmark_wrapper.py new file mode 100644 index 00000000..151a8bde --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/recommonmark_wrapper.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# :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: https://opensource.org/licenses/BSD-2-Clause +# +# Revision: $Revision: 9302 $ +# Date: $Date: 2022-12-02 18:14:05 +0100 (Fr, 02. Dez 2022) $ +""" +A parser for CommonMark Markdown text using `recommonmark`__. + +__ https://pypi.org/project/recommonmark/ + +.. important:: This module is provisional + + * The "recommonmark" package is unmaintained and deprecated. + This wrapper module will be removed in a future Docutils version. + + * The API is not settled and may change with any minor Docutils version. +""" + +from docutils import Component +from docutils import nodes + +try: + # If possible, import Sphinx's 'addnodes' + from sphinx import addnodes +except ImportError: + # stub to prevent errors if Sphinx isn't installed + import sys + import types + + class pending_xref(nodes.Inline, nodes.Element): ... # NoQA + + sys.modules['sphinx'] = sphinx = types.ModuleType('sphinx') + sphinx.addnodes = addnodes = types.SimpleNamespace() + addnodes.pending_xref = pending_xref +try: + import recommonmark + from recommonmark.parser import CommonMarkParser +except ImportError as err: + raise ImportError( + 'Parsing "recommonmark" Markdown flavour requires the\n' + ' package https://pypi.org/project/recommonmark.' + ) from err +else: + if recommonmark.__version__ < '0.6.0': + raise ImportError('The installed version of "recommonmark" is too old.' + ' Update with "pip install -U recommonmark".') + + +# auxiliary function for `document.findall()` +def is_literal(node): + return isinstance(node, (nodes.literal, nodes.literal_block)) + + +class Parser(CommonMarkParser): + """MarkDown parser based on recommonmark. + + This parser is provisional: + the API is not settled and may change with any minor Docutils version. + """ + supported = ('recommonmark', 'commonmark', 'markdown', 'md') + """Formats this parser supports.""" + + config_section = 'recommonmark parser' + config_section_dependencies = ('parsers',) + + def get_transforms(self): + return Component.get_transforms(self) # + [AutoStructify] + + def parse(self, inputstring, document): + """Use the upstream parser and clean up afterwards. + """ + # check for exorbitantly long lines + for i, line in enumerate(inputstring.split('\n')): + if len(line) > document.settings.line_length_limit: + error = document.reporter.error( + 'Line %d exceeds the line-length-limit.'%(i+1)) + document.append(error) + return + + # pass to upstream parser + try: + CommonMarkParser.parse(self, inputstring, document) + except Exception as err: + if document.settings.traceback: + raise err + error = document.reporter.error('Parsing with "recommonmark" ' + 'returned the error:\n%s'%err) + document.append(error) + + # Post-Processing + # --------------- + + # merge adjoining Text nodes: + for node in document.findall(nodes.TextElement): + children = node.children + i = 0 + while i+1 < len(children): + if (isinstance(children[i], nodes.Text) + and isinstance(children[i+1], nodes.Text)): + children[i] = nodes.Text(children[i]+children.pop(i+1)) + children[i].parent = node + else: + i += 1 + + # add "code" class argument to literal elements (inline and block) + for node in document.findall(is_literal): + if 'code' not in node['classes']: + node['classes'].append('code') + # move "language" argument to classes + for node in document.findall(nodes.literal_block): + if 'language' in node.attributes: + node['classes'].append(node['language']) + del node['language'] + + # replace raw nodes if raw is not allowed + if not document.settings.raw_enabled: + for node in document.findall(nodes.raw): + warning = document.reporter.warning('Raw content disabled.') + node.parent.replace(node, warning) + + # drop pending_xref (Sphinx cross reference extension) + for node in document.findall(addnodes.pending_xref): + reference = node.children[0] + if 'name' not in reference: + reference['name'] = nodes.fully_normalize_name( + reference.astext()) + node.parent.replace(node, reference) + + def visit_document(self, node): + """Dummy function to prevent spurious warnings. + + cf. https://github.com/readthedocs/recommonmark/issues/177 + """ + pass + + # Overwrite parent method with version that + # doesn't pass deprecated `rawsource` argument to nodes.Text: + def visit_text(self, mdnode): + self.current_node.append(nodes.Text(mdnode.literal)) diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/__init__.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/__init__.py new file mode 100644 index 00000000..460a2e95 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/__init__.py @@ -0,0 +1,413 @@ +# $Id: __init__.py 9258 2022-11-21 14:51:43Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This is ``docutils.parsers.rst`` package. It exports a single class, `Parser`, +the reStructuredText parser. + + +Usage +===== + +1. Create a parser:: + + parser = docutils.parsers.rst.Parser() + + Several optional arguments may be passed to modify the parser's behavior. + Please see `Customizing the Parser`_ below for details. + +2. Gather input (a multi-line string), by reading a file or the standard + input:: + + input = sys.stdin.read() + +3. Create a new empty `docutils.nodes.document` tree:: + + document = docutils.utils.new_document(source, settings) + + See `docutils.utils.new_document()` for parameter details. + +4. Run the parser, populating the document tree:: + + parser.parse(input, document) + + +Parser Overview +=============== + +The reStructuredText parser is implemented as a state machine, examining its +input one line at a time. To understand how the parser works, please first +become familiar with the `docutils.statemachine` module, then see the +`states` module. + + +Customizing the Parser +---------------------- + +Anything that isn't already customizable is that way simply because that type +of customizability hasn't been implemented yet. Patches welcome! + +When instantiating an object of the `Parser` class, two parameters may be +passed: ``rfc2822`` and ``inliner``. Pass ``rfc2822=True`` to enable an +initial RFC-2822 style header block, parsed as a "field_list" element (with +"class" attribute set to "rfc2822"). Currently this is the only body-level +element which is customizable without subclassing. (Tip: subclass `Parser` +and change its "state_classes" and "initial_state" attributes to refer to new +classes. Contact the author if you need more details.) + +The ``inliner`` parameter takes an instance of `states.Inliner` or a subclass. +It handles inline markup recognition. A common extension is the addition of +further implicit hyperlinks, like "RFC 2822". This can be done by subclassing +`states.Inliner`, adding a new method for the implicit markup, and adding a +``(pattern, method)`` pair to the "implicit_dispatch" attribute of the +subclass. See `states.Inliner.implicit_inline()` for details. Explicit +inline markup can be customized in a `states.Inliner` subclass via the +``patterns.initial`` and ``dispatch`` attributes (and new methods as +appropriate). +""" + +__docformat__ = 'reStructuredText' + + +import docutils.parsers +import docutils.statemachine +from docutils.parsers.rst import roles, states +from docutils import frontend, nodes +from docutils.transforms import universal + + +class Parser(docutils.parsers.Parser): + + """The reStructuredText parser.""" + + supported = ('rst', 'restructuredtext', 'rest', 'restx', 'rtxt', 'rstx') + """Aliases this parser supports.""" + + settings_spec = docutils.parsers.Parser.settings_spec + ( + 'reStructuredText Parser Options', + None, + (('Recognize and link to standalone PEP references (like "PEP 258").', + ['--pep-references'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Base URL for PEP references ' + '(default "https://peps.python.org/").', + ['--pep-base-url'], + {'metavar': '<URL>', 'default': 'https://peps.python.org/', + 'validator': frontend.validate_url_trailing_slash}), + ('Template for PEP file part of URL. (default "pep-%04d")', + ['--pep-file-url-template'], + {'metavar': '<URL>', 'default': 'pep-%04d'}), + ('Recognize and link to standalone RFC references (like "RFC 822").', + ['--rfc-references'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Base URL for RFC references ' + '(default "https://tools.ietf.org/html/").', + ['--rfc-base-url'], + {'metavar': '<URL>', 'default': 'https://tools.ietf.org/html/', + 'validator': frontend.validate_url_trailing_slash}), + ('Set number of spaces for tab expansion (default 8).', + ['--tab-width'], + {'metavar': '<width>', 'type': 'int', 'default': 8, + 'validator': frontend.validate_nonnegative_int}), + ('Remove spaces before footnote references.', + ['--trim-footnote-reference-space'], + {'action': 'store_true', 'validator': frontend.validate_boolean}), + ('Leave spaces before footnote references.', + ['--leave-footnote-reference-space'], + {'action': 'store_false', 'dest': 'trim_footnote_reference_space'}), + ('Token name set for parsing code with Pygments: one of ' + '"long", "short", or "none" (no parsing). Default is "long".', + ['--syntax-highlight'], + {'choices': ['long', 'short', 'none'], + 'default': 'long', 'metavar': '<format>'}), + ('Change straight quotation marks to typographic form: ' + 'one of "yes", "no", "alt[ernative]" (default "no").', + ['--smart-quotes'], + {'default': False, 'metavar': '<yes/no/alt>', + 'validator': frontend.validate_ternary}), + ('Characters to use as "smart quotes" for <language>. ', + ['--smartquotes-locales'], + {'metavar': '<language:quotes[,language:quotes,...]>', + 'action': 'append', + 'validator': frontend.validate_smartquotes_locales}), + ('Inline markup recognized at word boundaries only ' + '(adjacent to punctuation or whitespace). ' + 'Force character-level inline markup recognition with ' + '"\\ " (backslash + space). Default.', + ['--word-level-inline-markup'], + {'action': 'store_false', 'dest': 'character_level_inline_markup'}), + ('Inline markup recognized anywhere, regardless of surrounding ' + 'characters. Backslash-escapes must be used to avoid unwanted ' + 'markup recognition. Useful for East Asian languages. ' + 'Experimental.', + ['--character-level-inline-markup'], + {'action': 'store_true', 'default': False, + 'dest': 'character_level_inline_markup'}), + ) + ) + + config_section = 'restructuredtext parser' + config_section_dependencies = ('parsers',) + + def __init__(self, rfc2822=False, inliner=None): + if rfc2822: + self.initial_state = 'RFC2822Body' + else: + self.initial_state = 'Body' + self.state_classes = states.state_classes + self.inliner = inliner + + def get_transforms(self): + return super().get_transforms() + [universal.SmartQuotes] + + def parse(self, inputstring, document): + """Parse `inputstring` and populate `document`, a document tree.""" + self.setup_parse(inputstring, document) + # provide fallbacks in case the document has only generic settings + self.document.settings.setdefault('tab_width', 8) + self.document.settings.setdefault('syntax_highlight', 'long') + self.statemachine = states.RSTStateMachine( + state_classes=self.state_classes, + initial_state=self.initial_state, + debug=document.reporter.debug_flag) + inputlines = docutils.statemachine.string2lines( + inputstring, tab_width=document.settings.tab_width, + convert_whitespace=True) + for i, line in enumerate(inputlines): + if len(line) > self.document.settings.line_length_limit: + error = self.document.reporter.error( + 'Line %d exceeds the line-length-limit.'%(i+1)) + self.document.append(error) + break + else: + self.statemachine.run(inputlines, document, inliner=self.inliner) + # restore the "default" default role after parsing a document + if '' in roles._roles: + del roles._roles[''] + self.finish_parse() + + +class DirectiveError(Exception): + + """ + Store a message and a system message level. + + To be thrown from inside directive code. + + Do not instantiate directly -- use `Directive.directive_error()` + instead! + """ + + def __init__(self, level, message): + """Set error `message` and `level`""" + Exception.__init__(self) + self.level = level + self.msg = message + + +class Directive: + + """ + Base class for reStructuredText directives. + + The following attributes may be set by subclasses. They are + interpreted by the directive parser (which runs the directive + class): + + - `required_arguments`: The number of required arguments (default: + 0). + + - `optional_arguments`: The number of optional arguments (default: + 0). + + - `final_argument_whitespace`: A boolean, indicating if the final + argument may contain whitespace (default: False). + + - `option_spec`: A dictionary, mapping known option names to + conversion functions such as `int` or `float` (default: {}, no + options). Several conversion functions are defined in the + directives/__init__.py module. + + Option conversion functions take a single parameter, the option + argument (a string or ``None``), validate it and/or convert it + to the appropriate form. Conversion functions may raise + `ValueError` and `TypeError` exceptions. + + - `has_content`: A boolean; True if content is allowed. Client + code must handle the case where content is required but not + supplied (an empty content list will be supplied). + + Arguments are normally single whitespace-separated words. The + final argument may contain whitespace and/or newlines if + `final_argument_whitespace` is True. + + If the form of the arguments is more complex, specify only one + argument (either required or optional) and set + `final_argument_whitespace` to True; the client code must do any + context-sensitive parsing. + + When a directive implementation is being run, the directive class + is instantiated, and the `run()` method is executed. During + instantiation, the following instance variables are set: + + - ``name`` is the directive type or name (string). + + - ``arguments`` is the list of positional arguments (strings). + + - ``options`` is a dictionary mapping option names (strings) to + values (type depends on option conversion functions; see + `option_spec` above). + + - ``content`` is a list of strings, the directive content line by line. + + - ``lineno`` is the absolute line number of the first line + of the directive. + + - ``content_offset`` is the line offset of the first line + of the content from the beginning of the current input. + Used when initiating a nested parse. + + - ``block_text`` is a string containing the entire directive. + + - ``state`` is the state which called the directive function. + + - ``state_machine`` is the state machine which controls the state + which called the directive function. + + - ``reporter`` is the state machine's `reporter` instance. + + Directive functions return a list of nodes which will be inserted + into the document tree at the point where the directive was + encountered. This can be an empty list if there is nothing to + insert. + + For ordinary directives, the list must contain body elements or + structural elements. Some directives are intended specifically + for substitution definitions, and must return a list of `Text` + nodes and/or inline elements (suitable for inline insertion, in + place of the substitution reference). Such directives must verify + substitution definition context, typically using code like this:: + + if not isinstance(state, states.SubstitutionDef): + error = self.reporter.error( + 'Invalid context: the "%s" directive can only be used ' + 'within a substitution definition.' % (name), + nodes.literal_block(block_text, block_text), line=lineno) + return [error] + """ + + # There is a "Creating reStructuredText Directives" how-to at + # <https://docutils.sourceforge.io/docs/howto/rst-directives.html>. If you + # update this docstring, please update the how-to as well. + + required_arguments = 0 + """Number of required directive arguments.""" + + optional_arguments = 0 + """Number of optional arguments after the required arguments.""" + + final_argument_whitespace = False + """May the final argument contain whitespace?""" + + option_spec = None + """Mapping of option names to validator functions.""" + + has_content = False + """May the directive have content?""" + + def __init__(self, name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + self.name = name + self.arguments = arguments + self.options = options + self.content = content + self.lineno = lineno + self.content_offset = content_offset + self.block_text = block_text + self.state = state + self.state_machine = state_machine + self.reporter = state_machine.reporter + + def run(self): + raise NotImplementedError('Must override run() in subclass.') + + # Directive errors: + + def directive_error(self, level, message): + """ + Return a DirectiveError suitable for being thrown as an exception. + + Call "raise self.directive_error(level, message)" from within + a directive implementation to return one single system message + at level `level`, which automatically gets the directive block + and the line number added. + + Preferably use the `debug`, `info`, `warning`, `error`, or `severe` + wrapper methods, e.g. ``self.error(message)`` to generate an + ERROR-level directive error. + """ + return DirectiveError(level, message) + + def debug(self, message): + return self.directive_error(0, message) + + def info(self, message): + return self.directive_error(1, message) + + def warning(self, message): + return self.directive_error(2, message) + + def error(self, message): + return self.directive_error(3, message) + + def severe(self, message): + return self.directive_error(4, message) + + # Convenience methods: + + def assert_has_content(self): + """ + Throw an ERROR-level DirectiveError if the directive doesn't + have contents. + """ + if not self.content: + raise self.error('Content block expected for the "%s" directive; ' + 'none found.' % self.name) + + def add_name(self, node): + """Append self.options['name'] to node['names'] if it exists. + + Also normalize the name string and register it as explicit target. + """ + if 'name' in self.options: + name = nodes.fully_normalize_name(self.options.pop('name')) + if 'name' in node: + del node['name'] + node['names'].append(name) + self.state.document.note_explicit_target(node, node) + + +def convert_directive_function(directive_fn): + """ + Define & return a directive class generated from `directive_fn`. + + `directive_fn` uses the old-style, functional interface. + """ + + class FunctionalDirective(Directive): + + option_spec = getattr(directive_fn, 'options', None) + has_content = getattr(directive_fn, 'content', False) + _argument_spec = getattr(directive_fn, 'arguments', (0, 0, False)) + required_arguments, optional_arguments, final_argument_whitespace \ + = _argument_spec + + def run(self): + return directive_fn( + self.name, self.arguments, self.options, self.content, + self.lineno, self.content_offset, self.block_text, + self.state, self.state_machine) + + # Return new-style directive. + return FunctionalDirective diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py new file mode 100644 index 00000000..ebbdfe3b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/__init__.py @@ -0,0 +1,466 @@ +# $Id: __init__.py 9426 2023-07-03 12:38:54Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This package contains directive implementation modules. +""" + +__docformat__ = 'reStructuredText' + +import re +import codecs +from importlib import import_module + +from docutils import nodes, parsers +from docutils.utils import split_escaped_whitespace, escape2null +from docutils.parsers.rst.languages import en as _fallback_language_module + + +_directive_registry = { + 'attention': ('admonitions', 'Attention'), + 'caution': ('admonitions', 'Caution'), + 'code': ('body', 'CodeBlock'), + 'danger': ('admonitions', 'Danger'), + 'error': ('admonitions', 'Error'), + 'important': ('admonitions', 'Important'), + 'note': ('admonitions', 'Note'), + 'tip': ('admonitions', 'Tip'), + 'hint': ('admonitions', 'Hint'), + 'warning': ('admonitions', 'Warning'), + 'admonition': ('admonitions', 'Admonition'), + 'sidebar': ('body', 'Sidebar'), + 'topic': ('body', 'Topic'), + 'line-block': ('body', 'LineBlock'), + 'parsed-literal': ('body', 'ParsedLiteral'), + 'math': ('body', 'MathBlock'), + 'rubric': ('body', 'Rubric'), + 'epigraph': ('body', 'Epigraph'), + 'highlights': ('body', 'Highlights'), + 'pull-quote': ('body', 'PullQuote'), + 'compound': ('body', 'Compound'), + 'container': ('body', 'Container'), + # 'questions': ('body', 'question_list'), + 'table': ('tables', 'RSTTable'), + 'csv-table': ('tables', 'CSVTable'), + 'list-table': ('tables', 'ListTable'), + 'image': ('images', 'Image'), + 'figure': ('images', 'Figure'), + 'contents': ('parts', 'Contents'), + 'sectnum': ('parts', 'Sectnum'), + 'header': ('parts', 'Header'), + 'footer': ('parts', 'Footer'), + # 'footnotes': ('parts', 'footnotes'), + # 'citations': ('parts', 'citations'), + 'target-notes': ('references', 'TargetNotes'), + 'meta': ('misc', 'Meta'), + # 'imagemap': ('html', 'imagemap'), + 'raw': ('misc', 'Raw'), + 'include': ('misc', 'Include'), + 'replace': ('misc', 'Replace'), + 'unicode': ('misc', 'Unicode'), + 'class': ('misc', 'Class'), + 'role': ('misc', 'Role'), + 'default-role': ('misc', 'DefaultRole'), + 'title': ('misc', 'Title'), + 'date': ('misc', 'Date'), + 'restructuredtext-test-directive': ('misc', 'TestDirective'), + } +"""Mapping of directive name to (module name, class name). The +directive name is canonical & must be lowercase. Language-dependent +names are defined in the ``language`` subpackage.""" + +_directives = {} +"""Cache of imported directives.""" + + +def directive(directive_name, language_module, document): + """ + Locate and return a directive function from its language-dependent name. + If not found in the current language, check English. Return None if the + named directive cannot be found. + """ + normname = directive_name.lower() + messages = [] + msg_text = [] + if normname in _directives: + return _directives[normname], messages + canonicalname = None + try: + canonicalname = language_module.directives[normname] + except AttributeError as error: + msg_text.append('Problem retrieving directive entry from language ' + 'module %r: %s.' % (language_module, error)) + except KeyError: + msg_text.append('No directive entry for "%s" in module "%s".' + % (directive_name, language_module.__name__)) + if not canonicalname: + try: + canonicalname = _fallback_language_module.directives[normname] + msg_text.append('Using English fallback for directive "%s".' + % directive_name) + except KeyError: + msg_text.append('Trying "%s" as canonical directive name.' + % directive_name) + # The canonical name should be an English name, but just in case: + canonicalname = normname + if msg_text: + message = document.reporter.info( + '\n'.join(msg_text), line=document.current_line) + messages.append(message) + try: + modulename, classname = _directive_registry[canonicalname] + except KeyError: + # Error handling done by caller. + return None, messages + try: + module = import_module('docutils.parsers.rst.directives.'+modulename) + except ImportError as detail: + messages.append(document.reporter.error( + 'Error importing directive module "%s" (directive "%s"):\n%s' + % (modulename, directive_name, detail), + line=document.current_line)) + return None, messages + try: + directive = getattr(module, classname) + _directives[normname] = directive + except AttributeError: + messages.append(document.reporter.error( + 'No directive class "%s" in module "%s" (directive "%s").' + % (classname, modulename, directive_name), + line=document.current_line)) + return None, messages + return directive, messages + + +def register_directive(name, directive): + """ + Register a nonstandard application-defined directive function. + Language lookups are not needed for such functions. + """ + _directives[name] = directive + + +# conversion functions for `Directive.option_spec` +# ------------------------------------------------ +# +# see also `parsers.rst.Directive` in ../__init__.py. + + +def flag(argument): + """ + Check for a valid flag option (no argument) and return ``None``. + (Directive option conversion function.) + + Raise ``ValueError`` if an argument is found. + """ + if argument and argument.strip(): + raise ValueError('no argument is allowed; "%s" supplied' % argument) + else: + return None + + +def unchanged_required(argument): + """ + Return the argument text, unchanged. + (Directive option conversion function.) + + Raise ``ValueError`` if no argument is found. + """ + if argument is None: + raise ValueError('argument required but none supplied') + else: + return argument # unchanged! + + +def unchanged(argument): + """ + Return the argument text, unchanged. + (Directive option conversion function.) + + No argument implies empty string (""). + """ + if argument is None: + return '' + else: + return argument # unchanged! + + +def path(argument): + """ + Return the path argument unwrapped (with newlines removed). + (Directive option conversion function.) + + Raise ``ValueError`` if no argument is found. + """ + if argument is None: + raise ValueError('argument required but none supplied') + else: + return ''.join(s.strip() for s in argument.splitlines()) + + +def uri(argument): + """ + Return the URI argument with unescaped whitespace removed. + (Directive option conversion function.) + + Raise ``ValueError`` if no argument is found. + """ + if argument is None: + raise ValueError('argument required but none supplied') + else: + parts = split_escaped_whitespace(escape2null(argument)) + return ' '.join(''.join(nodes.unescape(part).split()) + for part in parts) + + +def nonnegative_int(argument): + """ + Check for a nonnegative integer argument; raise ``ValueError`` if not. + (Directive option conversion function.) + """ + value = int(argument) + if value < 0: + raise ValueError('negative value; must be positive or zero') + return value + + +def percentage(argument): + """ + Check for an integer percentage value with optional percent sign. + (Directive option conversion function.) + """ + try: + argument = argument.rstrip(' %') + except AttributeError: + pass + return nonnegative_int(argument) + + +length_units = ['em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc'] + + +def get_measure(argument, units): + """ + Check for a positive argument of one of the units and return a + normalized string of the form "<value><unit>" (without space in + between). + (Directive option conversion function.) + + To be called from directive option conversion functions. + """ + match = re.match(r'^([0-9.]+) *(%s)$' % '|'.join(units), argument) + try: + float(match.group(1)) + except (AttributeError, ValueError): + raise ValueError( + 'not a positive measure of one of the following units:\n%s' + % ' '.join('"%s"' % i for i in units)) + return match.group(1) + match.group(2) + + +def length_or_unitless(argument): + return get_measure(argument, length_units + ['']) + + +def length_or_percentage_or_unitless(argument, default=''): + """ + Return normalized string of a length or percentage unit. + (Directive option conversion function.) + + Add <default> if there is no unit. Raise ValueError if the argument is not + a positive measure of one of the valid CSS units (or without unit). + + >>> length_or_percentage_or_unitless('3 pt') + '3pt' + >>> length_or_percentage_or_unitless('3%', 'em') + '3%' + >>> length_or_percentage_or_unitless('3') + '3' + >>> length_or_percentage_or_unitless('3', 'px') + '3px' + """ + try: + return get_measure(argument, length_units + ['%']) + except ValueError: + try: + return get_measure(argument, ['']) + default + except ValueError: + # raise ValueError with list of valid units: + return get_measure(argument, length_units + ['%']) + + +def class_option(argument): + """ + Convert the argument into a list of ID-compatible strings and return it. + (Directive option conversion function.) + + Raise ``ValueError`` if no argument is found. + """ + if argument is None: + raise ValueError('argument required but none supplied') + names = argument.split() + class_names = [] + for name in names: + class_name = nodes.make_id(name) + if not class_name: + raise ValueError('cannot make "%s" into a class name' % name) + class_names.append(class_name) + return class_names + + +unicode_pattern = re.compile( + r'(?:0x|x|\\x|U\+?|\\u)([0-9a-f]+)$|&#x([0-9a-f]+);$', re.IGNORECASE) + + +def unicode_code(code): + r""" + Convert a Unicode character code to a Unicode character. + (Directive option conversion function.) + + Codes may be decimal numbers, hexadecimal numbers (prefixed by ``0x``, + ``x``, ``\x``, ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style + numeric character entities (e.g. ``☮``). Other text remains as-is. + + Raise ValueError for illegal Unicode code values. + """ + try: + if code.isdigit(): # decimal number + return chr(int(code)) + else: + match = unicode_pattern.match(code) + if match: # hex number + value = match.group(1) or match.group(2) + return chr(int(value, 16)) + else: # other text + return code + except OverflowError as detail: + raise ValueError('code too large (%s)' % detail) + + +def single_char_or_unicode(argument): + """ + A single character is returned as-is. Unicode character codes are + converted as in `unicode_code`. (Directive option conversion function.) + """ + char = unicode_code(argument) + if len(char) > 1: + raise ValueError('%r invalid; must be a single character or ' + 'a Unicode code' % char) + return char + + +def single_char_or_whitespace_or_unicode(argument): + """ + As with `single_char_or_unicode`, but "tab" and "space" are also supported. + (Directive option conversion function.) + """ + if argument == 'tab': + char = '\t' + elif argument == 'space': + char = ' ' + else: + char = single_char_or_unicode(argument) + return char + + +def positive_int(argument): + """ + Converts the argument into an integer. Raises ValueError for negative, + zero, or non-integer values. (Directive option conversion function.) + """ + value = int(argument) + if value < 1: + raise ValueError('negative or zero value; must be positive') + return value + + +def positive_int_list(argument): + """ + Converts a space- or comma-separated list of values into a Python list + of integers. + (Directive option conversion function.) + + Raises ValueError for non-positive-integer values. + """ + if ',' in argument: + entries = argument.split(',') + else: + entries = argument.split() + return [positive_int(entry) for entry in entries] + + +def encoding(argument): + """ + Verifies the encoding argument by lookup. + (Directive option conversion function.) + + Raises ValueError for unknown encodings. + """ + try: + codecs.lookup(argument) + except LookupError: + raise ValueError('unknown encoding: "%s"' % argument) + return argument + + +def choice(argument, values): + """ + Directive option utility function, supplied to enable options whose + argument must be a member of a finite set of possible values (must be + lower case). A custom conversion function must be written to use it. For + example:: + + from docutils.parsers.rst import directives + + def yesno(argument): + return directives.choice(argument, ('yes', 'no')) + + Raise ``ValueError`` if no argument is found or if the argument's value is + not valid (not an entry in the supplied list). + """ + try: + value = argument.lower().strip() + except AttributeError: + raise ValueError('must supply an argument; choose from %s' + % format_values(values)) + if value in values: + return value + else: + raise ValueError('"%s" unknown; choose from %s' + % (argument, format_values(values))) + + +def format_values(values): + return '%s, or "%s"' % (', '.join('"%s"' % s for s in values[:-1]), + values[-1]) + + +def value_or(values, other): + """ + Directive option conversion function. + + The argument can be any of `values` or `argument_type`. + """ + def auto_or_other(argument): + if argument in values: + return argument + else: + return other(argument) + return auto_or_other + + +def parser_name(argument): + """ + Return a docutils parser whose name matches the argument. + (Directive option conversion function.) + + Return `None`, if the argument evaluates to `False`. + Raise `ValueError` if importing the parser module fails. + """ + if not argument: + return None + try: + return parsers.get_parser_class(argument) + except ImportError as err: + raise ValueError(str(err)) diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py new file mode 100644 index 00000000..1990099e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/admonitions.py @@ -0,0 +1,101 @@ +# $Id: admonitions.py 9475 2023-11-13 22:30:00Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Admonition directives. +""" + +__docformat__ = 'reStructuredText' + + +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives +from docutils.parsers.rst.roles import set_classes +from docutils import nodes + + +class BaseAdmonition(Directive): + + final_argument_whitespace = True + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + has_content = True + + node_class = None + """Subclasses must set this to the appropriate admonition node class.""" + + def run(self): + set_classes(self.options) + self.assert_has_content() + text = '\n'.join(self.content) + admonition_node = self.node_class(text, **self.options) + self.add_name(admonition_node) + admonition_node.source, admonition_node.line = \ + self.state_machine.get_source_and_line(self.lineno) + if self.node_class is nodes.admonition: + title_text = self.arguments[0] + textnodes, messages = self.state.inline_text(title_text, + self.lineno) + title = nodes.title(title_text, '', *textnodes) + title.source, title.line = ( + self.state_machine.get_source_and_line(self.lineno)) + admonition_node += title + admonition_node += messages + if 'classes' not in self.options: + admonition_node['classes'] += ['admonition-' + + nodes.make_id(title_text)] + self.state.nested_parse(self.content, self.content_offset, + admonition_node) + return [admonition_node] + + +class Admonition(BaseAdmonition): + + required_arguments = 1 + node_class = nodes.admonition + + +class Attention(BaseAdmonition): + + node_class = nodes.attention + + +class Caution(BaseAdmonition): + + node_class = nodes.caution + + +class Danger(BaseAdmonition): + + node_class = nodes.danger + + +class Error(BaseAdmonition): + + node_class = nodes.error + + +class Hint(BaseAdmonition): + + node_class = nodes.hint + + +class Important(BaseAdmonition): + + node_class = nodes.important + + +class Note(BaseAdmonition): + + node_class = nodes.note + + +class Tip(BaseAdmonition): + + node_class = nodes.tip + + +class Warning(BaseAdmonition): + + node_class = nodes.warning diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py new file mode 100644 index 00000000..5cb90416 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/body.py @@ -0,0 +1,305 @@ +# $Id: body.py 9500 2023-12-14 22:38:49Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Directives for additional body elements. + +See `docutils.parsers.rst.directives` for API details. +""" + +__docformat__ = 'reStructuredText' + + +from docutils import nodes +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives +from docutils.parsers.rst.roles import set_classes +from docutils.utils.code_analyzer import Lexer, LexerError, NumberLines + + +class BasePseudoSection(Directive): + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + has_content = True + + node_class = None + """Node class to be used (must be set in subclasses).""" + + def run(self): + if not (self.state_machine.match_titles + or isinstance(self.state_machine.node, nodes.sidebar)): + raise self.error('The "%s" directive may not be used within ' + 'topics or body elements.' % self.name) + self.assert_has_content() + if self.arguments: # title (in sidebars optional) + title_text = self.arguments[0] + textnodes, messages = self.state.inline_text( + title_text, self.lineno) + titles = [nodes.title(title_text, '', *textnodes)] + # Sidebar uses this code. + if 'subtitle' in self.options: + textnodes, more_messages = self.state.inline_text( + self.options['subtitle'], self.lineno) + titles.append(nodes.subtitle(self.options['subtitle'], '', + *textnodes)) + messages.extend(more_messages) + else: + titles = [] + messages = [] + text = '\n'.join(self.content) + node = self.node_class(text, *(titles + messages)) + node['classes'] += self.options.get('class', []) + self.add_name(node) + if text: + self.state.nested_parse(self.content, self.content_offset, node) + return [node] + + +class Topic(BasePseudoSection): + + node_class = nodes.topic + + +class Sidebar(BasePseudoSection): + + node_class = nodes.sidebar + + required_arguments = 0 + optional_arguments = 1 + option_spec = BasePseudoSection.option_spec.copy() + option_spec['subtitle'] = directives.unchanged_required + + def run(self): + if isinstance(self.state_machine.node, nodes.sidebar): + raise self.error('The "%s" directive may not be used within a ' + 'sidebar element.' % self.name) + if 'subtitle' in self.options and not self.arguments: + raise self.error('The "subtitle" option may not be used ' + 'without a title.') + + return BasePseudoSection.run(self) + + +class LineBlock(Directive): + + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + has_content = True + + def run(self): + self.assert_has_content() + block = nodes.line_block(classes=self.options.get('class', [])) + self.add_name(block) + node_list = [block] + for line_text in self.content: + text_nodes, messages = self.state.inline_text( + line_text.strip(), self.lineno + self.content_offset) + line = nodes.line(line_text, '', *text_nodes) + if line_text.strip(): + line.indent = len(line_text) - len(line_text.lstrip()) + block += line + node_list.extend(messages) + self.content_offset += 1 + self.state.nest_line_block_lines(block) + return node_list + + +class ParsedLiteral(Directive): + + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + has_content = True + + def run(self): + set_classes(self.options) + self.assert_has_content() + text = '\n'.join(self.content) + text_nodes, messages = self.state.inline_text(text, self.lineno) + node = nodes.literal_block(text, '', *text_nodes, **self.options) + node.line = self.content_offset + 1 + self.add_name(node) + return [node] + messages + + +class CodeBlock(Directive): + """Parse and mark up content of a code block. + + Configuration setting: syntax_highlight + Highlight Code content with Pygments? + Possible values: ('long', 'short', 'none') + + """ + optional_arguments = 1 + option_spec = {'class': directives.class_option, + 'name': directives.unchanged, + 'number-lines': directives.unchanged # integer or None + } + has_content = True + + def run(self): + self.assert_has_content() + if self.arguments: + language = self.arguments[0] + else: + language = '' + set_classes(self.options) + classes = ['code'] + if language: + classes.append(language) + if 'classes' in self.options: + classes.extend(self.options['classes']) + + # set up lexical analyzer + try: + tokens = Lexer('\n'.join(self.content), language, + self.state.document.settings.syntax_highlight) + except LexerError as error: + if self.state.document.settings.report_level > 2: + # don't report warnings -> insert without syntax highlight + tokens = Lexer('\n'.join(self.content), language, 'none') + else: + raise self.warning(error) + + if 'number-lines' in self.options: + # optional argument `startline`, defaults to 1 + try: + startline = int(self.options['number-lines'] or 1) + except ValueError: + raise self.error(':number-lines: with non-integer start value') + endline = startline + len(self.content) + # add linenumber filter: + tokens = NumberLines(tokens, startline, endline) + + node = nodes.literal_block('\n'.join(self.content), classes=classes) + self.add_name(node) + # if called from "include", set the source + if 'source' in self.options: + node.attributes['source'] = self.options['source'] + # analyze content and add nodes for every token + for classes, value in tokens: + if classes: + node += nodes.inline(value, value, classes=classes) + else: + # insert as Text to decrease the verbosity of the output + node += nodes.Text(value) + + return [node] + + +class MathBlock(Directive): + + option_spec = {'class': directives.class_option, + 'name': directives.unchanged, + # TODO: Add Sphinx' ``mathbase.py`` option 'nowrap'? + # 'nowrap': directives.flag, + } + has_content = True + + def run(self): + set_classes(self.options) + self.assert_has_content() + # join lines, separate blocks + content = '\n'.join(self.content).split('\n\n') + _nodes = [] + for block in content: + if not block: + continue + node = nodes.math_block(self.block_text, block, **self.options) + (node.source, + node.line) = self.state_machine.get_source_and_line(self.lineno) + self.add_name(node) + _nodes.append(node) + return _nodes + + +class Rubric(Directive): + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + + def run(self): + set_classes(self.options) + rubric_text = self.arguments[0] + textnodes, messages = self.state.inline_text(rubric_text, self.lineno) + rubric = nodes.rubric(rubric_text, '', *textnodes, **self.options) + self.add_name(rubric) + return [rubric] + messages + + +class BlockQuote(Directive): + + has_content = True + classes = [] + + def run(self): + self.assert_has_content() + elements = self.state.block_quote(self.content, self.content_offset) + for element in elements: + if isinstance(element, nodes.block_quote): + element['classes'] += self.classes + return elements + + +class Epigraph(BlockQuote): + + classes = ['epigraph'] + + +class Highlights(BlockQuote): + + classes = ['highlights'] + + +class PullQuote(BlockQuote): + + classes = ['pull-quote'] + + +class Compound(Directive): + + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + has_content = True + + def run(self): + self.assert_has_content() + text = '\n'.join(self.content) + node = nodes.compound(text) + node['classes'] += self.options.get('class', []) + self.add_name(node) + self.state.nested_parse(self.content, self.content_offset, node) + return [node] + + +class Container(Directive): + + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {'name': directives.unchanged} + has_content = True + + def run(self): + self.assert_has_content() + text = '\n'.join(self.content) + try: + if self.arguments: + classes = directives.class_option(self.arguments[0]) + else: + classes = [] + except ValueError: + raise self.error( + 'Invalid class attribute value for "%s" directive: "%s".' + % (self.name, self.arguments[0])) + node = nodes.container(text) + node['classes'].extend(classes) + self.add_name(node) + self.state.nested_parse(self.content, self.content_offset, node) + return [node] diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py new file mode 100644 index 00000000..c22a26f2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/html.py @@ -0,0 +1,21 @@ +# $Id: html.py 9062 2022-05-30 21:09:09Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Dummy module for backwards compatibility. + +This module is provisional: it will be removed in Docutils 2.0. +""" + +__docformat__ = 'reStructuredText' + +import warnings + +from docutils.parsers.rst.directives.misc import MetaBody, Meta # noqa: F401 + +warnings.warn('The `docutils.parsers.rst.directive.html` module' + ' will be removed in Docutils 2.0.' + ' Since Docutils 0.18, the "Meta" node is defined in' + ' `docutils.parsers.rst.directives.misc`.', + DeprecationWarning, stacklevel=2) diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py new file mode 100644 index 00000000..bcde3a39 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/images.py @@ -0,0 +1,173 @@ +# $Id: images.py 9500 2023-12-14 22:38:49Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Directives for figures and simple images. +""" + +__docformat__ = 'reStructuredText' + +from urllib.request import url2pathname + +try: # check for the Python Imaging Library + import PIL.Image +except ImportError: + try: # sometimes PIL modules are put in PYTHONPATH's root + import Image + class PIL: pass # noqa:E701 dummy wrapper + PIL.Image = Image + except ImportError: + PIL = None + +from docutils import nodes +from docutils.nodes import fully_normalize_name, whitespace_normalize_name +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives, states +from docutils.parsers.rst.roles import set_classes + + +class Image(Directive): + + align_h_values = ('left', 'center', 'right') + align_v_values = ('top', 'middle', 'bottom') + align_values = align_v_values + align_h_values + loading_values = ('embed', 'link', 'lazy') + + def align(argument): + # This is not callable as `self.align()`. We cannot make it a + # staticmethod because we're saving an unbound method in + # option_spec below. + return directives.choice(argument, Image.align_values) + + def loading(argument): + # This is not callable as `self.loading()` (see above). + return directives.choice(argument, Image.loading_values) + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'alt': directives.unchanged, + 'height': directives.length_or_unitless, + 'width': directives.length_or_percentage_or_unitless, + 'scale': directives.percentage, + 'align': align, + 'target': directives.unchanged_required, + 'loading': loading, + 'class': directives.class_option, + 'name': directives.unchanged} + + def run(self): + if 'align' in self.options: + if isinstance(self.state, states.SubstitutionDef): + # Check for align_v_values. + if self.options['align'] not in self.align_v_values: + raise self.error( + 'Error in "%s" directive: "%s" is not a valid value ' + 'for the "align" option within a substitution ' + 'definition. Valid values for "align" are: "%s".' + % (self.name, self.options['align'], + '", "'.join(self.align_v_values))) + elif self.options['align'] not in self.align_h_values: + raise self.error( + 'Error in "%s" directive: "%s" is not a valid value for ' + 'the "align" option. Valid values for "align" are: "%s".' + % (self.name, self.options['align'], + '", "'.join(self.align_h_values))) + messages = [] + reference = directives.uri(self.arguments[0]) + self.options['uri'] = reference + reference_node = None + if 'target' in self.options: + block = states.escape2null( + self.options['target']).splitlines() + block = [line for line in block] + target_type, data = self.state.parse_target( + block, self.block_text, self.lineno) + if target_type == 'refuri': + reference_node = nodes.reference(refuri=data) + elif target_type == 'refname': + reference_node = nodes.reference( + refname=fully_normalize_name(data), + name=whitespace_normalize_name(data)) + reference_node.indirect_reference_name = data + self.state.document.note_refname(reference_node) + else: # malformed target + messages.append(data) # data is a system message + del self.options['target'] + set_classes(self.options) + image_node = nodes.image(self.block_text, **self.options) + (image_node.source, + image_node.line) = self.state_machine.get_source_and_line(self.lineno) + self.add_name(image_node) + if reference_node: + reference_node += image_node + return messages + [reference_node] + else: + return messages + [image_node] + + +class Figure(Image): + + def align(argument): + return directives.choice(argument, Figure.align_h_values) + + def figwidth_value(argument): + if argument.lower() == 'image': + return 'image' + else: + return directives.length_or_percentage_or_unitless(argument, 'px') + + option_spec = Image.option_spec.copy() + option_spec['figwidth'] = figwidth_value + option_spec['figclass'] = directives.class_option + option_spec['align'] = align + has_content = True + + def run(self): + figwidth = self.options.pop('figwidth', None) + figclasses = self.options.pop('figclass', None) + align = self.options.pop('align', None) + (image_node,) = Image.run(self) + if isinstance(image_node, nodes.system_message): + return [image_node] + figure_node = nodes.figure('', image_node) + (figure_node.source, figure_node.line + ) = self.state_machine.get_source_and_line(self.lineno) + if figwidth == 'image': + if PIL and self.state.document.settings.file_insertion_enabled: + imagepath = url2pathname(image_node['uri']) + try: + with PIL.Image.open(imagepath) as img: + figure_node['width'] = '%dpx' % img.size[0] + except (OSError, UnicodeEncodeError): + pass # TODO: warn/info? + else: + self.state.document.settings.record_dependencies.add( + imagepath.replace('\\', '/')) + elif figwidth is not None: + figure_node['width'] = figwidth + if figclasses: + figure_node['classes'] += figclasses + if align: + figure_node['align'] = align + if self.content: + node = nodes.Element() # anonymous container for parsing + self.state.nested_parse(self.content, self.content_offset, node) + first_node = node[0] + if isinstance(first_node, nodes.paragraph): + caption = nodes.caption(first_node.rawsource, '', + *first_node.children) + caption.source = first_node.source + caption.line = first_node.line + figure_node += caption + elif not (isinstance(first_node, nodes.comment) + and len(first_node) == 0): + error = self.reporter.error( + 'Figure caption must be a paragraph or empty comment.', + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [figure_node, error] + if len(node) > 1: + figure_node += nodes.legend('', *node[1:]) + return [figure_node] diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py new file mode 100644 index 00000000..c16e9430 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/misc.py @@ -0,0 +1,642 @@ +# $Id: misc.py 9492 2023-11-29 16:58:13Z milde $ +# Authors: David Goodger <goodger@python.org>; Dethe Elza +# Copyright: This module has been placed in the public domain. + +"""Miscellaneous directives.""" + +__docformat__ = 'reStructuredText' + +from pathlib import Path +import re +import time +from urllib.request import urlopen +from urllib.error import URLError + +from docutils import io, nodes, statemachine, utils +from docutils.parsers.rst import Directive, convert_directive_function +from docutils.parsers.rst import directives, roles, states +from docutils.parsers.rst.directives.body import CodeBlock, NumberLines +from docutils.transforms import misc + + +def adapt_path(path, source='', root_prefix='/'): + # Adapt path to files to include or embed. + # `root_prefix` is prepended to absolute paths (cf. root_prefix setting), + # `source` is the `current_source` of the including directive (which may + # be a file included by the main document). + if path.startswith('/'): + base = Path(root_prefix) + path = path[1:] + else: + base = Path(source).parent + # pepend "base" and convert to relative path for shorter system messages + return utils.relative_path(None, base/path) + + +class Include(Directive): + + """ + Include content read from a separate source file. + + Content may be parsed by the parser, or included as a literal + block. The encoding of the included file can be specified. Only + a part of the given file argument may be included by specifying + start and end line or text to match before and/or after the text + to be used. + + https://docutils.sourceforge.io/docs/ref/rst/directives.html#including-an-external-document-fragment + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'literal': directives.flag, + 'code': directives.unchanged, + 'encoding': directives.encoding, + 'parser': directives.parser_name, + 'tab-width': int, + 'start-line': int, + 'end-line': int, + 'start-after': directives.unchanged_required, + 'end-before': directives.unchanged_required, + # ignored except for 'literal' or 'code': + 'number-lines': directives.unchanged, # integer or None + 'class': directives.class_option, + 'name': directives.unchanged} + + standard_include_path = Path(states.__file__).parent / 'include' + + def run(self): + """Include a file as part of the content of this reST file. + + Depending on the options, the file (or a clipping) is + converted to nodes and returned or inserted into the input stream. + """ + settings = self.state.document.settings + if not settings.file_insertion_enabled: + raise self.warning('"%s" directive disabled.' % self.name) + tab_width = self.options.get('tab-width', settings.tab_width) + current_source = self.state.document.current_source + path = directives.path(self.arguments[0]) + if path.startswith('<') and path.endswith('>'): + path = '/' + path[1:-1] + root_prefix = self.standard_include_path + else: + root_prefix = settings.root_prefix + path = adapt_path(path, current_source, root_prefix) + encoding = self.options.get('encoding', settings.input_encoding) + error_handler = settings.input_encoding_error_handler + try: + include_file = io.FileInput(source_path=path, + encoding=encoding, + error_handler=error_handler) + except UnicodeEncodeError: + raise self.severe(f'Problems with "{self.name}" directive path:\n' + f'Cannot encode input file path "{path}" ' + '(wrong locale?).') + except OSError as error: + raise self.severe(f'Problems with "{self.name}" directive ' + f'path:\n{io.error_string(error)}.') + else: + settings.record_dependencies.add(path) + + # Get to-be-included content + startline = self.options.get('start-line', None) + endline = self.options.get('end-line', None) + try: + if startline or (endline is not None): + lines = include_file.readlines() + rawtext = ''.join(lines[startline:endline]) + else: + rawtext = include_file.read() + except UnicodeError as error: + raise self.severe(f'Problem with "{self.name}" directive:\n' + + io.error_string(error)) + # start-after/end-before: no restrictions on newlines in match-text, + # and no restrictions on matching inside lines vs. line boundaries + after_text = self.options.get('start-after', None) + if after_text: + # skip content in rawtext before *and incl.* a matching text + after_index = rawtext.find(after_text) + if after_index < 0: + raise self.severe('Problem with "start-after" option of "%s" ' + 'directive:\nText not found.' % self.name) + rawtext = rawtext[after_index + len(after_text):] + before_text = self.options.get('end-before', None) + if before_text: + # skip content in rawtext after *and incl.* a matching text + before_index = rawtext.find(before_text) + if before_index < 0: + raise self.severe('Problem with "end-before" option of "%s" ' + 'directive:\nText not found.' % self.name) + rawtext = rawtext[:before_index] + + include_lines = statemachine.string2lines(rawtext, tab_width, + convert_whitespace=True) + for i, line in enumerate(include_lines): + if len(line) > settings.line_length_limit: + raise self.warning('"%s": line %d exceeds the' + ' line-length-limit.' % (path, i+1)) + + if 'literal' in self.options: + # Don't convert tabs to spaces, if `tab_width` is negative. + if tab_width >= 0: + text = rawtext.expandtabs(tab_width) + else: + text = rawtext + literal_block = nodes.literal_block( + rawtext, source=path, + classes=self.options.get('class', [])) + literal_block.line = 1 + self.add_name(literal_block) + if 'number-lines' in self.options: + try: + startline = int(self.options['number-lines'] or 1) + except ValueError: + raise self.error(':number-lines: with non-integer ' + 'start value') + endline = startline + len(include_lines) + if text.endswith('\n'): + text = text[:-1] + tokens = NumberLines([([], text)], startline, endline) + for classes, value in tokens: + if classes: + literal_block += nodes.inline(value, value, + classes=classes) + else: + literal_block += nodes.Text(value) + else: + literal_block += nodes.Text(text) + return [literal_block] + + if 'code' in self.options: + self.options['source'] = path + # Don't convert tabs to spaces, if `tab_width` is negative: + if tab_width < 0: + include_lines = rawtext.splitlines() + codeblock = CodeBlock(self.name, + [self.options.pop('code')], # arguments + self.options, + include_lines, # content + self.lineno, + self.content_offset, + self.block_text, + self.state, + self.state_machine) + return codeblock.run() + + # Prevent circular inclusion: + clip_options = (startline, endline, before_text, after_text) + include_log = self.state.document.include_log + # log entries are tuples (<source>, <clip-options>) + if not include_log: # new document, initialize with document source + include_log.append((utils.relative_path(None, current_source), + (None, None, None, None))) + if (path, clip_options) in include_log: + master_paths = (pth for (pth, opt) in reversed(include_log)) + inclusion_chain = '\n> '.join((path, *master_paths)) + raise self.warning('circular inclusion in "%s" directive:\n%s' + % (self.name, inclusion_chain)) + + if 'parser' in self.options: + # parse into a dummy document and return created nodes + document = utils.new_document(path, settings) + document.include_log = include_log + [(path, clip_options)] + parser = self.options['parser']() + parser.parse('\n'.join(include_lines), document) + # clean up doctree and complete parsing + document.transformer.populate_from_components((parser,)) + document.transformer.apply_transforms() + return document.children + + # Include as rST source: + # + # mark end (cf. parsers.rst.states.Body.comment()) + include_lines += ['', '.. end of inclusion from "%s"' % path] + self.state_machine.insert_input(include_lines, path) + # update include-log + include_log.append((path, clip_options)) + return [] + + +class Raw(Directive): + + """ + Pass through content unchanged + + Content is included in output based on type argument + + Content may be included inline (content section of directive) or + imported from a file or url. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'file': directives.path, + 'url': directives.uri, + 'encoding': directives.encoding, + 'class': directives.class_option} + has_content = True + + def run(self): + settings = self.state.document.settings + if (not settings.raw_enabled + or (not settings.file_insertion_enabled + and ('file' in self.options or 'url' in self.options))): + raise self.warning('"%s" directive disabled.' % self.name) + attributes = {'format': ' '.join(self.arguments[0].lower().split())} + encoding = self.options.get('encoding', settings.input_encoding) + error_handler = settings.input_encoding_error_handler + if self.content: + if 'file' in self.options or 'url' in self.options: + raise self.error( + '"%s" directive may not both specify an external file ' + 'and have content.' % self.name) + text = '\n'.join(self.content) + elif 'file' in self.options: + if 'url' in self.options: + raise self.error( + 'The "file" and "url" options may not be simultaneously ' + 'specified for the "%s" directive.' % self.name) + path = adapt_path(self.options['file'], + self.state.document.current_source, + settings.root_prefix) + try: + raw_file = io.FileInput(source_path=path, + encoding=encoding, + error_handler=error_handler) + except OSError as error: + raise self.severe(f'Problems with "{self.name}" directive ' + f'path:\n{io.error_string(error)}.') + else: + # TODO: currently, raw input files are recorded as + # dependencies even if not used for the chosen output format. + settings.record_dependencies.add(path) + try: + text = raw_file.read() + except UnicodeError as error: + raise self.severe(f'Problem with "{self.name}" directive:\n' + + io.error_string(error)) + attributes['source'] = path + elif 'url' in self.options: + source = self.options['url'] + try: + raw_text = urlopen(source).read() + except (URLError, OSError) as error: + raise self.severe(f'Problems with "{self.name}" directive URL ' + f'"{self.options["url"]}":\n' + f'{io.error_string(error)}.') + raw_file = io.StringInput(source=raw_text, source_path=source, + encoding=encoding, + error_handler=error_handler) + try: + text = raw_file.read() + except UnicodeError as error: + raise self.severe(f'Problem with "{self.name}" directive:\n' + + io.error_string(error)) + attributes['source'] = source + else: + # This will always fail because there is no content. + self.assert_has_content() + raw_node = nodes.raw('', text, classes=self.options.get('class', []), + **attributes) + (raw_node.source, + raw_node.line) = self.state_machine.get_source_and_line(self.lineno) + return [raw_node] + + +class Replace(Directive): + + has_content = True + + def run(self): + if not isinstance(self.state, states.SubstitutionDef): + raise self.error( + 'Invalid context: the "%s" directive can only be used within ' + 'a substitution definition.' % self.name) + self.assert_has_content() + text = '\n'.join(self.content) + element = nodes.Element(text) + self.state.nested_parse(self.content, self.content_offset, + element) + # element might contain [paragraph] + system_message(s) + node = None + messages = [] + for elem in element: + if not node and isinstance(elem, nodes.paragraph): + node = elem + elif isinstance(elem, nodes.system_message): + elem['backrefs'] = [] + messages.append(elem) + else: + return [ + self.reporter.error( + f'Error in "{self.name}" directive: may contain ' + 'a single paragraph only.', line=self.lineno)] + if node: + return messages + node.children + return messages + + +class Unicode(Directive): + + r""" + Convert Unicode character codes (numbers) to characters. Codes may be + decimal numbers, hexadecimal numbers (prefixed by ``0x``, ``x``, ``\x``, + ``U+``, ``u``, or ``\u``; e.g. ``U+262E``), or XML-style numeric character + entities (e.g. ``☮``). Text following ".." is a comment and is + ignored. Spaces are ignored, and any other text remains as-is. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {'trim': directives.flag, + 'ltrim': directives.flag, + 'rtrim': directives.flag} + + comment_pattern = re.compile(r'( |\n|^)\.\. ') + + def run(self): + if not isinstance(self.state, states.SubstitutionDef): + raise self.error( + 'Invalid context: the "%s" directive can only be used within ' + 'a substitution definition.' % self.name) + substitution_definition = self.state_machine.node + if 'trim' in self.options: + substitution_definition.attributes['ltrim'] = 1 + substitution_definition.attributes['rtrim'] = 1 + if 'ltrim' in self.options: + substitution_definition.attributes['ltrim'] = 1 + if 'rtrim' in self.options: + substitution_definition.attributes['rtrim'] = 1 + codes = self.comment_pattern.split(self.arguments[0])[0].split() + element = nodes.Element() + for code in codes: + try: + decoded = directives.unicode_code(code) + except ValueError as error: + raise self.error('Invalid character code: %s\n%s' + % (code, io.error_string(error))) + element += nodes.Text(decoded) + return element.children + + +class Class(Directive): + + """ + Set a "class" attribute on the directive content or the next element. + When applied to the next element, a "pending" element is inserted, and a + transform does the work later. + """ + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + has_content = True + + def run(self): + try: + class_value = directives.class_option(self.arguments[0]) + except ValueError: + raise self.error( + 'Invalid class attribute value for "%s" directive: "%s".' + % (self.name, self.arguments[0])) + node_list = [] + if self.content: + container = nodes.Element() + self.state.nested_parse(self.content, self.content_offset, + container) + for node in container: + node['classes'].extend(class_value) + node_list.extend(container.children) + else: + pending = nodes.pending( + misc.ClassAttribute, + {'class': class_value, 'directive': self.name}, + self.block_text) + self.state_machine.document.note_pending(pending) + node_list.append(pending) + return node_list + + +class Role(Directive): + + has_content = True + + argument_pattern = re.compile(r'(%s)\s*(\(\s*(%s)\s*\)\s*)?$' + % ((states.Inliner.simplename,) * 2)) + + def run(self): + """Dynamically create and register a custom interpreted text role.""" + if self.content_offset > self.lineno or not self.content: + raise self.error('"%s" directive requires arguments on the first ' + 'line.' % self.name) + args = self.content[0] + match = self.argument_pattern.match(args) + if not match: + raise self.error('"%s" directive arguments not valid role names: ' + '"%s".' % (self.name, args)) + new_role_name = match.group(1) + base_role_name = match.group(3) + messages = [] + if base_role_name: + base_role, messages = roles.role( + base_role_name, self.state_machine.language, self.lineno, + self.state.reporter) + if base_role is None: + error = self.state.reporter.error( + 'Unknown interpreted text role "%s".' % base_role_name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return messages + [error] + else: + base_role = roles.generic_custom_role + assert not hasattr(base_role, 'arguments'), ( + 'Supplemental directive arguments for "%s" directive not ' + 'supported (specified by "%r" role).' % (self.name, base_role)) + try: + converted_role = convert_directive_function(base_role) + (arguments, options, content, content_offset + ) = self.state.parse_directive_block( + self.content[1:], self.content_offset, + converted_role, option_presets={}) + except states.MarkupError as detail: + error = self.reporter.error( + 'Error in "%s" directive:\n%s.' % (self.name, detail), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return messages + [error] + if 'class' not in options: + try: + options['class'] = directives.class_option(new_role_name) + except ValueError as detail: + error = self.reporter.error( + 'Invalid argument for "%s" directive:\n%s.' + % (self.name, detail), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return messages + [error] + role = roles.CustomRole(new_role_name, base_role, options, content) + roles.register_local_role(new_role_name, role) + return messages + + +class DefaultRole(Directive): + + """Set the default interpreted text role.""" + + optional_arguments = 1 + final_argument_whitespace = False + + def run(self): + if not self.arguments: + if '' in roles._roles: + # restore the "default" default role + del roles._roles[''] + return [] + role_name = self.arguments[0] + role, messages = roles.role(role_name, self.state_machine.language, + self.lineno, self.state.reporter) + if role is None: + error = self.state.reporter.error( + 'Unknown interpreted text role "%s".' % role_name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return messages + [error] + roles._roles[''] = role + return messages + + +class Title(Directive): + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + + def run(self): + self.state_machine.document['title'] = self.arguments[0] + return [] + + +class MetaBody(states.SpecializedBody): + + def field_marker(self, match, context, next_state): + """Meta element.""" + node, blank_finish = self.parsemeta(match) + self.parent += node + return [], next_state, [] + + def parsemeta(self, match): + name = self.parse_field_marker(match) + name = nodes.unescape(utils.escape2null(name)) + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + node = nodes.meta() + node['content'] = nodes.unescape(utils.escape2null( + ' '.join(indented))) + if not indented: + line = self.state_machine.line + msg = self.reporter.info( + 'No content for meta tag "%s".' % name, + nodes.literal_block(line, line)) + return msg, blank_finish + tokens = name.split() + try: + attname, val = utils.extract_name_value(tokens[0])[0] + node[attname.lower()] = val + except utils.NameValueError: + node['name'] = tokens[0] + for token in tokens[1:]: + try: + attname, val = utils.extract_name_value(token)[0] + node[attname.lower()] = val + except utils.NameValueError as detail: + line = self.state_machine.line + msg = self.reporter.error( + 'Error parsing meta tag attribute "%s": %s.' + % (token, detail), nodes.literal_block(line, line)) + return msg, blank_finish + return node, blank_finish + + +class Meta(Directive): + + has_content = True + + SMkwargs = {'state_classes': (MetaBody,)} + + def run(self): + self.assert_has_content() + node = nodes.Element() + new_line_offset, blank_finish = self.state.nested_list_parse( + self.content, self.content_offset, node, + initial_state='MetaBody', blank_finish=True, + state_machine_kwargs=self.SMkwargs) + if (new_line_offset - self.content_offset) != len(self.content): + # incomplete parse of block? + error = self.reporter.error( + 'Invalid meta directive.', + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + node += error + # insert at begin of document + index = self.state.document.first_child_not_matching_class( + (nodes.Titular, nodes.meta)) or 0 + self.state.document[index:index] = node.children + return [] + + +class Date(Directive): + + has_content = True + + def run(self): + if not isinstance(self.state, states.SubstitutionDef): + raise self.error( + 'Invalid context: the "%s" directive can only be used within ' + 'a substitution definition.' % self.name) + format_str = '\n'.join(self.content) or '%Y-%m-%d' + # @@@ + # Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable? + # Pro: Docutils-generated documentation + # can easily be part of `reproducible software builds`__ + # + # __ https://reproducible-builds.org/ + # + # Con: Changes the specs, hard to predict behaviour, + # + # See also the discussion about \date \time \year in TeX + # http://tug.org/pipermail/tex-k/2016-May/002704.html + # source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + # if (source_date_epoch): + # text = time.strftime(format_str, + # time.gmtime(int(source_date_epoch))) + # else: + text = time.strftime(format_str) + return [nodes.Text(text)] + + +class TestDirective(Directive): + + """This directive is useful only for testing purposes.""" + + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {'option': directives.unchanged_required} + has_content = True + + def run(self): + if self.content: + text = '\n'.join(self.content) + info = self.reporter.info( + 'Directive processed. Type="%s", arguments=%r, options=%r, ' + 'content:' % (self.name, self.arguments, self.options), + nodes.literal_block(text, text), line=self.lineno) + else: + info = self.reporter.info( + 'Directive processed. Type="%s", arguments=%r, options=%r, ' + 'content: None' % (self.name, self.arguments, self.options), + line=self.lineno) + return [info] diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py new file mode 100644 index 00000000..adb01d03 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/parts.py @@ -0,0 +1,126 @@ +# $Id: parts.py 8993 2022-01-29 13:20:04Z milde $ +# Authors: David Goodger <goodger@python.org>; Dmitry Jemerov +# Copyright: This module has been placed in the public domain. + +""" +Directives for document parts. +""" + +__docformat__ = 'reStructuredText' + +from docutils import nodes, languages +from docutils.transforms import parts +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives + + +class Contents(Directive): + + """ + Table of contents. + + The table of contents is generated in two passes: initial parse and + transform. During the initial parse, a 'pending' element is generated + which acts as a placeholder, storing the TOC title and any options + internally. At a later stage in the processing, the 'pending' element is + replaced by a 'topic' element, a title and the table of contents proper. + """ + + backlinks_values = ('top', 'entry', 'none') + + def backlinks(arg): + value = directives.choice(arg, Contents.backlinks_values) + if value == 'none': + return None + else: + return value + + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {'depth': directives.nonnegative_int, + 'local': directives.flag, + 'backlinks': backlinks, + 'class': directives.class_option} + + def run(self): + if not (self.state_machine.match_titles + or isinstance(self.state_machine.node, nodes.sidebar)): + raise self.error('The "%s" directive may not be used within ' + 'topics or body elements.' % self.name) + document = self.state_machine.document + language = languages.get_language(document.settings.language_code, + document.reporter) + if self.arguments: + title_text = self.arguments[0] + text_nodes, messages = self.state.inline_text(title_text, + self.lineno) + title = nodes.title(title_text, '', *text_nodes) + else: + messages = [] + if 'local' in self.options: + title = None + else: + title = nodes.title('', language.labels['contents']) + topic = nodes.topic(classes=['contents']) + topic['classes'] += self.options.get('class', []) + # the latex2e writer needs source and line for a warning: + topic.source, topic.line = self.state_machine.get_source_and_line() + topic.line -= 1 + if 'local' in self.options: + topic['classes'].append('local') + if title: + name = title.astext() + topic += title + else: + name = language.labels['contents'] + name = nodes.fully_normalize_name(name) + if not document.has_name(name): + topic['names'].append(name) + document.note_implicit_target(topic) + pending = nodes.pending(parts.Contents, rawsource=self.block_text) + pending.details.update(self.options) + document.note_pending(pending) + topic += pending + return [topic] + messages + + +class Sectnum(Directive): + + """Automatic section numbering.""" + + option_spec = {'depth': int, + 'start': int, + 'prefix': directives.unchanged_required, + 'suffix': directives.unchanged_required} + + def run(self): + pending = nodes.pending(parts.SectNum) + pending.details.update(self.options) + self.state_machine.document.note_pending(pending) + return [pending] + + +class Header(Directive): + + """Contents of document header.""" + + has_content = True + + def run(self): + self.assert_has_content() + header = self.state_machine.document.get_decoration().get_header() + self.state.nested_parse(self.content, self.content_offset, header) + return [] + + +class Footer(Directive): + + """Contents of document footer.""" + + has_content = True + + def run(self): + self.assert_has_content() + footer = self.state_machine.document.get_decoration().get_footer() + self.state.nested_parse(self.content, self.content_offset, footer) + return [] diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py new file mode 100644 index 00000000..96921f9d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/references.py @@ -0,0 +1,29 @@ +# $Id: references.py 7062 2011-06-30 22:14:29Z milde $ +# Authors: David Goodger <goodger@python.org>; Dmitry Jemerov +# Copyright: This module has been placed in the public domain. + +""" +Directives for references and targets. +""" + +__docformat__ = 'reStructuredText' + +from docutils import nodes +from docutils.transforms import references +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives + + +class TargetNotes(Directive): + + """Target footnote generation.""" + + option_spec = {'class': directives.class_option, + 'name': directives.unchanged} + + def run(self): + pending = nodes.pending(references.TargetNotes) + self.add_name(pending) + pending.details.update(self.options) + self.state_machine.document.note_pending(pending) + return [pending] diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py new file mode 100644 index 00000000..2cc266ff --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/directives/tables.py @@ -0,0 +1,538 @@ +# $Id: tables.py 9492 2023-11-29 16:58:13Z milde $ +# Authors: David Goodger <goodger@python.org>; David Priest +# Copyright: This module has been placed in the public domain. + +""" +Directives for table elements. +""" + +__docformat__ = 'reStructuredText' + + +import csv +from urllib.request import urlopen +from urllib.error import URLError +import warnings + +from docutils import nodes, statemachine +from docutils.io import FileInput, StringInput +from docutils.parsers.rst import Directive +from docutils.parsers.rst import directives +from docutils.parsers.rst.directives.misc import adapt_path +from docutils.utils import SystemMessagePropagation + + +def align(argument): + return directives.choice(argument, ('left', 'center', 'right')) + + +class Table(Directive): + + """ + Generic table base class. + """ + + optional_arguments = 1 + final_argument_whitespace = True + option_spec = {'class': directives.class_option, + 'name': directives.unchanged, + 'align': align, + 'width': directives.length_or_percentage_or_unitless, + 'widths': directives.value_or(('auto', 'grid'), + directives.positive_int_list)} + has_content = True + + def make_title(self): + if self.arguments: + title_text = self.arguments[0] + text_nodes, messages = self.state.inline_text(title_text, + self.lineno) + title = nodes.title(title_text, '', *text_nodes) + (title.source, + title.line) = self.state_machine.get_source_and_line(self.lineno) + else: + title = None + messages = [] + return title, messages + + def check_table_dimensions(self, rows, header_rows, stub_columns): + if len(rows) < header_rows: + error = self.reporter.error('%s header row(s) specified but ' + 'only %s row(s) of data supplied ("%s" directive).' + % (header_rows, len(rows), self.name), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + if len(rows) == header_rows > 0: + error = self.reporter.error( + f'Insufficient data supplied ({len(rows)} row(s)); ' + 'no data remaining for table body, ' + f'required by "{self.name}" directive.', + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + for row in rows: + if len(row) < stub_columns: + error = self.reporter.error( + f'{stub_columns} stub column(s) specified ' + f'but only {len(row)} columns(s) of data supplied ' + f'("{self.name}" directive).', + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + if len(row) == stub_columns > 0: + error = self.reporter.error( + 'Insufficient data supplied (%s columns(s)); ' + 'no data remaining for table body, required ' + 'by "%s" directive.' % (len(row), self.name), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + + def set_table_width(self, table_node): + if 'width' in self.options: + table_node['width'] = self.options.get('width') + + @property + def widths(self): + return self.options.get('widths', '') + + def get_column_widths(self, n_cols): + if isinstance(self.widths, list): + if len(self.widths) != n_cols: + # TODO: use last value for missing columns? + error = self.reporter.error('"%s" widths do not match the ' + 'number of columns in table (%s).' % (self.name, n_cols), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + col_widths = self.widths + elif n_cols: + col_widths = [100 // n_cols] * n_cols + else: + error = self.reporter.error('No table data detected in CSV file.', + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + return col_widths + + def extend_short_rows_with_empty_cells(self, columns, parts): + for part in parts: + for row in part: + if len(row) < columns: + row.extend([(0, 0, 0, [])] * (columns - len(row))) + + +class RSTTable(Table): + """ + Class for the `"table" directive`__ for formal tables using rST syntax. + + __ https://docutils.sourceforge.io/docs/ref/rst/directives.html + """ + + def run(self): + if not self.content: + warning = self.reporter.warning('Content block expected ' + 'for the "%s" directive; none found.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [warning] + title, messages = self.make_title() + node = nodes.Element() # anonymous container for parsing + self.state.nested_parse(self.content, self.content_offset, node) + if len(node) != 1 or not isinstance(node[0], nodes.table): + error = self.reporter.error('Error parsing content block for the ' + '"%s" directive: exactly one table expected.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [error] + table_node = node[0] + table_node['classes'] += self.options.get('class', []) + self.set_table_width(table_node) + if 'align' in self.options: + table_node['align'] = self.options.get('align') + if isinstance(self.widths, list): + tgroup = table_node[0] + try: + col_widths = self.get_column_widths(tgroup["cols"]) + except SystemMessagePropagation as detail: + return [detail.args[0]] + colspecs = [child for child in tgroup.children + if child.tagname == 'colspec'] + for colspec, col_width in zip(colspecs, col_widths): + colspec['colwidth'] = col_width + if self.widths == 'auto': + table_node['classes'] += ['colwidths-auto'] + elif self.widths: # "grid" or list of integers + table_node['classes'] += ['colwidths-given'] + self.add_name(table_node) + if title: + table_node.insert(0, title) + return [table_node] + messages + + +class CSVTable(Table): + + option_spec = {'header-rows': directives.nonnegative_int, + 'stub-columns': directives.nonnegative_int, + 'header': directives.unchanged, + 'width': directives.length_or_percentage_or_unitless, + 'widths': directives.value_or(('auto', ), + directives.positive_int_list), + 'file': directives.path, + 'url': directives.uri, + 'encoding': directives.encoding, + 'class': directives.class_option, + 'name': directives.unchanged, + 'align': align, + # field delimiter char + 'delim': directives.single_char_or_whitespace_or_unicode, + # treat whitespace after delimiter as significant + 'keepspace': directives.flag, + # text field quote/unquote char: + 'quote': directives.single_char_or_unicode, + # char used to escape delim & quote as-needed: + 'escape': directives.single_char_or_unicode} + + class DocutilsDialect(csv.Dialect): + + """CSV dialect for `csv_table` directive.""" + + delimiter = ',' + quotechar = '"' + doublequote = True + skipinitialspace = True + strict = True + lineterminator = '\n' + quoting = csv.QUOTE_MINIMAL + + def __init__(self, options): + if 'delim' in options: + self.delimiter = options['delim'] + if 'keepspace' in options: + self.skipinitialspace = False + if 'quote' in options: + self.quotechar = options['quote'] + if 'escape' in options: + self.doublequote = False + self.escapechar = options['escape'] + super().__init__() + + class HeaderDialect(csv.Dialect): + """ + CSV dialect used for the "header" option data. + + Deprecated. Will be removed in Docutils 0.22. + """ + # The separate HeaderDialect was introduced in revision 2294 + # (2004-06-17) in the sandbox before the "csv-table" directive moved + # to the trunk in r2309. Discussion in docutils-devel around this time + # did not mention a rationale (part of the discussion was in private + # mail). + # This is in conflict with the documentation, which always said: + # "Must use the same CSV format as the main CSV data." + # and did not change in this aspect. + # + # Maybe it was intended to have similar escape rules for rST and CSV, + # however with the current implementation this means we need + # `\\` for rST markup and ``\\\\`` for a literal backslash + # in the "option" header but ``\`` and ``\\`` in the header-lines and + # table cells of the main CSV data. + delimiter = ',' + quotechar = '"' + escapechar = '\\' + doublequote = False + skipinitialspace = True + strict = True + lineterminator = '\n' + quoting = csv.QUOTE_MINIMAL + + def __init__(self): + warnings.warn('CSVTable.HeaderDialect will be removed ' + 'in Docutils 0.22.', + PendingDeprecationWarning, stacklevel=2) + super().__init__() + + @staticmethod + def check_requirements(): + warnings.warn('CSVTable.check_requirements()' + ' is not required with Python 3' + ' and will be removed in Docutils 0.22.', + DeprecationWarning, stacklevel=2) + + def process_header_option(self): + source = self.state_machine.get_source(self.lineno - 1) + table_head = [] + max_header_cols = 0 + if 'header' in self.options: # separate table header in option + rows, max_header_cols = self.parse_csv_data_into_rows( + self.options['header'].split('\n'), + self.DocutilsDialect(self.options), + source) + table_head.extend(rows) + return table_head, max_header_cols + + def run(self): + try: + if (not self.state.document.settings.file_insertion_enabled + and ('file' in self.options + or 'url' in self.options)): + warning = self.reporter.warning('File and URL access ' + 'deactivated; ignoring "%s" directive.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [warning] + title, messages = self.make_title() + csv_data, source = self.get_csv_data() + table_head, max_header_cols = self.process_header_option() + rows, max_cols = self.parse_csv_data_into_rows( + csv_data, self.DocutilsDialect(self.options), source) + max_cols = max(max_cols, max_header_cols) + header_rows = self.options.get('header-rows', 0) + stub_columns = self.options.get('stub-columns', 0) + self.check_table_dimensions(rows, header_rows, stub_columns) + table_head.extend(rows[:header_rows]) + table_body = rows[header_rows:] + col_widths = self.get_column_widths(max_cols) + self.extend_short_rows_with_empty_cells(max_cols, + (table_head, table_body)) + except SystemMessagePropagation as detail: + return [detail.args[0]] + except csv.Error as detail: + message = str(detail) + error = self.reporter.error('Error with CSV data' + ' in "%s" directive:\n%s' % (self.name, message), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [error] + table = (col_widths, table_head, table_body) + table_node = self.state.build_table(table, self.content_offset, + stub_columns, widths=self.widths) + table_node['classes'] += self.options.get('class', []) + if 'align' in self.options: + table_node['align'] = self.options.get('align') + self.set_table_width(table_node) + self.add_name(table_node) + if title: + table_node.insert(0, title) + return [table_node] + messages + + def get_csv_data(self): + """ + Get CSV data from the directive content, from an external + file, or from a URL reference. + """ + settings = self.state.document.settings + encoding = self.options.get('encoding', settings.input_encoding) + error_handler = settings.input_encoding_error_handler + if self.content: + # CSV data is from directive content. + if 'file' in self.options or 'url' in self.options: + error = self.reporter.error('"%s" directive may not both ' + 'specify an external file and have content.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + source = self.content.source(0) + csv_data = self.content + elif 'file' in self.options: + # CSV data is from an external file. + if 'url' in self.options: + error = self.reporter.error('The "file" and "url" options ' + 'may not be simultaneously specified ' + 'for the "%s" directive.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + source = adapt_path(self.options['file'], + self.state.document.current_source, + settings.root_prefix) + try: + csv_file = FileInput(source_path=source, + encoding=encoding, + error_handler=error_handler) + csv_data = csv_file.read().splitlines() + except OSError as error: + severe = self.reporter.severe( + 'Problems with "%s" directive path:\n%s.' + % (self.name, error), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(severe) + else: + settings.record_dependencies.add(source) + elif 'url' in self.options: + source = self.options['url'] + try: + with urlopen(source) as response: + csv_text = response.read() + except (URLError, OSError, ValueError) as error: + severe = self.reporter.severe( + 'Problems with "%s" directive URL "%s":\n%s.' + % (self.name, self.options['url'], error), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(severe) + csv_file = StringInput(source=csv_text, source_path=source, + encoding=encoding, + error_handler=error_handler) + csv_data = csv_file.read().splitlines() + else: + error = self.reporter.warning( + 'The "%s" directive requires content; none supplied.' + % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + return csv_data, source + + @staticmethod + def decode_from_csv(s): + warnings.warn('CSVTable.decode_from_csv()' + ' is not required with Python 3' + ' and will be removed in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + return s + + @staticmethod + def encode_for_csv(s): + warnings.warn('CSVTable.encode_from_csv()' + ' is not required with Python 3' + ' and will be removed in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + return s + + def parse_csv_data_into_rows(self, csv_data, dialect, source): + csv_reader = csv.reader((line + '\n' for line in csv_data), + dialect=dialect) + rows = [] + max_cols = 0 + for row in csv_reader: + row_data = [] + for cell in row: + cell_data = (0, 0, 0, statemachine.StringList( + cell.splitlines(), source=source)) + row_data.append(cell_data) + rows.append(row_data) + max_cols = max(max_cols, len(row)) + return rows, max_cols + + +class ListTable(Table): + + """ + Implement tables whose data is encoded as a uniform two-level bullet list. + For further ideas, see + https://docutils.sourceforge.io/docs/dev/rst/alternatives.html#list-driven-tables + """ + + option_spec = {'header-rows': directives.nonnegative_int, + 'stub-columns': directives.nonnegative_int, + 'width': directives.length_or_percentage_or_unitless, + 'widths': directives.value_or(('auto', ), + directives.positive_int_list), + 'class': directives.class_option, + 'name': directives.unchanged, + 'align': align} + + def run(self): + if not self.content: + error = self.reporter.error('The "%s" directive is empty; ' + 'content required.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + return [error] + title, messages = self.make_title() + node = nodes.Element() # anonymous container for parsing + self.state.nested_parse(self.content, self.content_offset, node) + try: + num_cols, col_widths = self.check_list_content(node) + table_data = [[item.children for item in row_list[0]] + for row_list in node[0]] + header_rows = self.options.get('header-rows', 0) + stub_columns = self.options.get('stub-columns', 0) + self.check_table_dimensions(table_data, header_rows, stub_columns) + except SystemMessagePropagation as detail: + return [detail.args[0]] + table_node = self.build_table_from_list(table_data, col_widths, + header_rows, stub_columns) + if 'align' in self.options: + table_node['align'] = self.options.get('align') + table_node['classes'] += self.options.get('class', []) + self.set_table_width(table_node) + self.add_name(table_node) + if title: + table_node.insert(0, title) + return [table_node] + messages + + def check_list_content(self, node): + if len(node) != 1 or not isinstance(node[0], nodes.bullet_list): + error = self.reporter.error( + 'Error parsing content block for the "%s" directive: ' + 'exactly one bullet list expected.' % self.name, + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + list_node = node[0] + num_cols = 0 + # Check for a uniform two-level bullet list: + for item_index in range(len(list_node)): + item = list_node[item_index] + if len(item) != 1 or not isinstance(item[0], nodes.bullet_list): + error = self.reporter.error( + 'Error parsing content block for the "%s" directive: ' + 'two-level bullet list expected, but row %s does not ' + 'contain a second-level bullet list.' + % (self.name, item_index + 1), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + elif item_index: + if len(item[0]) != num_cols: + error = self.reporter.error( + 'Error parsing content block for the "%s" directive: ' + 'uniform two-level bullet list expected, but row %s ' + 'does not contain the same number of items as row 1 ' + '(%s vs %s).' + % (self.name, item_index + 1, len(item[0]), num_cols), + nodes.literal_block(self.block_text, self.block_text), + line=self.lineno) + raise SystemMessagePropagation(error) + else: + num_cols = len(item[0]) + col_widths = self.get_column_widths(num_cols) + return num_cols, col_widths + + def build_table_from_list(self, table_data, + col_widths, header_rows, stub_columns): + table = nodes.table() + if self.widths == 'auto': + table['classes'] += ['colwidths-auto'] + elif self.widths: # explicitly set column widths + table['classes'] += ['colwidths-given'] + tgroup = nodes.tgroup(cols=len(col_widths)) + table += tgroup + for col_width in col_widths: + colspec = nodes.colspec() + if col_width is not None: + colspec.attributes['colwidth'] = col_width + if stub_columns: + colspec.attributes['stub'] = 1 + stub_columns -= 1 + tgroup += colspec + rows = [] + for row in table_data: + row_node = nodes.row() + for cell in row: + entry = nodes.entry() + entry += cell + row_node += entry + rows.append(row_node) + if header_rows: + thead = nodes.thead() + thead.extend(rows[:header_rows]) + tgroup += thead + tbody = nodes.tbody() + tbody.extend(rows[header_rows:]) + tgroup += tbody + return table diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/README.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/README.txt new file mode 100644 index 00000000..a51328fb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/README.txt @@ -0,0 +1,17 @@ +============================================ + ``docutils/parsers/rst/include`` Directory +============================================ + +This directory contains standard data files intended for inclusion in +reStructuredText documents. To access these files, use the "include" +directive with the special syntax for standard "include" data files, +angle brackets around the file name:: + + .. include:: <isonum.txt> + +See the documentation for the `"include" directive`__ and +`reStructuredText Standard Definition Files`__ for +details. + +__ https://docutils.sourceforge.io/docs/ref/rst/directives.html#include +__ https://docutils.sourceforge.io/docs/ref/rst/definitions.html diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsa.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsa.txt new file mode 100644 index 00000000..a13b1d66 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsa.txt @@ -0,0 +1,162 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |angzarr| unicode:: U+0237C .. RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW +.. |cirmid| unicode:: U+02AEF .. VERTICAL LINE WITH CIRCLE ABOVE +.. |cudarrl| unicode:: U+02938 .. RIGHT-SIDE ARC CLOCKWISE ARROW +.. |cudarrr| unicode:: U+02935 .. ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS +.. |cularr| unicode:: U+021B6 .. ANTICLOCKWISE TOP SEMICIRCLE ARROW +.. |cularrp| unicode:: U+0293D .. TOP ARC ANTICLOCKWISE ARROW WITH PLUS +.. |curarr| unicode:: U+021B7 .. CLOCKWISE TOP SEMICIRCLE ARROW +.. |curarrm| unicode:: U+0293C .. TOP ARC CLOCKWISE ARROW WITH MINUS +.. |Darr| unicode:: U+021A1 .. DOWNWARDS TWO HEADED ARROW +.. |dArr| unicode:: U+021D3 .. DOWNWARDS DOUBLE ARROW +.. |darr2| unicode:: U+021CA .. DOWNWARDS PAIRED ARROWS +.. |ddarr| unicode:: U+021CA .. DOWNWARDS PAIRED ARROWS +.. |DDotrahd| unicode:: U+02911 .. RIGHTWARDS ARROW WITH DOTTED STEM +.. |dfisht| unicode:: U+0297F .. DOWN FISH TAIL +.. |dHar| unicode:: U+02965 .. DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT +.. |dharl| unicode:: U+021C3 .. DOWNWARDS HARPOON WITH BARB LEFTWARDS +.. |dharr| unicode:: U+021C2 .. DOWNWARDS HARPOON WITH BARB RIGHTWARDS +.. |dlarr| unicode:: U+02199 .. SOUTH WEST ARROW +.. |drarr| unicode:: U+02198 .. SOUTH EAST ARROW +.. |duarr| unicode:: U+021F5 .. DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW +.. |duhar| unicode:: U+0296F .. DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT +.. |dzigrarr| unicode:: U+027FF .. LONG RIGHTWARDS SQUIGGLE ARROW +.. |erarr| unicode:: U+02971 .. EQUALS SIGN ABOVE RIGHTWARDS ARROW +.. |hArr| unicode:: U+021D4 .. LEFT RIGHT DOUBLE ARROW +.. |harr| unicode:: U+02194 .. LEFT RIGHT ARROW +.. |harrcir| unicode:: U+02948 .. LEFT RIGHT ARROW THROUGH SMALL CIRCLE +.. |harrw| unicode:: U+021AD .. LEFT RIGHT WAVE ARROW +.. |hoarr| unicode:: U+021FF .. LEFT RIGHT OPEN-HEADED ARROW +.. |imof| unicode:: U+022B7 .. IMAGE OF +.. |lAarr| unicode:: U+021DA .. LEFTWARDS TRIPLE ARROW +.. |Larr| unicode:: U+0219E .. LEFTWARDS TWO HEADED ARROW +.. |larr2| unicode:: U+021C7 .. LEFTWARDS PAIRED ARROWS +.. |larrbfs| unicode:: U+0291F .. LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND +.. |larrfs| unicode:: U+0291D .. LEFTWARDS ARROW TO BLACK DIAMOND +.. |larrhk| unicode:: U+021A9 .. LEFTWARDS ARROW WITH HOOK +.. |larrlp| unicode:: U+021AB .. LEFTWARDS ARROW WITH LOOP +.. |larrpl| unicode:: U+02939 .. LEFT-SIDE ARC ANTICLOCKWISE ARROW +.. |larrsim| unicode:: U+02973 .. LEFTWARDS ARROW ABOVE TILDE OPERATOR +.. |larrtl| unicode:: U+021A2 .. LEFTWARDS ARROW WITH TAIL +.. |lAtail| unicode:: U+0291B .. LEFTWARDS DOUBLE ARROW-TAIL +.. |latail| unicode:: U+02919 .. LEFTWARDS ARROW-TAIL +.. |lBarr| unicode:: U+0290E .. LEFTWARDS TRIPLE DASH ARROW +.. |lbarr| unicode:: U+0290C .. LEFTWARDS DOUBLE DASH ARROW +.. |ldca| unicode:: U+02936 .. ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS +.. |ldrdhar| unicode:: U+02967 .. LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN +.. |ldrushar| unicode:: U+0294B .. LEFT BARB DOWN RIGHT BARB UP HARPOON +.. |ldsh| unicode:: U+021B2 .. DOWNWARDS ARROW WITH TIP LEFTWARDS +.. |lfisht| unicode:: U+0297C .. LEFT FISH TAIL +.. |lHar| unicode:: U+02962 .. LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN +.. |lhard| unicode:: U+021BD .. LEFTWARDS HARPOON WITH BARB DOWNWARDS +.. |lharu| unicode:: U+021BC .. LEFTWARDS HARPOON WITH BARB UPWARDS +.. |lharul| unicode:: U+0296A .. LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH +.. |llarr| unicode:: U+021C7 .. LEFTWARDS PAIRED ARROWS +.. |llhard| unicode:: U+0296B .. LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH +.. |loarr| unicode:: U+021FD .. LEFTWARDS OPEN-HEADED ARROW +.. |lrarr| unicode:: U+021C6 .. LEFTWARDS ARROW OVER RIGHTWARDS ARROW +.. |lrarr2| unicode:: U+021C6 .. LEFTWARDS ARROW OVER RIGHTWARDS ARROW +.. |lrhar| unicode:: U+021CB .. LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON +.. |lrhar2| unicode:: U+021CB .. LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON +.. |lrhard| unicode:: U+0296D .. RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH +.. |lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS +.. |lurdshar| unicode:: U+0294A .. LEFT BARB UP RIGHT BARB DOWN HARPOON +.. |luruhar| unicode:: U+02966 .. LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP +.. |Map| unicode:: U+02905 .. RIGHTWARDS TWO-HEADED ARROW FROM BAR +.. |map| unicode:: U+021A6 .. RIGHTWARDS ARROW FROM BAR +.. |midcir| unicode:: U+02AF0 .. VERTICAL LINE WITH CIRCLE BELOW +.. |mumap| unicode:: U+022B8 .. MULTIMAP +.. |nearhk| unicode:: U+02924 .. NORTH EAST ARROW WITH HOOK +.. |neArr| unicode:: U+021D7 .. NORTH EAST DOUBLE ARROW +.. |nearr| unicode:: U+02197 .. NORTH EAST ARROW +.. |nesear| unicode:: U+02928 .. NORTH EAST ARROW AND SOUTH EAST ARROW +.. |nhArr| unicode:: U+021CE .. LEFT RIGHT DOUBLE ARROW WITH STROKE +.. |nharr| unicode:: U+021AE .. LEFT RIGHT ARROW WITH STROKE +.. |nlArr| unicode:: U+021CD .. LEFTWARDS DOUBLE ARROW WITH STROKE +.. |nlarr| unicode:: U+0219A .. LEFTWARDS ARROW WITH STROKE +.. |nrArr| unicode:: U+021CF .. RIGHTWARDS DOUBLE ARROW WITH STROKE +.. |nrarr| unicode:: U+0219B .. RIGHTWARDS ARROW WITH STROKE +.. |nrarrc| unicode:: U+02933 U+00338 .. WAVE ARROW POINTING DIRECTLY RIGHT with slash +.. |nrarrw| unicode:: U+0219D U+00338 .. RIGHTWARDS WAVE ARROW with slash +.. |nvHarr| unicode:: U+02904 .. LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE +.. |nvlArr| unicode:: U+02902 .. LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE +.. |nvrArr| unicode:: U+02903 .. RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE +.. |nwarhk| unicode:: U+02923 .. NORTH WEST ARROW WITH HOOK +.. |nwArr| unicode:: U+021D6 .. NORTH WEST DOUBLE ARROW +.. |nwarr| unicode:: U+02196 .. NORTH WEST ARROW +.. |nwnear| unicode:: U+02927 .. NORTH WEST ARROW AND NORTH EAST ARROW +.. |olarr| unicode:: U+021BA .. ANTICLOCKWISE OPEN CIRCLE ARROW +.. |orarr| unicode:: U+021BB .. CLOCKWISE OPEN CIRCLE ARROW +.. |origof| unicode:: U+022B6 .. ORIGINAL OF +.. |rAarr| unicode:: U+021DB .. RIGHTWARDS TRIPLE ARROW +.. |Rarr| unicode:: U+021A0 .. RIGHTWARDS TWO HEADED ARROW +.. |rarr2| unicode:: U+021C9 .. RIGHTWARDS PAIRED ARROWS +.. |rarrap| unicode:: U+02975 .. RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO +.. |rarrbfs| unicode:: U+02920 .. RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND +.. |rarrc| unicode:: U+02933 .. WAVE ARROW POINTING DIRECTLY RIGHT +.. |rarrfs| unicode:: U+0291E .. RIGHTWARDS ARROW TO BLACK DIAMOND +.. |rarrhk| unicode:: U+021AA .. RIGHTWARDS ARROW WITH HOOK +.. |rarrlp| unicode:: U+021AC .. RIGHTWARDS ARROW WITH LOOP +.. |rarrpl| unicode:: U+02945 .. RIGHTWARDS ARROW WITH PLUS BELOW +.. |rarrsim| unicode:: U+02974 .. RIGHTWARDS ARROW ABOVE TILDE OPERATOR +.. |Rarrtl| unicode:: U+02916 .. RIGHTWARDS TWO-HEADED ARROW WITH TAIL +.. |rarrtl| unicode:: U+021A3 .. RIGHTWARDS ARROW WITH TAIL +.. |rarrw| unicode:: U+0219D .. RIGHTWARDS WAVE ARROW +.. |rAtail| unicode:: U+0291C .. RIGHTWARDS DOUBLE ARROW-TAIL +.. |ratail| unicode:: U+0291A .. RIGHTWARDS ARROW-TAIL +.. |RBarr| unicode:: U+02910 .. RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW +.. |rBarr| unicode:: U+0290F .. RIGHTWARDS TRIPLE DASH ARROW +.. |rbarr| unicode:: U+0290D .. RIGHTWARDS DOUBLE DASH ARROW +.. |rdca| unicode:: U+02937 .. ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS +.. |rdldhar| unicode:: U+02969 .. RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN +.. |rdsh| unicode:: U+021B3 .. DOWNWARDS ARROW WITH TIP RIGHTWARDS +.. |rfisht| unicode:: U+0297D .. RIGHT FISH TAIL +.. |rHar| unicode:: U+02964 .. RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN +.. |rhard| unicode:: U+021C1 .. RIGHTWARDS HARPOON WITH BARB DOWNWARDS +.. |rharu| unicode:: U+021C0 .. RIGHTWARDS HARPOON WITH BARB UPWARDS +.. |rharul| unicode:: U+0296C .. RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH +.. |rlarr| unicode:: U+021C4 .. RIGHTWARDS ARROW OVER LEFTWARDS ARROW +.. |rlarr2| unicode:: U+021C4 .. RIGHTWARDS ARROW OVER LEFTWARDS ARROW +.. |rlhar| unicode:: U+021CC .. RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON +.. |rlhar2| unicode:: U+021CC .. RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON +.. |roarr| unicode:: U+021FE .. RIGHTWARDS OPEN-HEADED ARROW +.. |rrarr| unicode:: U+021C9 .. RIGHTWARDS PAIRED ARROWS +.. |rsh| unicode:: U+021B1 .. UPWARDS ARROW WITH TIP RIGHTWARDS +.. |ruluhar| unicode:: U+02968 .. RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP +.. |searhk| unicode:: U+02925 .. SOUTH EAST ARROW WITH HOOK +.. |seArr| unicode:: U+021D8 .. SOUTH EAST DOUBLE ARROW +.. |searr| unicode:: U+02198 .. SOUTH EAST ARROW +.. |seswar| unicode:: U+02929 .. SOUTH EAST ARROW AND SOUTH WEST ARROW +.. |simrarr| unicode:: U+02972 .. TILDE OPERATOR ABOVE RIGHTWARDS ARROW +.. |slarr| unicode:: U+02190 .. LEFTWARDS ARROW +.. |srarr| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |swarhk| unicode:: U+02926 .. SOUTH WEST ARROW WITH HOOK +.. |swArr| unicode:: U+021D9 .. SOUTH WEST DOUBLE ARROW +.. |swarr| unicode:: U+02199 .. SOUTH WEST ARROW +.. |swnwar| unicode:: U+0292A .. SOUTH WEST ARROW AND NORTH WEST ARROW +.. |Uarr| unicode:: U+0219F .. UPWARDS TWO HEADED ARROW +.. |uArr| unicode:: U+021D1 .. UPWARDS DOUBLE ARROW +.. |uarr2| unicode:: U+021C8 .. UPWARDS PAIRED ARROWS +.. |Uarrocir| unicode:: U+02949 .. UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE +.. |udarr| unicode:: U+021C5 .. UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW +.. |udhar| unicode:: U+0296E .. UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT +.. |ufisht| unicode:: U+0297E .. UP FISH TAIL +.. |uHar| unicode:: U+02963 .. UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT +.. |uharl| unicode:: U+021BF .. UPWARDS HARPOON WITH BARB LEFTWARDS +.. |uharr| unicode:: U+021BE .. UPWARDS HARPOON WITH BARB RIGHTWARDS +.. |uuarr| unicode:: U+021C8 .. UPWARDS PAIRED ARROWS +.. |vArr| unicode:: U+021D5 .. UP DOWN DOUBLE ARROW +.. |varr| unicode:: U+02195 .. UP DOWN ARROW +.. |xhArr| unicode:: U+027FA .. LONG LEFT RIGHT DOUBLE ARROW +.. |xharr| unicode:: U+027F7 .. LONG LEFT RIGHT ARROW +.. |xlArr| unicode:: U+027F8 .. LONG LEFTWARDS DOUBLE ARROW +.. |xlarr| unicode:: U+027F5 .. LONG LEFTWARDS ARROW +.. |xmap| unicode:: U+027FC .. LONG RIGHTWARDS ARROW FROM BAR +.. |xrArr| unicode:: U+027F9 .. LONG RIGHTWARDS DOUBLE ARROW +.. |xrarr| unicode:: U+027F6 .. LONG RIGHTWARDS ARROW +.. |zigrarr| unicode:: U+021DD .. RIGHTWARDS SQUIGGLE ARROW diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsb.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsb.txt new file mode 100644 index 00000000..d66fd4dd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsb.txt @@ -0,0 +1,126 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |ac| unicode:: U+0223E .. INVERTED LAZY S +.. |acE| unicode:: U+0223E U+00333 .. INVERTED LAZY S with double underline +.. |amalg| unicode:: U+02A3F .. AMALGAMATION OR COPRODUCT +.. |barvee| unicode:: U+022BD .. NOR +.. |Barwed| unicode:: U+02306 .. PERSPECTIVE +.. |barwed| unicode:: U+02305 .. PROJECTIVE +.. |bsolb| unicode:: U+029C5 .. SQUARED FALLING DIAGONAL SLASH +.. |Cap| unicode:: U+022D2 .. DOUBLE INTERSECTION +.. |capand| unicode:: U+02A44 .. INTERSECTION WITH LOGICAL AND +.. |capbrcup| unicode:: U+02A49 .. INTERSECTION ABOVE BAR ABOVE UNION +.. |capcap| unicode:: U+02A4B .. INTERSECTION BESIDE AND JOINED WITH INTERSECTION +.. |capcup| unicode:: U+02A47 .. INTERSECTION ABOVE UNION +.. |capdot| unicode:: U+02A40 .. INTERSECTION WITH DOT +.. |caps| unicode:: U+02229 U+0FE00 .. INTERSECTION with serifs +.. |ccaps| unicode:: U+02A4D .. CLOSED INTERSECTION WITH SERIFS +.. |ccups| unicode:: U+02A4C .. CLOSED UNION WITH SERIFS +.. |ccupssm| unicode:: U+02A50 .. CLOSED UNION WITH SERIFS AND SMASH PRODUCT +.. |coprod| unicode:: U+02210 .. N-ARY COPRODUCT +.. |Cup| unicode:: U+022D3 .. DOUBLE UNION +.. |cupbrcap| unicode:: U+02A48 .. UNION ABOVE BAR ABOVE INTERSECTION +.. |cupcap| unicode:: U+02A46 .. UNION ABOVE INTERSECTION +.. |cupcup| unicode:: U+02A4A .. UNION BESIDE AND JOINED WITH UNION +.. |cupdot| unicode:: U+0228D .. MULTISET MULTIPLICATION +.. |cupor| unicode:: U+02A45 .. UNION WITH LOGICAL OR +.. |cups| unicode:: U+0222A U+0FE00 .. UNION with serifs +.. |cuvee| unicode:: U+022CE .. CURLY LOGICAL OR +.. |cuwed| unicode:: U+022CF .. CURLY LOGICAL AND +.. |Dagger| unicode:: U+02021 .. DOUBLE DAGGER +.. |dagger| unicode:: U+02020 .. DAGGER +.. |diam| unicode:: U+022C4 .. DIAMOND OPERATOR +.. |divonx| unicode:: U+022C7 .. DIVISION TIMES +.. |eplus| unicode:: U+02A71 .. EQUALS SIGN ABOVE PLUS SIGN +.. |hercon| unicode:: U+022B9 .. HERMITIAN CONJUGATE MATRIX +.. |intcal| unicode:: U+022BA .. INTERCALATE +.. |iprod| unicode:: U+02A3C .. INTERIOR PRODUCT +.. |loplus| unicode:: U+02A2D .. PLUS SIGN IN LEFT HALF CIRCLE +.. |lotimes| unicode:: U+02A34 .. MULTIPLICATION SIGN IN LEFT HALF CIRCLE +.. |lthree| unicode:: U+022CB .. LEFT SEMIDIRECT PRODUCT +.. |ltimes| unicode:: U+022C9 .. LEFT NORMAL FACTOR SEMIDIRECT PRODUCT +.. |midast| unicode:: U+0002A .. ASTERISK +.. |minusb| unicode:: U+0229F .. SQUARED MINUS +.. |minusd| unicode:: U+02238 .. DOT MINUS +.. |minusdu| unicode:: U+02A2A .. MINUS SIGN WITH DOT BELOW +.. |ncap| unicode:: U+02A43 .. INTERSECTION WITH OVERBAR +.. |ncup| unicode:: U+02A42 .. UNION WITH OVERBAR +.. |oast| unicode:: U+0229B .. CIRCLED ASTERISK OPERATOR +.. |ocir| unicode:: U+0229A .. CIRCLED RING OPERATOR +.. |odash| unicode:: U+0229D .. CIRCLED DASH +.. |odiv| unicode:: U+02A38 .. CIRCLED DIVISION SIGN +.. |odot| unicode:: U+02299 .. CIRCLED DOT OPERATOR +.. |odsold| unicode:: U+029BC .. CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN +.. |ofcir| unicode:: U+029BF .. CIRCLED BULLET +.. |ogt| unicode:: U+029C1 .. CIRCLED GREATER-THAN +.. |ohbar| unicode:: U+029B5 .. CIRCLE WITH HORIZONTAL BAR +.. |olcir| unicode:: U+029BE .. CIRCLED WHITE BULLET +.. |olt| unicode:: U+029C0 .. CIRCLED LESS-THAN +.. |omid| unicode:: U+029B6 .. CIRCLED VERTICAL BAR +.. |ominus| unicode:: U+02296 .. CIRCLED MINUS +.. |opar| unicode:: U+029B7 .. CIRCLED PARALLEL +.. |operp| unicode:: U+029B9 .. CIRCLED PERPENDICULAR +.. |oplus| unicode:: U+02295 .. CIRCLED PLUS +.. |osol| unicode:: U+02298 .. CIRCLED DIVISION SLASH +.. |Otimes| unicode:: U+02A37 .. MULTIPLICATION SIGN IN DOUBLE CIRCLE +.. |otimes| unicode:: U+02297 .. CIRCLED TIMES +.. |otimesas| unicode:: U+02A36 .. CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT +.. |ovbar| unicode:: U+0233D .. APL FUNCTIONAL SYMBOL CIRCLE STILE +.. |plusacir| unicode:: U+02A23 .. PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE +.. |plusb| unicode:: U+0229E .. SQUARED PLUS +.. |pluscir| unicode:: U+02A22 .. PLUS SIGN WITH SMALL CIRCLE ABOVE +.. |plusdo| unicode:: U+02214 .. DOT PLUS +.. |plusdu| unicode:: U+02A25 .. PLUS SIGN WITH DOT BELOW +.. |pluse| unicode:: U+02A72 .. PLUS SIGN ABOVE EQUALS SIGN +.. |plussim| unicode:: U+02A26 .. PLUS SIGN WITH TILDE BELOW +.. |plustwo| unicode:: U+02A27 .. PLUS SIGN WITH SUBSCRIPT TWO +.. |prod| unicode:: U+0220F .. N-ARY PRODUCT +.. |race| unicode:: U+029DA .. LEFT DOUBLE WIGGLY FENCE +.. |roplus| unicode:: U+02A2E .. PLUS SIGN IN RIGHT HALF CIRCLE +.. |rotimes| unicode:: U+02A35 .. MULTIPLICATION SIGN IN RIGHT HALF CIRCLE +.. |rthree| unicode:: U+022CC .. RIGHT SEMIDIRECT PRODUCT +.. |rtimes| unicode:: U+022CA .. RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT +.. |sdot| unicode:: U+022C5 .. DOT OPERATOR +.. |sdotb| unicode:: U+022A1 .. SQUARED DOT OPERATOR +.. |setmn| unicode:: U+02216 .. SET MINUS +.. |simplus| unicode:: U+02A24 .. PLUS SIGN WITH TILDE ABOVE +.. |smashp| unicode:: U+02A33 .. SMASH PRODUCT +.. |solb| unicode:: U+029C4 .. SQUARED RISING DIAGONAL SLASH +.. |sqcap| unicode:: U+02293 .. SQUARE CAP +.. |sqcaps| unicode:: U+02293 U+0FE00 .. SQUARE CAP with serifs +.. |sqcup| unicode:: U+02294 .. SQUARE CUP +.. |sqcups| unicode:: U+02294 U+0FE00 .. SQUARE CUP with serifs +.. |ssetmn| unicode:: U+02216 .. SET MINUS +.. |sstarf| unicode:: U+022C6 .. STAR OPERATOR +.. |subdot| unicode:: U+02ABD .. SUBSET WITH DOT +.. |sum| unicode:: U+02211 .. N-ARY SUMMATION +.. |supdot| unicode:: U+02ABE .. SUPERSET WITH DOT +.. |timesb| unicode:: U+022A0 .. SQUARED TIMES +.. |timesbar| unicode:: U+02A31 .. MULTIPLICATION SIGN WITH UNDERBAR +.. |timesd| unicode:: U+02A30 .. MULTIPLICATION SIGN WITH DOT ABOVE +.. |top| unicode:: U+022A4 .. DOWN TACK +.. |tridot| unicode:: U+025EC .. WHITE UP-POINTING TRIANGLE WITH DOT +.. |triminus| unicode:: U+02A3A .. MINUS SIGN IN TRIANGLE +.. |triplus| unicode:: U+02A39 .. PLUS SIGN IN TRIANGLE +.. |trisb| unicode:: U+029CD .. TRIANGLE WITH SERIFS AT BOTTOM +.. |tritime| unicode:: U+02A3B .. MULTIPLICATION SIGN IN TRIANGLE +.. |uplus| unicode:: U+0228E .. MULTISET UNION +.. |veebar| unicode:: U+022BB .. XOR +.. |wedbar| unicode:: U+02A5F .. LOGICAL AND WITH UNDERBAR +.. |wreath| unicode:: U+02240 .. WREATH PRODUCT +.. |xcap| unicode:: U+022C2 .. N-ARY INTERSECTION +.. |xcirc| unicode:: U+025EF .. LARGE CIRCLE +.. |xcup| unicode:: U+022C3 .. N-ARY UNION +.. |xdtri| unicode:: U+025BD .. WHITE DOWN-POINTING TRIANGLE +.. |xodot| unicode:: U+02A00 .. N-ARY CIRCLED DOT OPERATOR +.. |xoplus| unicode:: U+02A01 .. N-ARY CIRCLED PLUS OPERATOR +.. |xotime| unicode:: U+02A02 .. N-ARY CIRCLED TIMES OPERATOR +.. |xsqcup| unicode:: U+02A06 .. N-ARY SQUARE UNION OPERATOR +.. |xuplus| unicode:: U+02A04 .. N-ARY UNION OPERATOR WITH PLUS +.. |xutri| unicode:: U+025B3 .. WHITE UP-POINTING TRIANGLE +.. |xvee| unicode:: U+022C1 .. N-ARY LOGICAL OR +.. |xwedge| unicode:: U+022C0 .. N-ARY LOGICAL AND diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsc.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsc.txt new file mode 100644 index 00000000..bef4c3e7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsc.txt @@ -0,0 +1,29 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |dlcorn| unicode:: U+0231E .. BOTTOM LEFT CORNER +.. |drcorn| unicode:: U+0231F .. BOTTOM RIGHT CORNER +.. |gtlPar| unicode:: U+02995 .. DOUBLE LEFT ARC GREATER-THAN BRACKET +.. |langd| unicode:: U+02991 .. LEFT ANGLE BRACKET WITH DOT +.. |lbrke| unicode:: U+0298B .. LEFT SQUARE BRACKET WITH UNDERBAR +.. |lbrksld| unicode:: U+0298F .. LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +.. |lbrkslu| unicode:: U+0298D .. LEFT SQUARE BRACKET WITH TICK IN TOP CORNER +.. |lceil| unicode:: U+02308 .. LEFT CEILING +.. |lfloor| unicode:: U+0230A .. LEFT FLOOR +.. |lmoust| unicode:: U+023B0 .. UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION +.. |lpargt| unicode:: U+029A0 .. SPHERICAL ANGLE OPENING LEFT +.. |lparlt| unicode:: U+02993 .. LEFT ARC LESS-THAN BRACKET +.. |ltrPar| unicode:: U+02996 .. DOUBLE RIGHT ARC LESS-THAN BRACKET +.. |rangd| unicode:: U+02992 .. RIGHT ANGLE BRACKET WITH DOT +.. |rbrke| unicode:: U+0298C .. RIGHT SQUARE BRACKET WITH UNDERBAR +.. |rbrksld| unicode:: U+0298E .. RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER +.. |rbrkslu| unicode:: U+02990 .. RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER +.. |rceil| unicode:: U+02309 .. RIGHT CEILING +.. |rfloor| unicode:: U+0230B .. RIGHT FLOOR +.. |rmoust| unicode:: U+023B1 .. UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION +.. |rpargt| unicode:: U+02994 .. RIGHT ARC GREATER-THAN BRACKET +.. |ulcorn| unicode:: U+0231C .. TOP LEFT CORNER +.. |urcorn| unicode:: U+0231D .. TOP RIGHT CORNER diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsn.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsn.txt new file mode 100644 index 00000000..65389e8d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsn.txt @@ -0,0 +1,96 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |gnap| unicode:: U+02A8A .. GREATER-THAN AND NOT APPROXIMATE +.. |gnE| unicode:: U+02269 .. GREATER-THAN BUT NOT EQUAL TO +.. |gne| unicode:: U+02A88 .. GREATER-THAN AND SINGLE-LINE NOT EQUAL TO +.. |gnsim| unicode:: U+022E7 .. GREATER-THAN BUT NOT EQUIVALENT TO +.. |gvnE| unicode:: U+02269 U+0FE00 .. GREATER-THAN BUT NOT EQUAL TO - with vertical stroke +.. |lnap| unicode:: U+02A89 .. LESS-THAN AND NOT APPROXIMATE +.. |lnE| unicode:: U+02268 .. LESS-THAN BUT NOT EQUAL TO +.. |lne| unicode:: U+02A87 .. LESS-THAN AND SINGLE-LINE NOT EQUAL TO +.. |lnsim| unicode:: U+022E6 .. LESS-THAN BUT NOT EQUIVALENT TO +.. |lvnE| unicode:: U+02268 U+0FE00 .. LESS-THAN BUT NOT EQUAL TO - with vertical stroke +.. |nap| unicode:: U+02249 .. NOT ALMOST EQUAL TO +.. |napE| unicode:: U+02A70 U+00338 .. APPROXIMATELY EQUAL OR EQUAL TO with slash +.. |napid| unicode:: U+0224B U+00338 .. TRIPLE TILDE with slash +.. |ncong| unicode:: U+02247 .. NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +.. |ncongdot| unicode:: U+02A6D U+00338 .. CONGRUENT WITH DOT ABOVE with slash +.. |nequiv| unicode:: U+02262 .. NOT IDENTICAL TO +.. |ngE| unicode:: U+02267 U+00338 .. GREATER-THAN OVER EQUAL TO with slash +.. |nge| unicode:: U+02271 .. NEITHER GREATER-THAN NOR EQUAL TO +.. |nges| unicode:: U+02A7E U+00338 .. GREATER-THAN OR SLANTED EQUAL TO with slash +.. |nGg| unicode:: U+022D9 U+00338 .. VERY MUCH GREATER-THAN with slash +.. |ngsim| unicode:: U+02275 .. NEITHER GREATER-THAN NOR EQUIVALENT TO +.. |nGt| unicode:: U+0226B U+020D2 .. MUCH GREATER THAN with vertical line +.. |ngt| unicode:: U+0226F .. NOT GREATER-THAN +.. |nGtv| unicode:: U+0226B U+00338 .. MUCH GREATER THAN with slash +.. |nlE| unicode:: U+02266 U+00338 .. LESS-THAN OVER EQUAL TO with slash +.. |nle| unicode:: U+02270 .. NEITHER LESS-THAN NOR EQUAL TO +.. |nles| unicode:: U+02A7D U+00338 .. LESS-THAN OR SLANTED EQUAL TO with slash +.. |nLl| unicode:: U+022D8 U+00338 .. VERY MUCH LESS-THAN with slash +.. |nlsim| unicode:: U+02274 .. NEITHER LESS-THAN NOR EQUIVALENT TO +.. |nLt| unicode:: U+0226A U+020D2 .. MUCH LESS THAN with vertical line +.. |nlt| unicode:: U+0226E .. NOT LESS-THAN +.. |nltri| unicode:: U+022EA .. NOT NORMAL SUBGROUP OF +.. |nltrie| unicode:: U+022EC .. NOT NORMAL SUBGROUP OF OR EQUAL TO +.. |nLtv| unicode:: U+0226A U+00338 .. MUCH LESS THAN with slash +.. |nmid| unicode:: U+02224 .. DOES NOT DIVIDE +.. |npar| unicode:: U+02226 .. NOT PARALLEL TO +.. |npr| unicode:: U+02280 .. DOES NOT PRECEDE +.. |nprcue| unicode:: U+022E0 .. DOES NOT PRECEDE OR EQUAL +.. |npre| unicode:: U+02AAF U+00338 .. PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash +.. |nrtri| unicode:: U+022EB .. DOES NOT CONTAIN AS NORMAL SUBGROUP +.. |nrtrie| unicode:: U+022ED .. DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL +.. |nsc| unicode:: U+02281 .. DOES NOT SUCCEED +.. |nsccue| unicode:: U+022E1 .. DOES NOT SUCCEED OR EQUAL +.. |nsce| unicode:: U+02AB0 U+00338 .. SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash +.. |nsim| unicode:: U+02241 .. NOT TILDE +.. |nsime| unicode:: U+02244 .. NOT ASYMPTOTICALLY EQUAL TO +.. |nsmid| unicode:: U+02224 .. DOES NOT DIVIDE +.. |nspar| unicode:: U+02226 .. NOT PARALLEL TO +.. |nsqsube| unicode:: U+022E2 .. NOT SQUARE IMAGE OF OR EQUAL TO +.. |nsqsupe| unicode:: U+022E3 .. NOT SQUARE ORIGINAL OF OR EQUAL TO +.. |nsub| unicode:: U+02284 .. NOT A SUBSET OF +.. |nsubE| unicode:: U+02AC5 U+00338 .. SUBSET OF ABOVE EQUALS SIGN with slash +.. |nsube| unicode:: U+02288 .. NEITHER A SUBSET OF NOR EQUAL TO +.. |nsup| unicode:: U+02285 .. NOT A SUPERSET OF +.. |nsupE| unicode:: U+02AC6 U+00338 .. SUPERSET OF ABOVE EQUALS SIGN with slash +.. |nsupe| unicode:: U+02289 .. NEITHER A SUPERSET OF NOR EQUAL TO +.. |ntgl| unicode:: U+02279 .. NEITHER GREATER-THAN NOR LESS-THAN +.. |ntlg| unicode:: U+02278 .. NEITHER LESS-THAN NOR GREATER-THAN +.. |nvap| unicode:: U+0224D U+020D2 .. EQUIVALENT TO with vertical line +.. |nVDash| unicode:: U+022AF .. NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +.. |nVdash| unicode:: U+022AE .. DOES NOT FORCE +.. |nvDash| unicode:: U+022AD .. NOT TRUE +.. |nvdash| unicode:: U+022AC .. DOES NOT PROVE +.. |nvge| unicode:: U+02265 U+020D2 .. GREATER-THAN OR EQUAL TO with vertical line +.. |nvgt| unicode:: U+0003E U+020D2 .. GREATER-THAN SIGN with vertical line +.. |nvle| unicode:: U+02264 U+020D2 .. LESS-THAN OR EQUAL TO with vertical line +.. |nvlt| unicode:: U+0003C U+020D2 .. LESS-THAN SIGN with vertical line +.. |nvltrie| unicode:: U+022B4 U+020D2 .. NORMAL SUBGROUP OF OR EQUAL TO with vertical line +.. |nvrtrie| unicode:: U+022B5 U+020D2 .. CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line +.. |nvsim| unicode:: U+0223C U+020D2 .. TILDE OPERATOR with vertical line +.. |parsim| unicode:: U+02AF3 .. PARALLEL WITH TILDE OPERATOR +.. |prnap| unicode:: U+02AB9 .. PRECEDES ABOVE NOT ALMOST EQUAL TO +.. |prnE| unicode:: U+02AB5 .. PRECEDES ABOVE NOT EQUAL TO +.. |prnsim| unicode:: U+022E8 .. PRECEDES BUT NOT EQUIVALENT TO +.. |rnmid| unicode:: U+02AEE .. DOES NOT DIVIDE WITH REVERSED NEGATION SLASH +.. |scnap| unicode:: U+02ABA .. SUCCEEDS ABOVE NOT ALMOST EQUAL TO +.. |scnE| unicode:: U+02AB6 .. SUCCEEDS ABOVE NOT EQUAL TO +.. |scnsim| unicode:: U+022E9 .. SUCCEEDS BUT NOT EQUIVALENT TO +.. |simne| unicode:: U+02246 .. APPROXIMATELY BUT NOT ACTUALLY EQUAL TO +.. |solbar| unicode:: U+0233F .. APL FUNCTIONAL SYMBOL SLASH BAR +.. |subnE| unicode:: U+02ACB .. SUBSET OF ABOVE NOT EQUAL TO +.. |subne| unicode:: U+0228A .. SUBSET OF WITH NOT EQUAL TO +.. |supnE| unicode:: U+02ACC .. SUPERSET OF ABOVE NOT EQUAL TO +.. |supne| unicode:: U+0228B .. SUPERSET OF WITH NOT EQUAL TO +.. |vnsub| unicode:: U+02282 U+020D2 .. SUBSET OF with vertical line +.. |vnsup| unicode:: U+02283 U+020D2 .. SUPERSET OF with vertical line +.. |vsubnE| unicode:: U+02ACB U+0FE00 .. SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members +.. |vsubne| unicode:: U+0228A U+0FE00 .. SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members +.. |vsupnE| unicode:: U+02ACC U+0FE00 .. SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members +.. |vsupne| unicode:: U+0228B U+0FE00 .. SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamso.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamso.txt new file mode 100644 index 00000000..f17e16bc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamso.txt @@ -0,0 +1,62 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |ang| unicode:: U+02220 .. ANGLE +.. |ange| unicode:: U+029A4 .. ANGLE WITH UNDERBAR +.. |angmsd| unicode:: U+02221 .. MEASURED ANGLE +.. |angmsdaa| unicode:: U+029A8 .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT +.. |angmsdab| unicode:: U+029A9 .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT +.. |angmsdac| unicode:: U+029AA .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT +.. |angmsdad| unicode:: U+029AB .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT +.. |angmsdae| unicode:: U+029AC .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP +.. |angmsdaf| unicode:: U+029AD .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP +.. |angmsdag| unicode:: U+029AE .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN +.. |angmsdah| unicode:: U+029AF .. MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN +.. |angrtvb| unicode:: U+022BE .. RIGHT ANGLE WITH ARC +.. |angrtvbd| unicode:: U+0299D .. MEASURED RIGHT ANGLE WITH DOT +.. |bbrk| unicode:: U+023B5 .. BOTTOM SQUARE BRACKET +.. |bbrktbrk| unicode:: U+023B6 .. BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET +.. |bemptyv| unicode:: U+029B0 .. REVERSED EMPTY SET +.. |beth| unicode:: U+02136 .. BET SYMBOL +.. |boxbox| unicode:: U+029C9 .. TWO JOINED SQUARES +.. |bprime| unicode:: U+02035 .. REVERSED PRIME +.. |bsemi| unicode:: U+0204F .. REVERSED SEMICOLON +.. |cemptyv| unicode:: U+029B2 .. EMPTY SET WITH SMALL CIRCLE ABOVE +.. |cirE| unicode:: U+029C3 .. CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT +.. |cirscir| unicode:: U+029C2 .. CIRCLE WITH SMALL CIRCLE TO THE RIGHT +.. |comp| unicode:: U+02201 .. COMPLEMENT +.. |daleth| unicode:: U+02138 .. DALET SYMBOL +.. |demptyv| unicode:: U+029B1 .. EMPTY SET WITH OVERBAR +.. |ell| unicode:: U+02113 .. SCRIPT SMALL L +.. |empty| unicode:: U+02205 .. EMPTY SET +.. |emptyv| unicode:: U+02205 .. EMPTY SET +.. |gimel| unicode:: U+02137 .. GIMEL SYMBOL +.. |iiota| unicode:: U+02129 .. TURNED GREEK SMALL LETTER IOTA +.. |image| unicode:: U+02111 .. BLACK-LETTER CAPITAL I +.. |imath| unicode:: U+00131 .. LATIN SMALL LETTER DOTLESS I +.. |inodot| unicode:: U+00131 .. LATIN SMALL LETTER DOTLESS I +.. |jmath| unicode:: U+0006A .. LATIN SMALL LETTER J +.. |jnodot| unicode:: U+0006A .. LATIN SMALL LETTER J +.. |laemptyv| unicode:: U+029B4 .. EMPTY SET WITH LEFT ARROW ABOVE +.. |lltri| unicode:: U+025FA .. LOWER LEFT TRIANGLE +.. |lrtri| unicode:: U+022BF .. RIGHT TRIANGLE +.. |mho| unicode:: U+02127 .. INVERTED OHM SIGN +.. |nang| unicode:: U+02220 U+020D2 .. ANGLE with vertical line +.. |nexist| unicode:: U+02204 .. THERE DOES NOT EXIST +.. |oS| unicode:: U+024C8 .. CIRCLED LATIN CAPITAL LETTER S +.. |planck| unicode:: U+0210F .. PLANCK CONSTANT OVER TWO PI +.. |plankv| unicode:: U+0210F .. PLANCK CONSTANT OVER TWO PI +.. |raemptyv| unicode:: U+029B3 .. EMPTY SET WITH RIGHT ARROW ABOVE +.. |range| unicode:: U+029A5 .. REVERSED ANGLE WITH UNDERBAR +.. |real| unicode:: U+0211C .. BLACK-LETTER CAPITAL R +.. |sbsol| unicode:: U+0FE68 .. SMALL REVERSE SOLIDUS +.. |tbrk| unicode:: U+023B4 .. TOP SQUARE BRACKET +.. |trpezium| unicode:: U+0FFFD .. REPLACEMENT CHARACTER +.. |ultri| unicode:: U+025F8 .. UPPER LEFT TRIANGLE +.. |urtri| unicode:: U+025F9 .. UPPER RIGHT TRIANGLE +.. |vprime| unicode:: U+02032 .. PRIME +.. |vzigzag| unicode:: U+0299A .. VERTICAL ZIGZAG LINE +.. |weierp| unicode:: U+02118 .. SCRIPT CAPITAL P diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsr.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsr.txt new file mode 100644 index 00000000..7d3c1aac --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isoamsr.txt @@ -0,0 +1,191 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |apE| unicode:: U+02A70 .. APPROXIMATELY EQUAL OR EQUAL TO +.. |ape| unicode:: U+0224A .. ALMOST EQUAL OR EQUAL TO +.. |apid| unicode:: U+0224B .. TRIPLE TILDE +.. |asymp| unicode:: U+02248 .. ALMOST EQUAL TO +.. |Barv| unicode:: U+02AE7 .. SHORT DOWN TACK WITH OVERBAR +.. |bcong| unicode:: U+0224C .. ALL EQUAL TO +.. |bepsi| unicode:: U+003F6 .. GREEK REVERSED LUNATE EPSILON SYMBOL +.. |bowtie| unicode:: U+022C8 .. BOWTIE +.. |bsim| unicode:: U+0223D .. REVERSED TILDE +.. |bsime| unicode:: U+022CD .. REVERSED TILDE EQUALS +.. |bsolhsub| unicode:: U+0005C U+02282 .. REVERSE SOLIDUS, SUBSET OF +.. |bump| unicode:: U+0224E .. GEOMETRICALLY EQUIVALENT TO +.. |bumpE| unicode:: U+02AAE .. EQUALS SIGN WITH BUMPY ABOVE +.. |bumpe| unicode:: U+0224F .. DIFFERENCE BETWEEN +.. |cire| unicode:: U+02257 .. RING EQUAL TO +.. |Colon| unicode:: U+02237 .. PROPORTION +.. |Colone| unicode:: U+02A74 .. DOUBLE COLON EQUAL +.. |colone| unicode:: U+02254 .. COLON EQUALS +.. |congdot| unicode:: U+02A6D .. CONGRUENT WITH DOT ABOVE +.. |csub| unicode:: U+02ACF .. CLOSED SUBSET +.. |csube| unicode:: U+02AD1 .. CLOSED SUBSET OR EQUAL TO +.. |csup| unicode:: U+02AD0 .. CLOSED SUPERSET +.. |csupe| unicode:: U+02AD2 .. CLOSED SUPERSET OR EQUAL TO +.. |cuepr| unicode:: U+022DE .. EQUAL TO OR PRECEDES +.. |cuesc| unicode:: U+022DF .. EQUAL TO OR SUCCEEDS +.. |cupre| unicode:: U+0227C .. PRECEDES OR EQUAL TO +.. |Dashv| unicode:: U+02AE4 .. VERTICAL BAR DOUBLE LEFT TURNSTILE +.. |dashv| unicode:: U+022A3 .. LEFT TACK +.. |easter| unicode:: U+02A6E .. EQUALS WITH ASTERISK +.. |ecir| unicode:: U+02256 .. RING IN EQUAL TO +.. |ecolon| unicode:: U+02255 .. EQUALS COLON +.. |eDDot| unicode:: U+02A77 .. EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW +.. |eDot| unicode:: U+02251 .. GEOMETRICALLY EQUAL TO +.. |efDot| unicode:: U+02252 .. APPROXIMATELY EQUAL TO OR THE IMAGE OF +.. |eg| unicode:: U+02A9A .. DOUBLE-LINE EQUAL TO OR GREATER-THAN +.. |egs| unicode:: U+02A96 .. SLANTED EQUAL TO OR GREATER-THAN +.. |egsdot| unicode:: U+02A98 .. SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE +.. |el| unicode:: U+02A99 .. DOUBLE-LINE EQUAL TO OR LESS-THAN +.. |els| unicode:: U+02A95 .. SLANTED EQUAL TO OR LESS-THAN +.. |elsdot| unicode:: U+02A97 .. SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE +.. |equest| unicode:: U+0225F .. QUESTIONED EQUAL TO +.. |equivDD| unicode:: U+02A78 .. EQUIVALENT WITH FOUR DOTS ABOVE +.. |erDot| unicode:: U+02253 .. IMAGE OF OR APPROXIMATELY EQUAL TO +.. |esdot| unicode:: U+02250 .. APPROACHES THE LIMIT +.. |Esim| unicode:: U+02A73 .. EQUALS SIGN ABOVE TILDE OPERATOR +.. |esim| unicode:: U+02242 .. MINUS TILDE +.. |fork| unicode:: U+022D4 .. PITCHFORK +.. |forkv| unicode:: U+02AD9 .. ELEMENT OF OPENING DOWNWARDS +.. |frown| unicode:: U+02322 .. FROWN +.. |gap| unicode:: U+02A86 .. GREATER-THAN OR APPROXIMATE +.. |gE| unicode:: U+02267 .. GREATER-THAN OVER EQUAL TO +.. |gEl| unicode:: U+02A8C .. GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN +.. |gel| unicode:: U+022DB .. GREATER-THAN EQUAL TO OR LESS-THAN +.. |ges| unicode:: U+02A7E .. GREATER-THAN OR SLANTED EQUAL TO +.. |gescc| unicode:: U+02AA9 .. GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL +.. |gesdot| unicode:: U+02A80 .. GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE +.. |gesdoto| unicode:: U+02A82 .. GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE +.. |gesdotol| unicode:: U+02A84 .. GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT +.. |gesl| unicode:: U+022DB U+0FE00 .. GREATER-THAN slanted EQUAL TO OR LESS-THAN +.. |gesles| unicode:: U+02A94 .. GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL +.. |Gg| unicode:: U+022D9 .. VERY MUCH GREATER-THAN +.. |gl| unicode:: U+02277 .. GREATER-THAN OR LESS-THAN +.. |gla| unicode:: U+02AA5 .. GREATER-THAN BESIDE LESS-THAN +.. |glE| unicode:: U+02A92 .. GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL +.. |glj| unicode:: U+02AA4 .. GREATER-THAN OVERLAPPING LESS-THAN +.. |gsdot| unicode:: U+022D7 .. GREATER-THAN WITH DOT +.. |gsim| unicode:: U+02273 .. GREATER-THAN OR EQUIVALENT TO +.. |gsime| unicode:: U+02A8E .. GREATER-THAN ABOVE SIMILAR OR EQUAL +.. |gsiml| unicode:: U+02A90 .. GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN +.. |Gt| unicode:: U+0226B .. MUCH GREATER-THAN +.. |gtcc| unicode:: U+02AA7 .. GREATER-THAN CLOSED BY CURVE +.. |gtcir| unicode:: U+02A7A .. GREATER-THAN WITH CIRCLE INSIDE +.. |gtdot| unicode:: U+022D7 .. GREATER-THAN WITH DOT +.. |gtquest| unicode:: U+02A7C .. GREATER-THAN WITH QUESTION MARK ABOVE +.. |gtrarr| unicode:: U+02978 .. GREATER-THAN ABOVE RIGHTWARDS ARROW +.. |homtht| unicode:: U+0223B .. HOMOTHETIC +.. |lap| unicode:: U+02A85 .. LESS-THAN OR APPROXIMATE +.. |lat| unicode:: U+02AAB .. LARGER THAN +.. |late| unicode:: U+02AAD .. LARGER THAN OR EQUAL TO +.. |lates| unicode:: U+02AAD U+0FE00 .. LARGER THAN OR slanted EQUAL +.. |ldot| unicode:: U+022D6 .. LESS-THAN WITH DOT +.. |lE| unicode:: U+02266 .. LESS-THAN OVER EQUAL TO +.. |lEg| unicode:: U+02A8B .. LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN +.. |leg| unicode:: U+022DA .. LESS-THAN EQUAL TO OR GREATER-THAN +.. |les| unicode:: U+02A7D .. LESS-THAN OR SLANTED EQUAL TO +.. |lescc| unicode:: U+02AA8 .. LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL +.. |lesdot| unicode:: U+02A7F .. LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE +.. |lesdoto| unicode:: U+02A81 .. LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE +.. |lesdotor| unicode:: U+02A83 .. LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT +.. |lesg| unicode:: U+022DA U+0FE00 .. LESS-THAN slanted EQUAL TO OR GREATER-THAN +.. |lesges| unicode:: U+02A93 .. LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL +.. |lg| unicode:: U+02276 .. LESS-THAN OR GREATER-THAN +.. |lgE| unicode:: U+02A91 .. LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL +.. |Ll| unicode:: U+022D8 .. VERY MUCH LESS-THAN +.. |lsim| unicode:: U+02272 .. LESS-THAN OR EQUIVALENT TO +.. |lsime| unicode:: U+02A8D .. LESS-THAN ABOVE SIMILAR OR EQUAL +.. |lsimg| unicode:: U+02A8F .. LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN +.. |Lt| unicode:: U+0226A .. MUCH LESS-THAN +.. |ltcc| unicode:: U+02AA6 .. LESS-THAN CLOSED BY CURVE +.. |ltcir| unicode:: U+02A79 .. LESS-THAN WITH CIRCLE INSIDE +.. |ltdot| unicode:: U+022D6 .. LESS-THAN WITH DOT +.. |ltlarr| unicode:: U+02976 .. LESS-THAN ABOVE LEFTWARDS ARROW +.. |ltquest| unicode:: U+02A7B .. LESS-THAN WITH QUESTION MARK ABOVE +.. |ltrie| unicode:: U+022B4 .. NORMAL SUBGROUP OF OR EQUAL TO +.. |mcomma| unicode:: U+02A29 .. MINUS SIGN WITH COMMA ABOVE +.. |mDDot| unicode:: U+0223A .. GEOMETRIC PROPORTION +.. |mid| unicode:: U+02223 .. DIVIDES +.. |mlcp| unicode:: U+02ADB .. TRANSVERSAL INTERSECTION +.. |models| unicode:: U+022A7 .. MODELS +.. |mstpos| unicode:: U+0223E .. INVERTED LAZY S +.. |Pr| unicode:: U+02ABB .. DOUBLE PRECEDES +.. |pr| unicode:: U+0227A .. PRECEDES +.. |prap| unicode:: U+02AB7 .. PRECEDES ABOVE ALMOST EQUAL TO +.. |prcue| unicode:: U+0227C .. PRECEDES OR EQUAL TO +.. |prE| unicode:: U+02AB3 .. PRECEDES ABOVE EQUALS SIGN +.. |pre| unicode:: U+02AAF .. PRECEDES ABOVE SINGLE-LINE EQUALS SIGN +.. |prsim| unicode:: U+0227E .. PRECEDES OR EQUIVALENT TO +.. |prurel| unicode:: U+022B0 .. PRECEDES UNDER RELATION +.. |ratio| unicode:: U+02236 .. RATIO +.. |rtrie| unicode:: U+022B5 .. CONTAINS AS NORMAL SUBGROUP OR EQUAL TO +.. |rtriltri| unicode:: U+029CE .. RIGHT TRIANGLE ABOVE LEFT TRIANGLE +.. |samalg| unicode:: U+02210 .. N-ARY COPRODUCT +.. |Sc| unicode:: U+02ABC .. DOUBLE SUCCEEDS +.. |sc| unicode:: U+0227B .. SUCCEEDS +.. |scap| unicode:: U+02AB8 .. SUCCEEDS ABOVE ALMOST EQUAL TO +.. |sccue| unicode:: U+0227D .. SUCCEEDS OR EQUAL TO +.. |scE| unicode:: U+02AB4 .. SUCCEEDS ABOVE EQUALS SIGN +.. |sce| unicode:: U+02AB0 .. SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN +.. |scsim| unicode:: U+0227F .. SUCCEEDS OR EQUIVALENT TO +.. |sdote| unicode:: U+02A66 .. EQUALS SIGN WITH DOT BELOW +.. |sfrown| unicode:: U+02322 .. FROWN +.. |simg| unicode:: U+02A9E .. SIMILAR OR GREATER-THAN +.. |simgE| unicode:: U+02AA0 .. SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN +.. |siml| unicode:: U+02A9D .. SIMILAR OR LESS-THAN +.. |simlE| unicode:: U+02A9F .. SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN +.. |smid| unicode:: U+02223 .. DIVIDES +.. |smile| unicode:: U+02323 .. SMILE +.. |smt| unicode:: U+02AAA .. SMALLER THAN +.. |smte| unicode:: U+02AAC .. SMALLER THAN OR EQUAL TO +.. |smtes| unicode:: U+02AAC U+0FE00 .. SMALLER THAN OR slanted EQUAL +.. |spar| unicode:: U+02225 .. PARALLEL TO +.. |sqsub| unicode:: U+0228F .. SQUARE IMAGE OF +.. |sqsube| unicode:: U+02291 .. SQUARE IMAGE OF OR EQUAL TO +.. |sqsup| unicode:: U+02290 .. SQUARE ORIGINAL OF +.. |sqsupe| unicode:: U+02292 .. SQUARE ORIGINAL OF OR EQUAL TO +.. |ssmile| unicode:: U+02323 .. SMILE +.. |Sub| unicode:: U+022D0 .. DOUBLE SUBSET +.. |subE| unicode:: U+02AC5 .. SUBSET OF ABOVE EQUALS SIGN +.. |subedot| unicode:: U+02AC3 .. SUBSET OF OR EQUAL TO WITH DOT ABOVE +.. |submult| unicode:: U+02AC1 .. SUBSET WITH MULTIPLICATION SIGN BELOW +.. |subplus| unicode:: U+02ABF .. SUBSET WITH PLUS SIGN BELOW +.. |subrarr| unicode:: U+02979 .. SUBSET ABOVE RIGHTWARDS ARROW +.. |subsim| unicode:: U+02AC7 .. SUBSET OF ABOVE TILDE OPERATOR +.. |subsub| unicode:: U+02AD5 .. SUBSET ABOVE SUBSET +.. |subsup| unicode:: U+02AD3 .. SUBSET ABOVE SUPERSET +.. |Sup| unicode:: U+022D1 .. DOUBLE SUPERSET +.. |supdsub| unicode:: U+02AD8 .. SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET +.. |supE| unicode:: U+02AC6 .. SUPERSET OF ABOVE EQUALS SIGN +.. |supedot| unicode:: U+02AC4 .. SUPERSET OF OR EQUAL TO WITH DOT ABOVE +.. |suphsol| unicode:: U+02283 U+0002F .. SUPERSET OF, SOLIDUS +.. |suphsub| unicode:: U+02AD7 .. SUPERSET BESIDE SUBSET +.. |suplarr| unicode:: U+0297B .. SUPERSET ABOVE LEFTWARDS ARROW +.. |supmult| unicode:: U+02AC2 .. SUPERSET WITH MULTIPLICATION SIGN BELOW +.. |supplus| unicode:: U+02AC0 .. SUPERSET WITH PLUS SIGN BELOW +.. |supsim| unicode:: U+02AC8 .. SUPERSET OF ABOVE TILDE OPERATOR +.. |supsub| unicode:: U+02AD4 .. SUPERSET ABOVE SUBSET +.. |supsup| unicode:: U+02AD6 .. SUPERSET ABOVE SUPERSET +.. |thkap| unicode:: U+02248 .. ALMOST EQUAL TO +.. |thksim| unicode:: U+0223C .. TILDE OPERATOR +.. |topfork| unicode:: U+02ADA .. PITCHFORK WITH TEE TOP +.. |trie| unicode:: U+0225C .. DELTA EQUAL TO +.. |twixt| unicode:: U+0226C .. BETWEEN +.. |Vbar| unicode:: U+02AEB .. DOUBLE UP TACK +.. |vBar| unicode:: U+02AE8 .. SHORT UP TACK WITH UNDERBAR +.. |vBarv| unicode:: U+02AE9 .. SHORT UP TACK ABOVE SHORT DOWN TACK +.. |VDash| unicode:: U+022AB .. DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE +.. |Vdash| unicode:: U+022A9 .. FORCES +.. |vDash| unicode:: U+022A8 .. TRUE +.. |vdash| unicode:: U+022A2 .. RIGHT TACK +.. |Vdashl| unicode:: U+02AE6 .. LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL +.. |veebar| unicode:: U+022BB .. XOR +.. |vltri| unicode:: U+022B2 .. NORMAL SUBGROUP OF +.. |vprop| unicode:: U+0221D .. PROPORTIONAL TO +.. |vrtri| unicode:: U+022B3 .. CONTAINS AS NORMAL SUBGROUP +.. |Vvdash| unicode:: U+022AA .. TRIPLE VERTICAL BAR RIGHT TURNSTILE diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isobox.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isobox.txt new file mode 100644 index 00000000..17d45bc6 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isobox.txt @@ -0,0 +1,46 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |boxDL| unicode:: U+02557 .. BOX DRAWINGS DOUBLE DOWN AND LEFT +.. |boxDl| unicode:: U+02556 .. BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE +.. |boxdL| unicode:: U+02555 .. BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE +.. |boxdl| unicode:: U+02510 .. BOX DRAWINGS LIGHT DOWN AND LEFT +.. |boxDR| unicode:: U+02554 .. BOX DRAWINGS DOUBLE DOWN AND RIGHT +.. |boxDr| unicode:: U+02553 .. BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE +.. |boxdR| unicode:: U+02552 .. BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE +.. |boxdr| unicode:: U+0250C .. BOX DRAWINGS LIGHT DOWN AND RIGHT +.. |boxH| unicode:: U+02550 .. BOX DRAWINGS DOUBLE HORIZONTAL +.. |boxh| unicode:: U+02500 .. BOX DRAWINGS LIGHT HORIZONTAL +.. |boxHD| unicode:: U+02566 .. BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL +.. |boxHd| unicode:: U+02564 .. BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE +.. |boxhD| unicode:: U+02565 .. BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE +.. |boxhd| unicode:: U+0252C .. BOX DRAWINGS LIGHT DOWN AND HORIZONTAL +.. |boxHU| unicode:: U+02569 .. BOX DRAWINGS DOUBLE UP AND HORIZONTAL +.. |boxHu| unicode:: U+02567 .. BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE +.. |boxhU| unicode:: U+02568 .. BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE +.. |boxhu| unicode:: U+02534 .. BOX DRAWINGS LIGHT UP AND HORIZONTAL +.. |boxUL| unicode:: U+0255D .. BOX DRAWINGS DOUBLE UP AND LEFT +.. |boxUl| unicode:: U+0255C .. BOX DRAWINGS UP DOUBLE AND LEFT SINGLE +.. |boxuL| unicode:: U+0255B .. BOX DRAWINGS UP SINGLE AND LEFT DOUBLE +.. |boxul| unicode:: U+02518 .. BOX DRAWINGS LIGHT UP AND LEFT +.. |boxUR| unicode:: U+0255A .. BOX DRAWINGS DOUBLE UP AND RIGHT +.. |boxUr| unicode:: U+02559 .. BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE +.. |boxuR| unicode:: U+02558 .. BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE +.. |boxur| unicode:: U+02514 .. BOX DRAWINGS LIGHT UP AND RIGHT +.. |boxV| unicode:: U+02551 .. BOX DRAWINGS DOUBLE VERTICAL +.. |boxv| unicode:: U+02502 .. BOX DRAWINGS LIGHT VERTICAL +.. |boxVH| unicode:: U+0256C .. BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL +.. |boxVh| unicode:: U+0256B .. BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE +.. |boxvH| unicode:: U+0256A .. BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE +.. |boxvh| unicode:: U+0253C .. BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL +.. |boxVL| unicode:: U+02563 .. BOX DRAWINGS DOUBLE VERTICAL AND LEFT +.. |boxVl| unicode:: U+02562 .. BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE +.. |boxvL| unicode:: U+02561 .. BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE +.. |boxvl| unicode:: U+02524 .. BOX DRAWINGS LIGHT VERTICAL AND LEFT +.. |boxVR| unicode:: U+02560 .. BOX DRAWINGS DOUBLE VERTICAL AND RIGHT +.. |boxVr| unicode:: U+0255F .. BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE +.. |boxvR| unicode:: U+0255E .. BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE +.. |boxvr| unicode:: U+0251C .. BOX DRAWINGS LIGHT VERTICAL AND RIGHT diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isocyr1.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isocyr1.txt new file mode 100644 index 00000000..5e0a18f5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isocyr1.txt @@ -0,0 +1,73 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Acy| unicode:: U+00410 .. CYRILLIC CAPITAL LETTER A +.. |acy| unicode:: U+00430 .. CYRILLIC SMALL LETTER A +.. |Bcy| unicode:: U+00411 .. CYRILLIC CAPITAL LETTER BE +.. |bcy| unicode:: U+00431 .. CYRILLIC SMALL LETTER BE +.. |CHcy| unicode:: U+00427 .. CYRILLIC CAPITAL LETTER CHE +.. |chcy| unicode:: U+00447 .. CYRILLIC SMALL LETTER CHE +.. |Dcy| unicode:: U+00414 .. CYRILLIC CAPITAL LETTER DE +.. |dcy| unicode:: U+00434 .. CYRILLIC SMALL LETTER DE +.. |Ecy| unicode:: U+0042D .. CYRILLIC CAPITAL LETTER E +.. |ecy| unicode:: U+0044D .. CYRILLIC SMALL LETTER E +.. |Fcy| unicode:: U+00424 .. CYRILLIC CAPITAL LETTER EF +.. |fcy| unicode:: U+00444 .. CYRILLIC SMALL LETTER EF +.. |Gcy| unicode:: U+00413 .. CYRILLIC CAPITAL LETTER GHE +.. |gcy| unicode:: U+00433 .. CYRILLIC SMALL LETTER GHE +.. |HARDcy| unicode:: U+0042A .. CYRILLIC CAPITAL LETTER HARD SIGN +.. |hardcy| unicode:: U+0044A .. CYRILLIC SMALL LETTER HARD SIGN +.. |Icy| unicode:: U+00418 .. CYRILLIC CAPITAL LETTER I +.. |icy| unicode:: U+00438 .. CYRILLIC SMALL LETTER I +.. |IEcy| unicode:: U+00415 .. CYRILLIC CAPITAL LETTER IE +.. |iecy| unicode:: U+00435 .. CYRILLIC SMALL LETTER IE +.. |IOcy| unicode:: U+00401 .. CYRILLIC CAPITAL LETTER IO +.. |iocy| unicode:: U+00451 .. CYRILLIC SMALL LETTER IO +.. |Jcy| unicode:: U+00419 .. CYRILLIC CAPITAL LETTER SHORT I +.. |jcy| unicode:: U+00439 .. CYRILLIC SMALL LETTER SHORT I +.. |Kcy| unicode:: U+0041A .. CYRILLIC CAPITAL LETTER KA +.. |kcy| unicode:: U+0043A .. CYRILLIC SMALL LETTER KA +.. |KHcy| unicode:: U+00425 .. CYRILLIC CAPITAL LETTER HA +.. |khcy| unicode:: U+00445 .. CYRILLIC SMALL LETTER HA +.. |Lcy| unicode:: U+0041B .. CYRILLIC CAPITAL LETTER EL +.. |lcy| unicode:: U+0043B .. CYRILLIC SMALL LETTER EL +.. |Mcy| unicode:: U+0041C .. CYRILLIC CAPITAL LETTER EM +.. |mcy| unicode:: U+0043C .. CYRILLIC SMALL LETTER EM +.. |Ncy| unicode:: U+0041D .. CYRILLIC CAPITAL LETTER EN +.. |ncy| unicode:: U+0043D .. CYRILLIC SMALL LETTER EN +.. |numero| unicode:: U+02116 .. NUMERO SIGN +.. |Ocy| unicode:: U+0041E .. CYRILLIC CAPITAL LETTER O +.. |ocy| unicode:: U+0043E .. CYRILLIC SMALL LETTER O +.. |Pcy| unicode:: U+0041F .. CYRILLIC CAPITAL LETTER PE +.. |pcy| unicode:: U+0043F .. CYRILLIC SMALL LETTER PE +.. |Rcy| unicode:: U+00420 .. CYRILLIC CAPITAL LETTER ER +.. |rcy| unicode:: U+00440 .. CYRILLIC SMALL LETTER ER +.. |Scy| unicode:: U+00421 .. CYRILLIC CAPITAL LETTER ES +.. |scy| unicode:: U+00441 .. CYRILLIC SMALL LETTER ES +.. |SHCHcy| unicode:: U+00429 .. CYRILLIC CAPITAL LETTER SHCHA +.. |shchcy| unicode:: U+00449 .. CYRILLIC SMALL LETTER SHCHA +.. |SHcy| unicode:: U+00428 .. CYRILLIC CAPITAL LETTER SHA +.. |shcy| unicode:: U+00448 .. CYRILLIC SMALL LETTER SHA +.. |SOFTcy| unicode:: U+0042C .. CYRILLIC CAPITAL LETTER SOFT SIGN +.. |softcy| unicode:: U+0044C .. CYRILLIC SMALL LETTER SOFT SIGN +.. |Tcy| unicode:: U+00422 .. CYRILLIC CAPITAL LETTER TE +.. |tcy| unicode:: U+00442 .. CYRILLIC SMALL LETTER TE +.. |TScy| unicode:: U+00426 .. CYRILLIC CAPITAL LETTER TSE +.. |tscy| unicode:: U+00446 .. CYRILLIC SMALL LETTER TSE +.. |Ucy| unicode:: U+00423 .. CYRILLIC CAPITAL LETTER U +.. |ucy| unicode:: U+00443 .. CYRILLIC SMALL LETTER U +.. |Vcy| unicode:: U+00412 .. CYRILLIC CAPITAL LETTER VE +.. |vcy| unicode:: U+00432 .. CYRILLIC SMALL LETTER VE +.. |YAcy| unicode:: U+0042F .. CYRILLIC CAPITAL LETTER YA +.. |yacy| unicode:: U+0044F .. CYRILLIC SMALL LETTER YA +.. |Ycy| unicode:: U+0042B .. CYRILLIC CAPITAL LETTER YERU +.. |ycy| unicode:: U+0044B .. CYRILLIC SMALL LETTER YERU +.. |YUcy| unicode:: U+0042E .. CYRILLIC CAPITAL LETTER YU +.. |yucy| unicode:: U+0044E .. CYRILLIC SMALL LETTER YU +.. |Zcy| unicode:: U+00417 .. CYRILLIC CAPITAL LETTER ZE +.. |zcy| unicode:: U+00437 .. CYRILLIC SMALL LETTER ZE +.. |ZHcy| unicode:: U+00416 .. CYRILLIC CAPITAL LETTER ZHE +.. |zhcy| unicode:: U+00436 .. CYRILLIC SMALL LETTER ZHE diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isocyr2.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isocyr2.txt new file mode 100644 index 00000000..a78190c0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isocyr2.txt @@ -0,0 +1,32 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |DJcy| unicode:: U+00402 .. CYRILLIC CAPITAL LETTER DJE +.. |djcy| unicode:: U+00452 .. CYRILLIC SMALL LETTER DJE +.. |DScy| unicode:: U+00405 .. CYRILLIC CAPITAL LETTER DZE +.. |dscy| unicode:: U+00455 .. CYRILLIC SMALL LETTER DZE +.. |DZcy| unicode:: U+0040F .. CYRILLIC CAPITAL LETTER DZHE +.. |dzcy| unicode:: U+0045F .. CYRILLIC SMALL LETTER DZHE +.. |GJcy| unicode:: U+00403 .. CYRILLIC CAPITAL LETTER GJE +.. |gjcy| unicode:: U+00453 .. CYRILLIC SMALL LETTER GJE +.. |Iukcy| unicode:: U+00406 .. CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I +.. |iukcy| unicode:: U+00456 .. CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I +.. |Jsercy| unicode:: U+00408 .. CYRILLIC CAPITAL LETTER JE +.. |jsercy| unicode:: U+00458 .. CYRILLIC SMALL LETTER JE +.. |Jukcy| unicode:: U+00404 .. CYRILLIC CAPITAL LETTER UKRAINIAN IE +.. |jukcy| unicode:: U+00454 .. CYRILLIC SMALL LETTER UKRAINIAN IE +.. |KJcy| unicode:: U+0040C .. CYRILLIC CAPITAL LETTER KJE +.. |kjcy| unicode:: U+0045C .. CYRILLIC SMALL LETTER KJE +.. |LJcy| unicode:: U+00409 .. CYRILLIC CAPITAL LETTER LJE +.. |ljcy| unicode:: U+00459 .. CYRILLIC SMALL LETTER LJE +.. |NJcy| unicode:: U+0040A .. CYRILLIC CAPITAL LETTER NJE +.. |njcy| unicode:: U+0045A .. CYRILLIC SMALL LETTER NJE +.. |TSHcy| unicode:: U+0040B .. CYRILLIC CAPITAL LETTER TSHE +.. |tshcy| unicode:: U+0045B .. CYRILLIC SMALL LETTER TSHE +.. |Ubrcy| unicode:: U+0040E .. CYRILLIC CAPITAL LETTER SHORT U +.. |ubrcy| unicode:: U+0045E .. CYRILLIC SMALL LETTER SHORT U +.. |YIcy| unicode:: U+00407 .. CYRILLIC CAPITAL LETTER YI +.. |yicy| unicode:: U+00457 .. CYRILLIC SMALL LETTER YI diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isodia.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isodia.txt new file mode 100644 index 00000000..cfe403ab --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isodia.txt @@ -0,0 +1,20 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |acute| unicode:: U+000B4 .. ACUTE ACCENT +.. |breve| unicode:: U+002D8 .. BREVE +.. |caron| unicode:: U+002C7 .. CARON +.. |cedil| unicode:: U+000B8 .. CEDILLA +.. |circ| unicode:: U+002C6 .. MODIFIER LETTER CIRCUMFLEX ACCENT +.. |dblac| unicode:: U+002DD .. DOUBLE ACUTE ACCENT +.. |die| unicode:: U+000A8 .. DIAERESIS +.. |dot| unicode:: U+002D9 .. DOT ABOVE +.. |grave| unicode:: U+00060 .. GRAVE ACCENT +.. |macr| unicode:: U+000AF .. MACRON +.. |ogon| unicode:: U+002DB .. OGONEK +.. |ring| unicode:: U+002DA .. RING ABOVE +.. |tilde| unicode:: U+002DC .. SMALL TILDE +.. |uml| unicode:: U+000A8 .. DIAERESIS diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk1.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk1.txt new file mode 100644 index 00000000..22a414bb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk1.txt @@ -0,0 +1,55 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Agr| unicode:: U+00391 .. GREEK CAPITAL LETTER ALPHA +.. |agr| unicode:: U+003B1 .. GREEK SMALL LETTER ALPHA +.. |Bgr| unicode:: U+00392 .. GREEK CAPITAL LETTER BETA +.. |bgr| unicode:: U+003B2 .. GREEK SMALL LETTER BETA +.. |Dgr| unicode:: U+00394 .. GREEK CAPITAL LETTER DELTA +.. |dgr| unicode:: U+003B4 .. GREEK SMALL LETTER DELTA +.. |EEgr| unicode:: U+00397 .. GREEK CAPITAL LETTER ETA +.. |eegr| unicode:: U+003B7 .. GREEK SMALL LETTER ETA +.. |Egr| unicode:: U+00395 .. GREEK CAPITAL LETTER EPSILON +.. |egr| unicode:: U+003B5 .. GREEK SMALL LETTER EPSILON +.. |Ggr| unicode:: U+00393 .. GREEK CAPITAL LETTER GAMMA +.. |ggr| unicode:: U+003B3 .. GREEK SMALL LETTER GAMMA +.. |Igr| unicode:: U+00399 .. GREEK CAPITAL LETTER IOTA +.. |igr| unicode:: U+003B9 .. GREEK SMALL LETTER IOTA +.. |Kgr| unicode:: U+0039A .. GREEK CAPITAL LETTER KAPPA +.. |kgr| unicode:: U+003BA .. GREEK SMALL LETTER KAPPA +.. |KHgr| unicode:: U+003A7 .. GREEK CAPITAL LETTER CHI +.. |khgr| unicode:: U+003C7 .. GREEK SMALL LETTER CHI +.. |Lgr| unicode:: U+0039B .. GREEK CAPITAL LETTER LAMDA +.. |lgr| unicode:: U+003BB .. GREEK SMALL LETTER LAMDA +.. |Mgr| unicode:: U+0039C .. GREEK CAPITAL LETTER MU +.. |mgr| unicode:: U+003BC .. GREEK SMALL LETTER MU +.. |Ngr| unicode:: U+0039D .. GREEK CAPITAL LETTER NU +.. |ngr| unicode:: U+003BD .. GREEK SMALL LETTER NU +.. |Ogr| unicode:: U+0039F .. GREEK CAPITAL LETTER OMICRON +.. |ogr| unicode:: U+003BF .. GREEK SMALL LETTER OMICRON +.. |OHgr| unicode:: U+003A9 .. GREEK CAPITAL LETTER OMEGA +.. |ohgr| unicode:: U+003C9 .. GREEK SMALL LETTER OMEGA +.. |Pgr| unicode:: U+003A0 .. GREEK CAPITAL LETTER PI +.. |pgr| unicode:: U+003C0 .. GREEK SMALL LETTER PI +.. |PHgr| unicode:: U+003A6 .. GREEK CAPITAL LETTER PHI +.. |phgr| unicode:: U+003C6 .. GREEK SMALL LETTER PHI +.. |PSgr| unicode:: U+003A8 .. GREEK CAPITAL LETTER PSI +.. |psgr| unicode:: U+003C8 .. GREEK SMALL LETTER PSI +.. |Rgr| unicode:: U+003A1 .. GREEK CAPITAL LETTER RHO +.. |rgr| unicode:: U+003C1 .. GREEK SMALL LETTER RHO +.. |sfgr| unicode:: U+003C2 .. GREEK SMALL LETTER FINAL SIGMA +.. |Sgr| unicode:: U+003A3 .. GREEK CAPITAL LETTER SIGMA +.. |sgr| unicode:: U+003C3 .. GREEK SMALL LETTER SIGMA +.. |Tgr| unicode:: U+003A4 .. GREEK CAPITAL LETTER TAU +.. |tgr| unicode:: U+003C4 .. GREEK SMALL LETTER TAU +.. |THgr| unicode:: U+00398 .. GREEK CAPITAL LETTER THETA +.. |thgr| unicode:: U+003B8 .. GREEK SMALL LETTER THETA +.. |Ugr| unicode:: U+003A5 .. GREEK CAPITAL LETTER UPSILON +.. |ugr| unicode:: U+003C5 .. GREEK SMALL LETTER UPSILON +.. |Xgr| unicode:: U+0039E .. GREEK CAPITAL LETTER XI +.. |xgr| unicode:: U+003BE .. GREEK SMALL LETTER XI +.. |Zgr| unicode:: U+00396 .. GREEK CAPITAL LETTER ZETA +.. |zgr| unicode:: U+003B6 .. GREEK SMALL LETTER ZETA diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk2.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk2.txt new file mode 100644 index 00000000..4b4090ec --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk2.txt @@ -0,0 +1,26 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Aacgr| unicode:: U+00386 .. GREEK CAPITAL LETTER ALPHA WITH TONOS +.. |aacgr| unicode:: U+003AC .. GREEK SMALL LETTER ALPHA WITH TONOS +.. |Eacgr| unicode:: U+00388 .. GREEK CAPITAL LETTER EPSILON WITH TONOS +.. |eacgr| unicode:: U+003AD .. GREEK SMALL LETTER EPSILON WITH TONOS +.. |EEacgr| unicode:: U+00389 .. GREEK CAPITAL LETTER ETA WITH TONOS +.. |eeacgr| unicode:: U+003AE .. GREEK SMALL LETTER ETA WITH TONOS +.. |Iacgr| unicode:: U+0038A .. GREEK CAPITAL LETTER IOTA WITH TONOS +.. |iacgr| unicode:: U+003AF .. GREEK SMALL LETTER IOTA WITH TONOS +.. |idiagr| unicode:: U+00390 .. GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS +.. |Idigr| unicode:: U+003AA .. GREEK CAPITAL LETTER IOTA WITH DIALYTIKA +.. |idigr| unicode:: U+003CA .. GREEK SMALL LETTER IOTA WITH DIALYTIKA +.. |Oacgr| unicode:: U+0038C .. GREEK CAPITAL LETTER OMICRON WITH TONOS +.. |oacgr| unicode:: U+003CC .. GREEK SMALL LETTER OMICRON WITH TONOS +.. |OHacgr| unicode:: U+0038F .. GREEK CAPITAL LETTER OMEGA WITH TONOS +.. |ohacgr| unicode:: U+003CE .. GREEK SMALL LETTER OMEGA WITH TONOS +.. |Uacgr| unicode:: U+0038E .. GREEK CAPITAL LETTER UPSILON WITH TONOS +.. |uacgr| unicode:: U+003CD .. GREEK SMALL LETTER UPSILON WITH TONOS +.. |udiagr| unicode:: U+003B0 .. GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS +.. |Udigr| unicode:: U+003AB .. GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA +.. |udigr| unicode:: U+003CB .. GREEK SMALL LETTER UPSILON WITH DIALYTIKA diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk3.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk3.txt new file mode 100644 index 00000000..54d212f2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk3.txt @@ -0,0 +1,52 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |alpha| unicode:: U+003B1 .. GREEK SMALL LETTER ALPHA +.. |beta| unicode:: U+003B2 .. GREEK SMALL LETTER BETA +.. |chi| unicode:: U+003C7 .. GREEK SMALL LETTER CHI +.. |Delta| unicode:: U+00394 .. GREEK CAPITAL LETTER DELTA +.. |delta| unicode:: U+003B4 .. GREEK SMALL LETTER DELTA +.. |epsi| unicode:: U+003F5 .. GREEK LUNATE EPSILON SYMBOL +.. |epsis| unicode:: U+003F5 .. GREEK LUNATE EPSILON SYMBOL +.. |epsiv| unicode:: U+003B5 .. GREEK SMALL LETTER EPSILON +.. |eta| unicode:: U+003B7 .. GREEK SMALL LETTER ETA +.. |Gamma| unicode:: U+00393 .. GREEK CAPITAL LETTER GAMMA +.. |gamma| unicode:: U+003B3 .. GREEK SMALL LETTER GAMMA +.. |Gammad| unicode:: U+003DC .. GREEK LETTER DIGAMMA +.. |gammad| unicode:: U+003DD .. GREEK SMALL LETTER DIGAMMA +.. |iota| unicode:: U+003B9 .. GREEK SMALL LETTER IOTA +.. |kappa| unicode:: U+003BA .. GREEK SMALL LETTER KAPPA +.. |kappav| unicode:: U+003F0 .. GREEK KAPPA SYMBOL +.. |Lambda| unicode:: U+0039B .. GREEK CAPITAL LETTER LAMDA +.. |lambda| unicode:: U+003BB .. GREEK SMALL LETTER LAMDA +.. |mu| unicode:: U+003BC .. GREEK SMALL LETTER MU +.. |nu| unicode:: U+003BD .. GREEK SMALL LETTER NU +.. |Omega| unicode:: U+003A9 .. GREEK CAPITAL LETTER OMEGA +.. |omega| unicode:: U+003C9 .. GREEK SMALL LETTER OMEGA +.. |Phi| unicode:: U+003A6 .. GREEK CAPITAL LETTER PHI +.. |phi| unicode:: U+003D5 .. GREEK PHI SYMBOL +.. |phis| unicode:: U+003D5 .. GREEK PHI SYMBOL +.. |phiv| unicode:: U+003C6 .. GREEK SMALL LETTER PHI +.. |Pi| unicode:: U+003A0 .. GREEK CAPITAL LETTER PI +.. |pi| unicode:: U+003C0 .. GREEK SMALL LETTER PI +.. |piv| unicode:: U+003D6 .. GREEK PI SYMBOL +.. |Psi| unicode:: U+003A8 .. GREEK CAPITAL LETTER PSI +.. |psi| unicode:: U+003C8 .. GREEK SMALL LETTER PSI +.. |rho| unicode:: U+003C1 .. GREEK SMALL LETTER RHO +.. |rhov| unicode:: U+003F1 .. GREEK RHO SYMBOL +.. |Sigma| unicode:: U+003A3 .. GREEK CAPITAL LETTER SIGMA +.. |sigma| unicode:: U+003C3 .. GREEK SMALL LETTER SIGMA +.. |sigmav| unicode:: U+003C2 .. GREEK SMALL LETTER FINAL SIGMA +.. |tau| unicode:: U+003C4 .. GREEK SMALL LETTER TAU +.. |Theta| unicode:: U+00398 .. GREEK CAPITAL LETTER THETA +.. |theta| unicode:: U+003B8 .. GREEK SMALL LETTER THETA +.. |thetas| unicode:: U+003B8 .. GREEK SMALL LETTER THETA +.. |thetav| unicode:: U+003D1 .. GREEK THETA SYMBOL +.. |Upsi| unicode:: U+003D2 .. GREEK UPSILON WITH HOOK SYMBOL +.. |upsi| unicode:: U+003C5 .. GREEK SMALL LETTER UPSILON +.. |Xi| unicode:: U+0039E .. GREEK CAPITAL LETTER XI +.. |xi| unicode:: U+003BE .. GREEK SMALL LETTER XI +.. |zeta| unicode:: U+003B6 .. GREEK SMALL LETTER ZETA diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk4-wide.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk4-wide.txt new file mode 100644 index 00000000..c0e0238d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk4-wide.txt @@ -0,0 +1,49 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |b.alpha| unicode:: U+1D6C2 .. MATHEMATICAL BOLD SMALL ALPHA +.. |b.beta| unicode:: U+1D6C3 .. MATHEMATICAL BOLD SMALL BETA +.. |b.chi| unicode:: U+1D6D8 .. MATHEMATICAL BOLD SMALL CHI +.. |b.Delta| unicode:: U+1D6AB .. MATHEMATICAL BOLD CAPITAL DELTA +.. |b.delta| unicode:: U+1D6C5 .. MATHEMATICAL BOLD SMALL DELTA +.. |b.epsi| unicode:: U+1D6C6 .. MATHEMATICAL BOLD SMALL EPSILON +.. |b.epsiv| unicode:: U+1D6DC .. MATHEMATICAL BOLD EPSILON SYMBOL +.. |b.eta| unicode:: U+1D6C8 .. MATHEMATICAL BOLD SMALL ETA +.. |b.Gamma| unicode:: U+1D6AA .. MATHEMATICAL BOLD CAPITAL GAMMA +.. |b.gamma| unicode:: U+1D6C4 .. MATHEMATICAL BOLD SMALL GAMMA +.. |b.Gammad| unicode:: U+003DC .. GREEK LETTER DIGAMMA +.. |b.gammad| unicode:: U+003DD .. GREEK SMALL LETTER DIGAMMA +.. |b.iota| unicode:: U+1D6CA .. MATHEMATICAL BOLD SMALL IOTA +.. |b.kappa| unicode:: U+1D6CB .. MATHEMATICAL BOLD SMALL KAPPA +.. |b.kappav| unicode:: U+1D6DE .. MATHEMATICAL BOLD KAPPA SYMBOL +.. |b.Lambda| unicode:: U+1D6B2 .. MATHEMATICAL BOLD CAPITAL LAMDA +.. |b.lambda| unicode:: U+1D6CC .. MATHEMATICAL BOLD SMALL LAMDA +.. |b.mu| unicode:: U+1D6CD .. MATHEMATICAL BOLD SMALL MU +.. |b.nu| unicode:: U+1D6CE .. MATHEMATICAL BOLD SMALL NU +.. |b.Omega| unicode:: U+1D6C0 .. MATHEMATICAL BOLD CAPITAL OMEGA +.. |b.omega| unicode:: U+1D6DA .. MATHEMATICAL BOLD SMALL OMEGA +.. |b.Phi| unicode:: U+1D6BD .. MATHEMATICAL BOLD CAPITAL PHI +.. |b.phi| unicode:: U+1D6D7 .. MATHEMATICAL BOLD SMALL PHI +.. |b.phiv| unicode:: U+1D6DF .. MATHEMATICAL BOLD PHI SYMBOL +.. |b.Pi| unicode:: U+1D6B7 .. MATHEMATICAL BOLD CAPITAL PI +.. |b.pi| unicode:: U+1D6D1 .. MATHEMATICAL BOLD SMALL PI +.. |b.piv| unicode:: U+1D6E1 .. MATHEMATICAL BOLD PI SYMBOL +.. |b.Psi| unicode:: U+1D6BF .. MATHEMATICAL BOLD CAPITAL PSI +.. |b.psi| unicode:: U+1D6D9 .. MATHEMATICAL BOLD SMALL PSI +.. |b.rho| unicode:: U+1D6D2 .. MATHEMATICAL BOLD SMALL RHO +.. |b.rhov| unicode:: U+1D6E0 .. MATHEMATICAL BOLD RHO SYMBOL +.. |b.Sigma| unicode:: U+1D6BA .. MATHEMATICAL BOLD CAPITAL SIGMA +.. |b.sigma| unicode:: U+1D6D4 .. MATHEMATICAL BOLD SMALL SIGMA +.. |b.sigmav| unicode:: U+1D6D3 .. MATHEMATICAL BOLD SMALL FINAL SIGMA +.. |b.tau| unicode:: U+1D6D5 .. MATHEMATICAL BOLD SMALL TAU +.. |b.Theta| unicode:: U+1D6AF .. MATHEMATICAL BOLD CAPITAL THETA +.. |b.thetas| unicode:: U+1D6C9 .. MATHEMATICAL BOLD SMALL THETA +.. |b.thetav| unicode:: U+1D6DD .. MATHEMATICAL BOLD THETA SYMBOL +.. |b.Upsi| unicode:: U+1D6BC .. MATHEMATICAL BOLD CAPITAL UPSILON +.. |b.upsi| unicode:: U+1D6D6 .. MATHEMATICAL BOLD SMALL UPSILON +.. |b.Xi| unicode:: U+1D6B5 .. MATHEMATICAL BOLD CAPITAL XI +.. |b.xi| unicode:: U+1D6CF .. MATHEMATICAL BOLD SMALL XI +.. |b.zeta| unicode:: U+1D6C7 .. MATHEMATICAL BOLD SMALL ZETA diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk4.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk4.txt new file mode 100644 index 00000000..836b6bd7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isogrk4.txt @@ -0,0 +1,8 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |b.Gammad| unicode:: U+003DC .. GREEK LETTER DIGAMMA +.. |b.gammad| unicode:: U+003DD .. GREEK SMALL LETTER DIGAMMA diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isolat1.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isolat1.txt new file mode 100644 index 00000000..4e4202b7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isolat1.txt @@ -0,0 +1,68 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Aacute| unicode:: U+000C1 .. LATIN CAPITAL LETTER A WITH ACUTE +.. |aacute| unicode:: U+000E1 .. LATIN SMALL LETTER A WITH ACUTE +.. |Acirc| unicode:: U+000C2 .. LATIN CAPITAL LETTER A WITH CIRCUMFLEX +.. |acirc| unicode:: U+000E2 .. LATIN SMALL LETTER A WITH CIRCUMFLEX +.. |AElig| unicode:: U+000C6 .. LATIN CAPITAL LETTER AE +.. |aelig| unicode:: U+000E6 .. LATIN SMALL LETTER AE +.. |Agrave| unicode:: U+000C0 .. LATIN CAPITAL LETTER A WITH GRAVE +.. |agrave| unicode:: U+000E0 .. LATIN SMALL LETTER A WITH GRAVE +.. |Aring| unicode:: U+000C5 .. LATIN CAPITAL LETTER A WITH RING ABOVE +.. |aring| unicode:: U+000E5 .. LATIN SMALL LETTER A WITH RING ABOVE +.. |Atilde| unicode:: U+000C3 .. LATIN CAPITAL LETTER A WITH TILDE +.. |atilde| unicode:: U+000E3 .. LATIN SMALL LETTER A WITH TILDE +.. |Auml| unicode:: U+000C4 .. LATIN CAPITAL LETTER A WITH DIAERESIS +.. |auml| unicode:: U+000E4 .. LATIN SMALL LETTER A WITH DIAERESIS +.. |Ccedil| unicode:: U+000C7 .. LATIN CAPITAL LETTER C WITH CEDILLA +.. |ccedil| unicode:: U+000E7 .. LATIN SMALL LETTER C WITH CEDILLA +.. |Eacute| unicode:: U+000C9 .. LATIN CAPITAL LETTER E WITH ACUTE +.. |eacute| unicode:: U+000E9 .. LATIN SMALL LETTER E WITH ACUTE +.. |Ecirc| unicode:: U+000CA .. LATIN CAPITAL LETTER E WITH CIRCUMFLEX +.. |ecirc| unicode:: U+000EA .. LATIN SMALL LETTER E WITH CIRCUMFLEX +.. |Egrave| unicode:: U+000C8 .. LATIN CAPITAL LETTER E WITH GRAVE +.. |egrave| unicode:: U+000E8 .. LATIN SMALL LETTER E WITH GRAVE +.. |ETH| unicode:: U+000D0 .. LATIN CAPITAL LETTER ETH +.. |eth| unicode:: U+000F0 .. LATIN SMALL LETTER ETH +.. |Euml| unicode:: U+000CB .. LATIN CAPITAL LETTER E WITH DIAERESIS +.. |euml| unicode:: U+000EB .. LATIN SMALL LETTER E WITH DIAERESIS +.. |Iacute| unicode:: U+000CD .. LATIN CAPITAL LETTER I WITH ACUTE +.. |iacute| unicode:: U+000ED .. LATIN SMALL LETTER I WITH ACUTE +.. |Icirc| unicode:: U+000CE .. LATIN CAPITAL LETTER I WITH CIRCUMFLEX +.. |icirc| unicode:: U+000EE .. LATIN SMALL LETTER I WITH CIRCUMFLEX +.. |Igrave| unicode:: U+000CC .. LATIN CAPITAL LETTER I WITH GRAVE +.. |igrave| unicode:: U+000EC .. LATIN SMALL LETTER I WITH GRAVE +.. |Iuml| unicode:: U+000CF .. LATIN CAPITAL LETTER I WITH DIAERESIS +.. |iuml| unicode:: U+000EF .. LATIN SMALL LETTER I WITH DIAERESIS +.. |Ntilde| unicode:: U+000D1 .. LATIN CAPITAL LETTER N WITH TILDE +.. |ntilde| unicode:: U+000F1 .. LATIN SMALL LETTER N WITH TILDE +.. |Oacute| unicode:: U+000D3 .. LATIN CAPITAL LETTER O WITH ACUTE +.. |oacute| unicode:: U+000F3 .. LATIN SMALL LETTER O WITH ACUTE +.. |Ocirc| unicode:: U+000D4 .. LATIN CAPITAL LETTER O WITH CIRCUMFLEX +.. |ocirc| unicode:: U+000F4 .. LATIN SMALL LETTER O WITH CIRCUMFLEX +.. |Ograve| unicode:: U+000D2 .. LATIN CAPITAL LETTER O WITH GRAVE +.. |ograve| unicode:: U+000F2 .. LATIN SMALL LETTER O WITH GRAVE +.. |Oslash| unicode:: U+000D8 .. LATIN CAPITAL LETTER O WITH STROKE +.. |oslash| unicode:: U+000F8 .. LATIN SMALL LETTER O WITH STROKE +.. |Otilde| unicode:: U+000D5 .. LATIN CAPITAL LETTER O WITH TILDE +.. |otilde| unicode:: U+000F5 .. LATIN SMALL LETTER O WITH TILDE +.. |Ouml| unicode:: U+000D6 .. LATIN CAPITAL LETTER O WITH DIAERESIS +.. |ouml| unicode:: U+000F6 .. LATIN SMALL LETTER O WITH DIAERESIS +.. |szlig| unicode:: U+000DF .. LATIN SMALL LETTER SHARP S +.. |THORN| unicode:: U+000DE .. LATIN CAPITAL LETTER THORN +.. |thorn| unicode:: U+000FE .. LATIN SMALL LETTER THORN +.. |Uacute| unicode:: U+000DA .. LATIN CAPITAL LETTER U WITH ACUTE +.. |uacute| unicode:: U+000FA .. LATIN SMALL LETTER U WITH ACUTE +.. |Ucirc| unicode:: U+000DB .. LATIN CAPITAL LETTER U WITH CIRCUMFLEX +.. |ucirc| unicode:: U+000FB .. LATIN SMALL LETTER U WITH CIRCUMFLEX +.. |Ugrave| unicode:: U+000D9 .. LATIN CAPITAL LETTER U WITH GRAVE +.. |ugrave| unicode:: U+000F9 .. LATIN SMALL LETTER U WITH GRAVE +.. |Uuml| unicode:: U+000DC .. LATIN CAPITAL LETTER U WITH DIAERESIS +.. |uuml| unicode:: U+000FC .. LATIN SMALL LETTER U WITH DIAERESIS +.. |Yacute| unicode:: U+000DD .. LATIN CAPITAL LETTER Y WITH ACUTE +.. |yacute| unicode:: U+000FD .. LATIN SMALL LETTER Y WITH ACUTE +.. |yuml| unicode:: U+000FF .. LATIN SMALL LETTER Y WITH DIAERESIS diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isolat2.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isolat2.txt new file mode 100644 index 00000000..808ef937 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isolat2.txt @@ -0,0 +1,128 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Abreve| unicode:: U+00102 .. LATIN CAPITAL LETTER A WITH BREVE +.. |abreve| unicode:: U+00103 .. LATIN SMALL LETTER A WITH BREVE +.. |Amacr| unicode:: U+00100 .. LATIN CAPITAL LETTER A WITH MACRON +.. |amacr| unicode:: U+00101 .. LATIN SMALL LETTER A WITH MACRON +.. |Aogon| unicode:: U+00104 .. LATIN CAPITAL LETTER A WITH OGONEK +.. |aogon| unicode:: U+00105 .. LATIN SMALL LETTER A WITH OGONEK +.. |Cacute| unicode:: U+00106 .. LATIN CAPITAL LETTER C WITH ACUTE +.. |cacute| unicode:: U+00107 .. LATIN SMALL LETTER C WITH ACUTE +.. |Ccaron| unicode:: U+0010C .. LATIN CAPITAL LETTER C WITH CARON +.. |ccaron| unicode:: U+0010D .. LATIN SMALL LETTER C WITH CARON +.. |Ccirc| unicode:: U+00108 .. LATIN CAPITAL LETTER C WITH CIRCUMFLEX +.. |ccirc| unicode:: U+00109 .. LATIN SMALL LETTER C WITH CIRCUMFLEX +.. |Cdot| unicode:: U+0010A .. LATIN CAPITAL LETTER C WITH DOT ABOVE +.. |cdot| unicode:: U+0010B .. LATIN SMALL LETTER C WITH DOT ABOVE +.. |Dcaron| unicode:: U+0010E .. LATIN CAPITAL LETTER D WITH CARON +.. |dcaron| unicode:: U+0010F .. LATIN SMALL LETTER D WITH CARON +.. |Dstrok| unicode:: U+00110 .. LATIN CAPITAL LETTER D WITH STROKE +.. |dstrok| unicode:: U+00111 .. LATIN SMALL LETTER D WITH STROKE +.. |Ecaron| unicode:: U+0011A .. LATIN CAPITAL LETTER E WITH CARON +.. |ecaron| unicode:: U+0011B .. LATIN SMALL LETTER E WITH CARON +.. |Edot| unicode:: U+00116 .. LATIN CAPITAL LETTER E WITH DOT ABOVE +.. |edot| unicode:: U+00117 .. LATIN SMALL LETTER E WITH DOT ABOVE +.. |Emacr| unicode:: U+00112 .. LATIN CAPITAL LETTER E WITH MACRON +.. |emacr| unicode:: U+00113 .. LATIN SMALL LETTER E WITH MACRON +.. |ENG| unicode:: U+0014A .. LATIN CAPITAL LETTER ENG +.. |eng| unicode:: U+0014B .. LATIN SMALL LETTER ENG +.. |Eogon| unicode:: U+00118 .. LATIN CAPITAL LETTER E WITH OGONEK +.. |eogon| unicode:: U+00119 .. LATIN SMALL LETTER E WITH OGONEK +.. |gacute| unicode:: U+001F5 .. LATIN SMALL LETTER G WITH ACUTE +.. |Gbreve| unicode:: U+0011E .. LATIN CAPITAL LETTER G WITH BREVE +.. |gbreve| unicode:: U+0011F .. LATIN SMALL LETTER G WITH BREVE +.. |Gcedil| unicode:: U+00122 .. LATIN CAPITAL LETTER G WITH CEDILLA +.. |gcedil| unicode:: U+00123 .. LATIN SMALL LETTER G WITH CEDILLA +.. |Gcirc| unicode:: U+0011C .. LATIN CAPITAL LETTER G WITH CIRCUMFLEX +.. |gcirc| unicode:: U+0011D .. LATIN SMALL LETTER G WITH CIRCUMFLEX +.. |Gdot| unicode:: U+00120 .. LATIN CAPITAL LETTER G WITH DOT ABOVE +.. |gdot| unicode:: U+00121 .. LATIN SMALL LETTER G WITH DOT ABOVE +.. |Hcirc| unicode:: U+00124 .. LATIN CAPITAL LETTER H WITH CIRCUMFLEX +.. |hcirc| unicode:: U+00125 .. LATIN SMALL LETTER H WITH CIRCUMFLEX +.. |Hstrok| unicode:: U+00126 .. LATIN CAPITAL LETTER H WITH STROKE +.. |hstrok| unicode:: U+00127 .. LATIN SMALL LETTER H WITH STROKE +.. |Idot| unicode:: U+00130 .. LATIN CAPITAL LETTER I WITH DOT ABOVE +.. |IJlig| unicode:: U+00132 .. LATIN CAPITAL LIGATURE IJ +.. |ijlig| unicode:: U+00133 .. LATIN SMALL LIGATURE IJ +.. |Imacr| unicode:: U+0012A .. LATIN CAPITAL LETTER I WITH MACRON +.. |imacr| unicode:: U+0012B .. LATIN SMALL LETTER I WITH MACRON +.. |inodot| unicode:: U+00131 .. LATIN SMALL LETTER DOTLESS I +.. |Iogon| unicode:: U+0012E .. LATIN CAPITAL LETTER I WITH OGONEK +.. |iogon| unicode:: U+0012F .. LATIN SMALL LETTER I WITH OGONEK +.. |Itilde| unicode:: U+00128 .. LATIN CAPITAL LETTER I WITH TILDE +.. |itilde| unicode:: U+00129 .. LATIN SMALL LETTER I WITH TILDE +.. |Jcirc| unicode:: U+00134 .. LATIN CAPITAL LETTER J WITH CIRCUMFLEX +.. |jcirc| unicode:: U+00135 .. LATIN SMALL LETTER J WITH CIRCUMFLEX +.. |Kcedil| unicode:: U+00136 .. LATIN CAPITAL LETTER K WITH CEDILLA +.. |kcedil| unicode:: U+00137 .. LATIN SMALL LETTER K WITH CEDILLA +.. |kgreen| unicode:: U+00138 .. LATIN SMALL LETTER KRA +.. |Lacute| unicode:: U+00139 .. LATIN CAPITAL LETTER L WITH ACUTE +.. |lacute| unicode:: U+0013A .. LATIN SMALL LETTER L WITH ACUTE +.. |Lcaron| unicode:: U+0013D .. LATIN CAPITAL LETTER L WITH CARON +.. |lcaron| unicode:: U+0013E .. LATIN SMALL LETTER L WITH CARON +.. |Lcedil| unicode:: U+0013B .. LATIN CAPITAL LETTER L WITH CEDILLA +.. |lcedil| unicode:: U+0013C .. LATIN SMALL LETTER L WITH CEDILLA +.. |Lmidot| unicode:: U+0013F .. LATIN CAPITAL LETTER L WITH MIDDLE DOT +.. |lmidot| unicode:: U+00140 .. LATIN SMALL LETTER L WITH MIDDLE DOT +.. |Lstrok| unicode:: U+00141 .. LATIN CAPITAL LETTER L WITH STROKE +.. |lstrok| unicode:: U+00142 .. LATIN SMALL LETTER L WITH STROKE +.. |Nacute| unicode:: U+00143 .. LATIN CAPITAL LETTER N WITH ACUTE +.. |nacute| unicode:: U+00144 .. LATIN SMALL LETTER N WITH ACUTE +.. |napos| unicode:: U+00149 .. LATIN SMALL LETTER N PRECEDED BY APOSTROPHE +.. |Ncaron| unicode:: U+00147 .. LATIN CAPITAL LETTER N WITH CARON +.. |ncaron| unicode:: U+00148 .. LATIN SMALL LETTER N WITH CARON +.. |Ncedil| unicode:: U+00145 .. LATIN CAPITAL LETTER N WITH CEDILLA +.. |ncedil| unicode:: U+00146 .. LATIN SMALL LETTER N WITH CEDILLA +.. |Odblac| unicode:: U+00150 .. LATIN CAPITAL LETTER O WITH DOUBLE ACUTE +.. |odblac| unicode:: U+00151 .. LATIN SMALL LETTER O WITH DOUBLE ACUTE +.. |OElig| unicode:: U+00152 .. LATIN CAPITAL LIGATURE OE +.. |oelig| unicode:: U+00153 .. LATIN SMALL LIGATURE OE +.. |Omacr| unicode:: U+0014C .. LATIN CAPITAL LETTER O WITH MACRON +.. |omacr| unicode:: U+0014D .. LATIN SMALL LETTER O WITH MACRON +.. |Racute| unicode:: U+00154 .. LATIN CAPITAL LETTER R WITH ACUTE +.. |racute| unicode:: U+00155 .. LATIN SMALL LETTER R WITH ACUTE +.. |Rcaron| unicode:: U+00158 .. LATIN CAPITAL LETTER R WITH CARON +.. |rcaron| unicode:: U+00159 .. LATIN SMALL LETTER R WITH CARON +.. |Rcedil| unicode:: U+00156 .. LATIN CAPITAL LETTER R WITH CEDILLA +.. |rcedil| unicode:: U+00157 .. LATIN SMALL LETTER R WITH CEDILLA +.. |Sacute| unicode:: U+0015A .. LATIN CAPITAL LETTER S WITH ACUTE +.. |sacute| unicode:: U+0015B .. LATIN SMALL LETTER S WITH ACUTE +.. |Scaron| unicode:: U+00160 .. LATIN CAPITAL LETTER S WITH CARON +.. |scaron| unicode:: U+00161 .. LATIN SMALL LETTER S WITH CARON +.. |Scedil| unicode:: U+0015E .. LATIN CAPITAL LETTER S WITH CEDILLA +.. |scedil| unicode:: U+0015F .. LATIN SMALL LETTER S WITH CEDILLA +.. |Scirc| unicode:: U+0015C .. LATIN CAPITAL LETTER S WITH CIRCUMFLEX +.. |scirc| unicode:: U+0015D .. LATIN SMALL LETTER S WITH CIRCUMFLEX +.. |Tcaron| unicode:: U+00164 .. LATIN CAPITAL LETTER T WITH CARON +.. |tcaron| unicode:: U+00165 .. LATIN SMALL LETTER T WITH CARON +.. |Tcedil| unicode:: U+00162 .. LATIN CAPITAL LETTER T WITH CEDILLA +.. |tcedil| unicode:: U+00163 .. LATIN SMALL LETTER T WITH CEDILLA +.. |Tstrok| unicode:: U+00166 .. LATIN CAPITAL LETTER T WITH STROKE +.. |tstrok| unicode:: U+00167 .. LATIN SMALL LETTER T WITH STROKE +.. |Ubreve| unicode:: U+0016C .. LATIN CAPITAL LETTER U WITH BREVE +.. |ubreve| unicode:: U+0016D .. LATIN SMALL LETTER U WITH BREVE +.. |Udblac| unicode:: U+00170 .. LATIN CAPITAL LETTER U WITH DOUBLE ACUTE +.. |udblac| unicode:: U+00171 .. LATIN SMALL LETTER U WITH DOUBLE ACUTE +.. |Umacr| unicode:: U+0016A .. LATIN CAPITAL LETTER U WITH MACRON +.. |umacr| unicode:: U+0016B .. LATIN SMALL LETTER U WITH MACRON +.. |Uogon| unicode:: U+00172 .. LATIN CAPITAL LETTER U WITH OGONEK +.. |uogon| unicode:: U+00173 .. LATIN SMALL LETTER U WITH OGONEK +.. |Uring| unicode:: U+0016E .. LATIN CAPITAL LETTER U WITH RING ABOVE +.. |uring| unicode:: U+0016F .. LATIN SMALL LETTER U WITH RING ABOVE +.. |Utilde| unicode:: U+00168 .. LATIN CAPITAL LETTER U WITH TILDE +.. |utilde| unicode:: U+00169 .. LATIN SMALL LETTER U WITH TILDE +.. |Wcirc| unicode:: U+00174 .. LATIN CAPITAL LETTER W WITH CIRCUMFLEX +.. |wcirc| unicode:: U+00175 .. LATIN SMALL LETTER W WITH CIRCUMFLEX +.. |Ycirc| unicode:: U+00176 .. LATIN CAPITAL LETTER Y WITH CIRCUMFLEX +.. |ycirc| unicode:: U+00177 .. LATIN SMALL LETTER Y WITH CIRCUMFLEX +.. |Yuml| unicode:: U+00178 .. LATIN CAPITAL LETTER Y WITH DIAERESIS +.. |Zacute| unicode:: U+00179 .. LATIN CAPITAL LETTER Z WITH ACUTE +.. |zacute| unicode:: U+0017A .. LATIN SMALL LETTER Z WITH ACUTE +.. |Zcaron| unicode:: U+0017D .. LATIN CAPITAL LETTER Z WITH CARON +.. |zcaron| unicode:: U+0017E .. LATIN SMALL LETTER Z WITH CARON +.. |Zdot| unicode:: U+0017B .. LATIN CAPITAL LETTER Z WITH DOT ABOVE +.. |zdot| unicode:: U+0017C .. LATIN SMALL LETTER Z WITH DOT ABOVE diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomfrk-wide.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomfrk-wide.txt new file mode 100644 index 00000000..73768bcc --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomfrk-wide.txt @@ -0,0 +1,58 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Afr| unicode:: U+1D504 .. MATHEMATICAL FRAKTUR CAPITAL A +.. |afr| unicode:: U+1D51E .. MATHEMATICAL FRAKTUR SMALL A +.. |Bfr| unicode:: U+1D505 .. MATHEMATICAL FRAKTUR CAPITAL B +.. |bfr| unicode:: U+1D51F .. MATHEMATICAL FRAKTUR SMALL B +.. |Cfr| unicode:: U+0212D .. BLACK-LETTER CAPITAL C +.. |cfr| unicode:: U+1D520 .. MATHEMATICAL FRAKTUR SMALL C +.. |Dfr| unicode:: U+1D507 .. MATHEMATICAL FRAKTUR CAPITAL D +.. |dfr| unicode:: U+1D521 .. MATHEMATICAL FRAKTUR SMALL D +.. |Efr| unicode:: U+1D508 .. MATHEMATICAL FRAKTUR CAPITAL E +.. |efr| unicode:: U+1D522 .. MATHEMATICAL FRAKTUR SMALL E +.. |Ffr| unicode:: U+1D509 .. MATHEMATICAL FRAKTUR CAPITAL F +.. |ffr| unicode:: U+1D523 .. MATHEMATICAL FRAKTUR SMALL F +.. |Gfr| unicode:: U+1D50A .. MATHEMATICAL FRAKTUR CAPITAL G +.. |gfr| unicode:: U+1D524 .. MATHEMATICAL FRAKTUR SMALL G +.. |Hfr| unicode:: U+0210C .. BLACK-LETTER CAPITAL H +.. |hfr| unicode:: U+1D525 .. MATHEMATICAL FRAKTUR SMALL H +.. |Ifr| unicode:: U+02111 .. BLACK-LETTER CAPITAL I +.. |ifr| unicode:: U+1D526 .. MATHEMATICAL FRAKTUR SMALL I +.. |Jfr| unicode:: U+1D50D .. MATHEMATICAL FRAKTUR CAPITAL J +.. |jfr| unicode:: U+1D527 .. MATHEMATICAL FRAKTUR SMALL J +.. |Kfr| unicode:: U+1D50E .. MATHEMATICAL FRAKTUR CAPITAL K +.. |kfr| unicode:: U+1D528 .. MATHEMATICAL FRAKTUR SMALL K +.. |Lfr| unicode:: U+1D50F .. MATHEMATICAL FRAKTUR CAPITAL L +.. |lfr| unicode:: U+1D529 .. MATHEMATICAL FRAKTUR SMALL L +.. |Mfr| unicode:: U+1D510 .. MATHEMATICAL FRAKTUR CAPITAL M +.. |mfr| unicode:: U+1D52A .. MATHEMATICAL FRAKTUR SMALL M +.. |Nfr| unicode:: U+1D511 .. MATHEMATICAL FRAKTUR CAPITAL N +.. |nfr| unicode:: U+1D52B .. MATHEMATICAL FRAKTUR SMALL N +.. |Ofr| unicode:: U+1D512 .. MATHEMATICAL FRAKTUR CAPITAL O +.. |ofr| unicode:: U+1D52C .. MATHEMATICAL FRAKTUR SMALL O +.. |Pfr| unicode:: U+1D513 .. MATHEMATICAL FRAKTUR CAPITAL P +.. |pfr| unicode:: U+1D52D .. MATHEMATICAL FRAKTUR SMALL P +.. |Qfr| unicode:: U+1D514 .. MATHEMATICAL FRAKTUR CAPITAL Q +.. |qfr| unicode:: U+1D52E .. MATHEMATICAL FRAKTUR SMALL Q +.. |Rfr| unicode:: U+0211C .. BLACK-LETTER CAPITAL R +.. |rfr| unicode:: U+1D52F .. MATHEMATICAL FRAKTUR SMALL R +.. |Sfr| unicode:: U+1D516 .. MATHEMATICAL FRAKTUR CAPITAL S +.. |sfr| unicode:: U+1D530 .. MATHEMATICAL FRAKTUR SMALL S +.. |Tfr| unicode:: U+1D517 .. MATHEMATICAL FRAKTUR CAPITAL T +.. |tfr| unicode:: U+1D531 .. MATHEMATICAL FRAKTUR SMALL T +.. |Ufr| unicode:: U+1D518 .. MATHEMATICAL FRAKTUR CAPITAL U +.. |ufr| unicode:: U+1D532 .. MATHEMATICAL FRAKTUR SMALL U +.. |Vfr| unicode:: U+1D519 .. MATHEMATICAL FRAKTUR CAPITAL V +.. |vfr| unicode:: U+1D533 .. MATHEMATICAL FRAKTUR SMALL V +.. |Wfr| unicode:: U+1D51A .. MATHEMATICAL FRAKTUR CAPITAL W +.. |wfr| unicode:: U+1D534 .. MATHEMATICAL FRAKTUR SMALL W +.. |Xfr| unicode:: U+1D51B .. MATHEMATICAL FRAKTUR CAPITAL X +.. |xfr| unicode:: U+1D535 .. MATHEMATICAL FRAKTUR SMALL X +.. |Yfr| unicode:: U+1D51C .. MATHEMATICAL FRAKTUR CAPITAL Y +.. |yfr| unicode:: U+1D536 .. MATHEMATICAL FRAKTUR SMALL Y +.. |Zfr| unicode:: U+02128 .. BLACK-LETTER CAPITAL Z +.. |zfr| unicode:: U+1D537 .. MATHEMATICAL FRAKTUR SMALL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomfrk.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomfrk.txt new file mode 100644 index 00000000..81dd4b6e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomfrk.txt @@ -0,0 +1,11 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Cfr| unicode:: U+0212D .. BLACK-LETTER CAPITAL C +.. |Hfr| unicode:: U+0210C .. BLACK-LETTER CAPITAL H +.. |Ifr| unicode:: U+02111 .. BLACK-LETTER CAPITAL I +.. |Rfr| unicode:: U+0211C .. BLACK-LETTER CAPITAL R +.. |Zfr| unicode:: U+02128 .. BLACK-LETTER CAPITAL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomopf-wide.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomopf-wide.txt new file mode 100644 index 00000000..2c16866e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomopf-wide.txt @@ -0,0 +1,32 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Aopf| unicode:: U+1D538 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL A +.. |Bopf| unicode:: U+1D539 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL B +.. |Copf| unicode:: U+02102 .. DOUBLE-STRUCK CAPITAL C +.. |Dopf| unicode:: U+1D53B .. MATHEMATICAL DOUBLE-STRUCK CAPITAL D +.. |Eopf| unicode:: U+1D53C .. MATHEMATICAL DOUBLE-STRUCK CAPITAL E +.. |Fopf| unicode:: U+1D53D .. MATHEMATICAL DOUBLE-STRUCK CAPITAL F +.. |Gopf| unicode:: U+1D53E .. MATHEMATICAL DOUBLE-STRUCK CAPITAL G +.. |Hopf| unicode:: U+0210D .. DOUBLE-STRUCK CAPITAL H +.. |Iopf| unicode:: U+1D540 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL I +.. |Jopf| unicode:: U+1D541 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL J +.. |Kopf| unicode:: U+1D542 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL K +.. |Lopf| unicode:: U+1D543 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL L +.. |Mopf| unicode:: U+1D544 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL M +.. |Nopf| unicode:: U+02115 .. DOUBLE-STRUCK CAPITAL N +.. |Oopf| unicode:: U+1D546 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL O +.. |Popf| unicode:: U+02119 .. DOUBLE-STRUCK CAPITAL P +.. |Qopf| unicode:: U+0211A .. DOUBLE-STRUCK CAPITAL Q +.. |Ropf| unicode:: U+0211D .. DOUBLE-STRUCK CAPITAL R +.. |Sopf| unicode:: U+1D54A .. MATHEMATICAL DOUBLE-STRUCK CAPITAL S +.. |Topf| unicode:: U+1D54B .. MATHEMATICAL DOUBLE-STRUCK CAPITAL T +.. |Uopf| unicode:: U+1D54C .. MATHEMATICAL DOUBLE-STRUCK CAPITAL U +.. |Vopf| unicode:: U+1D54D .. MATHEMATICAL DOUBLE-STRUCK CAPITAL V +.. |Wopf| unicode:: U+1D54E .. MATHEMATICAL DOUBLE-STRUCK CAPITAL W +.. |Xopf| unicode:: U+1D54F .. MATHEMATICAL DOUBLE-STRUCK CAPITAL X +.. |Yopf| unicode:: U+1D550 .. MATHEMATICAL DOUBLE-STRUCK CAPITAL Y +.. |Zopf| unicode:: U+02124 .. DOUBLE-STRUCK CAPITAL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomopf.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomopf.txt new file mode 100644 index 00000000..03c6a0d2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomopf.txt @@ -0,0 +1,13 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Copf| unicode:: U+02102 .. DOUBLE-STRUCK CAPITAL C +.. |Hopf| unicode:: U+0210D .. DOUBLE-STRUCK CAPITAL H +.. |Nopf| unicode:: U+02115 .. DOUBLE-STRUCK CAPITAL N +.. |Popf| unicode:: U+02119 .. DOUBLE-STRUCK CAPITAL P +.. |Qopf| unicode:: U+0211A .. DOUBLE-STRUCK CAPITAL Q +.. |Ropf| unicode:: U+0211D .. DOUBLE-STRUCK CAPITAL R +.. |Zopf| unicode:: U+02124 .. DOUBLE-STRUCK CAPITAL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomscr-wide.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomscr-wide.txt new file mode 100644 index 00000000..acfe7a00 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomscr-wide.txt @@ -0,0 +1,58 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Ascr| unicode:: U+1D49C .. MATHEMATICAL SCRIPT CAPITAL A +.. |ascr| unicode:: U+1D4B6 .. MATHEMATICAL SCRIPT SMALL A +.. |Bscr| unicode:: U+0212C .. SCRIPT CAPITAL B +.. |bscr| unicode:: U+1D4B7 .. MATHEMATICAL SCRIPT SMALL B +.. |Cscr| unicode:: U+1D49E .. MATHEMATICAL SCRIPT CAPITAL C +.. |cscr| unicode:: U+1D4B8 .. MATHEMATICAL SCRIPT SMALL C +.. |Dscr| unicode:: U+1D49F .. MATHEMATICAL SCRIPT CAPITAL D +.. |dscr| unicode:: U+1D4B9 .. MATHEMATICAL SCRIPT SMALL D +.. |Escr| unicode:: U+02130 .. SCRIPT CAPITAL E +.. |escr| unicode:: U+0212F .. SCRIPT SMALL E +.. |Fscr| unicode:: U+02131 .. SCRIPT CAPITAL F +.. |fscr| unicode:: U+1D4BB .. MATHEMATICAL SCRIPT SMALL F +.. |Gscr| unicode:: U+1D4A2 .. MATHEMATICAL SCRIPT CAPITAL G +.. |gscr| unicode:: U+0210A .. SCRIPT SMALL G +.. |Hscr| unicode:: U+0210B .. SCRIPT CAPITAL H +.. |hscr| unicode:: U+1D4BD .. MATHEMATICAL SCRIPT SMALL H +.. |Iscr| unicode:: U+02110 .. SCRIPT CAPITAL I +.. |iscr| unicode:: U+1D4BE .. MATHEMATICAL SCRIPT SMALL I +.. |Jscr| unicode:: U+1D4A5 .. MATHEMATICAL SCRIPT CAPITAL J +.. |jscr| unicode:: U+1D4BF .. MATHEMATICAL SCRIPT SMALL J +.. |Kscr| unicode:: U+1D4A6 .. MATHEMATICAL SCRIPT CAPITAL K +.. |kscr| unicode:: U+1D4C0 .. MATHEMATICAL SCRIPT SMALL K +.. |Lscr| unicode:: U+02112 .. SCRIPT CAPITAL L +.. |lscr| unicode:: U+1D4C1 .. MATHEMATICAL SCRIPT SMALL L +.. |Mscr| unicode:: U+02133 .. SCRIPT CAPITAL M +.. |mscr| unicode:: U+1D4C2 .. MATHEMATICAL SCRIPT SMALL M +.. |Nscr| unicode:: U+1D4A9 .. MATHEMATICAL SCRIPT CAPITAL N +.. |nscr| unicode:: U+1D4C3 .. MATHEMATICAL SCRIPT SMALL N +.. |Oscr| unicode:: U+1D4AA .. MATHEMATICAL SCRIPT CAPITAL O +.. |oscr| unicode:: U+02134 .. SCRIPT SMALL O +.. |Pscr| unicode:: U+1D4AB .. MATHEMATICAL SCRIPT CAPITAL P +.. |pscr| unicode:: U+1D4C5 .. MATHEMATICAL SCRIPT SMALL P +.. |Qscr| unicode:: U+1D4AC .. MATHEMATICAL SCRIPT CAPITAL Q +.. |qscr| unicode:: U+1D4C6 .. MATHEMATICAL SCRIPT SMALL Q +.. |Rscr| unicode:: U+0211B .. SCRIPT CAPITAL R +.. |rscr| unicode:: U+1D4C7 .. MATHEMATICAL SCRIPT SMALL R +.. |Sscr| unicode:: U+1D4AE .. MATHEMATICAL SCRIPT CAPITAL S +.. |sscr| unicode:: U+1D4C8 .. MATHEMATICAL SCRIPT SMALL S +.. |Tscr| unicode:: U+1D4AF .. MATHEMATICAL SCRIPT CAPITAL T +.. |tscr| unicode:: U+1D4C9 .. MATHEMATICAL SCRIPT SMALL T +.. |Uscr| unicode:: U+1D4B0 .. MATHEMATICAL SCRIPT CAPITAL U +.. |uscr| unicode:: U+1D4CA .. MATHEMATICAL SCRIPT SMALL U +.. |Vscr| unicode:: U+1D4B1 .. MATHEMATICAL SCRIPT CAPITAL V +.. |vscr| unicode:: U+1D4CB .. MATHEMATICAL SCRIPT SMALL V +.. |Wscr| unicode:: U+1D4B2 .. MATHEMATICAL SCRIPT CAPITAL W +.. |wscr| unicode:: U+1D4CC .. MATHEMATICAL SCRIPT SMALL W +.. |Xscr| unicode:: U+1D4B3 .. MATHEMATICAL SCRIPT CAPITAL X +.. |xscr| unicode:: U+1D4CD .. MATHEMATICAL SCRIPT SMALL X +.. |Yscr| unicode:: U+1D4B4 .. MATHEMATICAL SCRIPT CAPITAL Y +.. |yscr| unicode:: U+1D4CE .. MATHEMATICAL SCRIPT SMALL Y +.. |Zscr| unicode:: U+1D4B5 .. MATHEMATICAL SCRIPT CAPITAL Z +.. |zscr| unicode:: U+1D4CF .. MATHEMATICAL SCRIPT SMALL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomscr.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomscr.txt new file mode 100644 index 00000000..951577e8 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isomscr.txt @@ -0,0 +1,17 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Bscr| unicode:: U+0212C .. SCRIPT CAPITAL B +.. |Escr| unicode:: U+02130 .. SCRIPT CAPITAL E +.. |escr| unicode:: U+0212F .. SCRIPT SMALL E +.. |Fscr| unicode:: U+02131 .. SCRIPT CAPITAL F +.. |gscr| unicode:: U+0210A .. SCRIPT SMALL G +.. |Hscr| unicode:: U+0210B .. SCRIPT CAPITAL H +.. |Iscr| unicode:: U+02110 .. SCRIPT CAPITAL I +.. |Lscr| unicode:: U+02112 .. SCRIPT CAPITAL L +.. |Mscr| unicode:: U+02133 .. SCRIPT CAPITAL M +.. |oscr| unicode:: U+02134 .. SCRIPT SMALL O +.. |Rscr| unicode:: U+0211B .. SCRIPT CAPITAL R diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isonum.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isonum.txt new file mode 100644 index 00000000..0d280c98 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isonum.txt @@ -0,0 +1,82 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |amp| unicode:: U+00026 .. AMPERSAND +.. |apos| unicode:: U+00027 .. APOSTROPHE +.. |ast| unicode:: U+0002A .. ASTERISK +.. |brvbar| unicode:: U+000A6 .. BROKEN BAR +.. |bsol| unicode:: U+0005C .. REVERSE SOLIDUS +.. |cent| unicode:: U+000A2 .. CENT SIGN +.. |colon| unicode:: U+0003A .. COLON +.. |comma| unicode:: U+0002C .. COMMA +.. |commat| unicode:: U+00040 .. COMMERCIAL AT +.. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN +.. |curren| unicode:: U+000A4 .. CURRENCY SIGN +.. |darr| unicode:: U+02193 .. DOWNWARDS ARROW +.. |deg| unicode:: U+000B0 .. DEGREE SIGN +.. |divide| unicode:: U+000F7 .. DIVISION SIGN +.. |dollar| unicode:: U+00024 .. DOLLAR SIGN +.. |equals| unicode:: U+0003D .. EQUALS SIGN +.. |excl| unicode:: U+00021 .. EXCLAMATION MARK +.. |frac12| unicode:: U+000BD .. VULGAR FRACTION ONE HALF +.. |frac14| unicode:: U+000BC .. VULGAR FRACTION ONE QUARTER +.. |frac18| unicode:: U+0215B .. VULGAR FRACTION ONE EIGHTH +.. |frac34| unicode:: U+000BE .. VULGAR FRACTION THREE QUARTERS +.. |frac38| unicode:: U+0215C .. VULGAR FRACTION THREE EIGHTHS +.. |frac58| unicode:: U+0215D .. VULGAR FRACTION FIVE EIGHTHS +.. |frac78| unicode:: U+0215E .. VULGAR FRACTION SEVEN EIGHTHS +.. |gt| unicode:: U+0003E .. GREATER-THAN SIGN +.. |half| unicode:: U+000BD .. VULGAR FRACTION ONE HALF +.. |horbar| unicode:: U+02015 .. HORIZONTAL BAR +.. |hyphen| unicode:: U+02010 .. HYPHEN +.. |iexcl| unicode:: U+000A1 .. INVERTED EXCLAMATION MARK +.. |iquest| unicode:: U+000BF .. INVERTED QUESTION MARK +.. |laquo| unicode:: U+000AB .. LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +.. |larr| unicode:: U+02190 .. LEFTWARDS ARROW +.. |lcub| unicode:: U+0007B .. LEFT CURLY BRACKET +.. |ldquo| unicode:: U+0201C .. LEFT DOUBLE QUOTATION MARK +.. |lowbar| unicode:: U+0005F .. LOW LINE +.. |lpar| unicode:: U+00028 .. LEFT PARENTHESIS +.. |lsqb| unicode:: U+0005B .. LEFT SQUARE BRACKET +.. |lsquo| unicode:: U+02018 .. LEFT SINGLE QUOTATION MARK +.. |lt| unicode:: U+0003C .. LESS-THAN SIGN +.. |micro| unicode:: U+000B5 .. MICRO SIGN +.. |middot| unicode:: U+000B7 .. MIDDLE DOT +.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE +.. |not| unicode:: U+000AC .. NOT SIGN +.. |num| unicode:: U+00023 .. NUMBER SIGN +.. |ohm| unicode:: U+02126 .. OHM SIGN +.. |ordf| unicode:: U+000AA .. FEMININE ORDINAL INDICATOR +.. |ordm| unicode:: U+000BA .. MASCULINE ORDINAL INDICATOR +.. |para| unicode:: U+000B6 .. PILCROW SIGN +.. |percnt| unicode:: U+00025 .. PERCENT SIGN +.. |period| unicode:: U+0002E .. FULL STOP +.. |plus| unicode:: U+0002B .. PLUS SIGN +.. |plusmn| unicode:: U+000B1 .. PLUS-MINUS SIGN +.. |pound| unicode:: U+000A3 .. POUND SIGN +.. |quest| unicode:: U+0003F .. QUESTION MARK +.. |quot| unicode:: U+00022 .. QUOTATION MARK +.. |raquo| unicode:: U+000BB .. RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +.. |rarr| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |rcub| unicode:: U+0007D .. RIGHT CURLY BRACKET +.. |rdquo| unicode:: U+0201D .. RIGHT DOUBLE QUOTATION MARK +.. |reg| unicode:: U+000AE .. REGISTERED SIGN +.. |rpar| unicode:: U+00029 .. RIGHT PARENTHESIS +.. |rsqb| unicode:: U+0005D .. RIGHT SQUARE BRACKET +.. |rsquo| unicode:: U+02019 .. RIGHT SINGLE QUOTATION MARK +.. |sect| unicode:: U+000A7 .. SECTION SIGN +.. |semi| unicode:: U+0003B .. SEMICOLON +.. |shy| unicode:: U+000AD .. SOFT HYPHEN +.. |sol| unicode:: U+0002F .. SOLIDUS +.. |sung| unicode:: U+0266A .. EIGHTH NOTE +.. |sup1| unicode:: U+000B9 .. SUPERSCRIPT ONE +.. |sup2| unicode:: U+000B2 .. SUPERSCRIPT TWO +.. |sup3| unicode:: U+000B3 .. SUPERSCRIPT THREE +.. |times| unicode:: U+000D7 .. MULTIPLICATION SIGN +.. |trade| unicode:: U+02122 .. TRADE MARK SIGN +.. |uarr| unicode:: U+02191 .. UPWARDS ARROW +.. |verbar| unicode:: U+0007C .. VERTICAL LINE +.. |yen| unicode:: U+000A5 .. YEN SIGN diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isopub.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isopub.txt new file mode 100644 index 00000000..78e12513 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isopub.txt @@ -0,0 +1,90 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |blank| unicode:: U+02423 .. OPEN BOX +.. |blk12| unicode:: U+02592 .. MEDIUM SHADE +.. |blk14| unicode:: U+02591 .. LIGHT SHADE +.. |blk34| unicode:: U+02593 .. DARK SHADE +.. |block| unicode:: U+02588 .. FULL BLOCK +.. |bull| unicode:: U+02022 .. BULLET +.. |caret| unicode:: U+02041 .. CARET INSERTION POINT +.. |check| unicode:: U+02713 .. CHECK MARK +.. |cir| unicode:: U+025CB .. WHITE CIRCLE +.. |clubs| unicode:: U+02663 .. BLACK CLUB SUIT +.. |copysr| unicode:: U+02117 .. SOUND RECORDING COPYRIGHT +.. |cross| unicode:: U+02717 .. BALLOT X +.. |Dagger| unicode:: U+02021 .. DOUBLE DAGGER +.. |dagger| unicode:: U+02020 .. DAGGER +.. |dash| unicode:: U+02010 .. HYPHEN +.. |diams| unicode:: U+02666 .. BLACK DIAMOND SUIT +.. |dlcrop| unicode:: U+0230D .. BOTTOM LEFT CROP +.. |drcrop| unicode:: U+0230C .. BOTTOM RIGHT CROP +.. |dtri| unicode:: U+025BF .. WHITE DOWN-POINTING SMALL TRIANGLE +.. |dtrif| unicode:: U+025BE .. BLACK DOWN-POINTING SMALL TRIANGLE +.. |emsp| unicode:: U+02003 .. EM SPACE +.. |emsp13| unicode:: U+02004 .. THREE-PER-EM SPACE +.. |emsp14| unicode:: U+02005 .. FOUR-PER-EM SPACE +.. |ensp| unicode:: U+02002 .. EN SPACE +.. |female| unicode:: U+02640 .. FEMALE SIGN +.. |ffilig| unicode:: U+0FB03 .. LATIN SMALL LIGATURE FFI +.. |fflig| unicode:: U+0FB00 .. LATIN SMALL LIGATURE FF +.. |ffllig| unicode:: U+0FB04 .. LATIN SMALL LIGATURE FFL +.. |filig| unicode:: U+0FB01 .. LATIN SMALL LIGATURE FI +.. |flat| unicode:: U+0266D .. MUSIC FLAT SIGN +.. |fllig| unicode:: U+0FB02 .. LATIN SMALL LIGATURE FL +.. |frac13| unicode:: U+02153 .. VULGAR FRACTION ONE THIRD +.. |frac15| unicode:: U+02155 .. VULGAR FRACTION ONE FIFTH +.. |frac16| unicode:: U+02159 .. VULGAR FRACTION ONE SIXTH +.. |frac23| unicode:: U+02154 .. VULGAR FRACTION TWO THIRDS +.. |frac25| unicode:: U+02156 .. VULGAR FRACTION TWO FIFTHS +.. |frac35| unicode:: U+02157 .. VULGAR FRACTION THREE FIFTHS +.. |frac45| unicode:: U+02158 .. VULGAR FRACTION FOUR FIFTHS +.. |frac56| unicode:: U+0215A .. VULGAR FRACTION FIVE SIXTHS +.. |hairsp| unicode:: U+0200A .. HAIR SPACE +.. |hearts| unicode:: U+02665 .. BLACK HEART SUIT +.. |hellip| unicode:: U+02026 .. HORIZONTAL ELLIPSIS +.. |hybull| unicode:: U+02043 .. HYPHEN BULLET +.. |incare| unicode:: U+02105 .. CARE OF +.. |ldquor| unicode:: U+0201E .. DOUBLE LOW-9 QUOTATION MARK +.. |lhblk| unicode:: U+02584 .. LOWER HALF BLOCK +.. |loz| unicode:: U+025CA .. LOZENGE +.. |lozf| unicode:: U+029EB .. BLACK LOZENGE +.. |lsquor| unicode:: U+0201A .. SINGLE LOW-9 QUOTATION MARK +.. |ltri| unicode:: U+025C3 .. WHITE LEFT-POINTING SMALL TRIANGLE +.. |ltrif| unicode:: U+025C2 .. BLACK LEFT-POINTING SMALL TRIANGLE +.. |male| unicode:: U+02642 .. MALE SIGN +.. |malt| unicode:: U+02720 .. MALTESE CROSS +.. |marker| unicode:: U+025AE .. BLACK VERTICAL RECTANGLE +.. |mdash| unicode:: U+02014 .. EM DASH +.. |mldr| unicode:: U+02026 .. HORIZONTAL ELLIPSIS +.. |natur| unicode:: U+0266E .. MUSIC NATURAL SIGN +.. |ndash| unicode:: U+02013 .. EN DASH +.. |nldr| unicode:: U+02025 .. TWO DOT LEADER +.. |numsp| unicode:: U+02007 .. FIGURE SPACE +.. |phone| unicode:: U+0260E .. BLACK TELEPHONE +.. |puncsp| unicode:: U+02008 .. PUNCTUATION SPACE +.. |rdquor| unicode:: U+0201D .. RIGHT DOUBLE QUOTATION MARK +.. |rect| unicode:: U+025AD .. WHITE RECTANGLE +.. |rsquor| unicode:: U+02019 .. RIGHT SINGLE QUOTATION MARK +.. |rtri| unicode:: U+025B9 .. WHITE RIGHT-POINTING SMALL TRIANGLE +.. |rtrif| unicode:: U+025B8 .. BLACK RIGHT-POINTING SMALL TRIANGLE +.. |rx| unicode:: U+0211E .. PRESCRIPTION TAKE +.. |sext| unicode:: U+02736 .. SIX POINTED BLACK STAR +.. |sharp| unicode:: U+0266F .. MUSIC SHARP SIGN +.. |spades| unicode:: U+02660 .. BLACK SPADE SUIT +.. |squ| unicode:: U+025A1 .. WHITE SQUARE +.. |squf| unicode:: U+025AA .. BLACK SMALL SQUARE +.. |star| unicode:: U+02606 .. WHITE STAR +.. |starf| unicode:: U+02605 .. BLACK STAR +.. |target| unicode:: U+02316 .. POSITION INDICATOR +.. |telrec| unicode:: U+02315 .. TELEPHONE RECORDER +.. |thinsp| unicode:: U+02009 .. THIN SPACE +.. |uhblk| unicode:: U+02580 .. UPPER HALF BLOCK +.. |ulcrop| unicode:: U+0230F .. TOP LEFT CROP +.. |urcrop| unicode:: U+0230E .. TOP RIGHT CROP +.. |utri| unicode:: U+025B5 .. WHITE UP-POINTING SMALL TRIANGLE +.. |utrif| unicode:: U+025B4 .. BLACK UP-POINTING SMALL TRIANGLE +.. |vellip| unicode:: U+022EE .. VERTICAL ELLIPSIS diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isotech.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isotech.txt new file mode 100644 index 00000000..9b01eaad --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/isotech.txt @@ -0,0 +1,168 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |acd| unicode:: U+0223F .. SINE WAVE +.. |aleph| unicode:: U+02135 .. ALEF SYMBOL +.. |And| unicode:: U+02A53 .. DOUBLE LOGICAL AND +.. |and| unicode:: U+02227 .. LOGICAL AND +.. |andand| unicode:: U+02A55 .. TWO INTERSECTING LOGICAL AND +.. |andd| unicode:: U+02A5C .. LOGICAL AND WITH HORIZONTAL DASH +.. |andslope| unicode:: U+02A58 .. SLOPING LARGE AND +.. |andv| unicode:: U+02A5A .. LOGICAL AND WITH MIDDLE STEM +.. |ang90| unicode:: U+0221F .. RIGHT ANGLE +.. |angrt| unicode:: U+0221F .. RIGHT ANGLE +.. |angsph| unicode:: U+02222 .. SPHERICAL ANGLE +.. |angst| unicode:: U+0212B .. ANGSTROM SIGN +.. |ap| unicode:: U+02248 .. ALMOST EQUAL TO +.. |apacir| unicode:: U+02A6F .. ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT +.. |awconint| unicode:: U+02233 .. ANTICLOCKWISE CONTOUR INTEGRAL +.. |awint| unicode:: U+02A11 .. ANTICLOCKWISE INTEGRATION +.. |becaus| unicode:: U+02235 .. BECAUSE +.. |bernou| unicode:: U+0212C .. SCRIPT CAPITAL B +.. |bne| unicode:: U+0003D U+020E5 .. EQUALS SIGN with reverse slash +.. |bnequiv| unicode:: U+02261 U+020E5 .. IDENTICAL TO with reverse slash +.. |bNot| unicode:: U+02AED .. REVERSED DOUBLE STROKE NOT SIGN +.. |bnot| unicode:: U+02310 .. REVERSED NOT SIGN +.. |bottom| unicode:: U+022A5 .. UP TACK +.. |cap| unicode:: U+02229 .. INTERSECTION +.. |Cconint| unicode:: U+02230 .. VOLUME INTEGRAL +.. |cirfnint| unicode:: U+02A10 .. CIRCULATION FUNCTION +.. |compfn| unicode:: U+02218 .. RING OPERATOR +.. |cong| unicode:: U+02245 .. APPROXIMATELY EQUAL TO +.. |Conint| unicode:: U+0222F .. SURFACE INTEGRAL +.. |conint| unicode:: U+0222E .. CONTOUR INTEGRAL +.. |ctdot| unicode:: U+022EF .. MIDLINE HORIZONTAL ELLIPSIS +.. |cup| unicode:: U+0222A .. UNION +.. |cwconint| unicode:: U+02232 .. CLOCKWISE CONTOUR INTEGRAL +.. |cwint| unicode:: U+02231 .. CLOCKWISE INTEGRAL +.. |cylcty| unicode:: U+0232D .. CYLINDRICITY +.. |disin| unicode:: U+022F2 .. ELEMENT OF WITH LONG HORIZONTAL STROKE +.. |Dot| unicode:: U+000A8 .. DIAERESIS +.. |DotDot| unicode:: U+020DC .. COMBINING FOUR DOTS ABOVE +.. |dsol| unicode:: U+029F6 .. SOLIDUS WITH OVERBAR +.. |dtdot| unicode:: U+022F1 .. DOWN RIGHT DIAGONAL ELLIPSIS +.. |dwangle| unicode:: U+029A6 .. OBLIQUE ANGLE OPENING UP +.. |elinters| unicode:: U+0FFFD .. REPLACEMENT CHARACTER +.. |epar| unicode:: U+022D5 .. EQUAL AND PARALLEL TO +.. |eparsl| unicode:: U+029E3 .. EQUALS SIGN AND SLANTED PARALLEL +.. |equiv| unicode:: U+02261 .. IDENTICAL TO +.. |eqvparsl| unicode:: U+029E5 .. IDENTICAL TO AND SLANTED PARALLEL +.. |exist| unicode:: U+02203 .. THERE EXISTS +.. |fltns| unicode:: U+025B1 .. WHITE PARALLELOGRAM +.. |fnof| unicode:: U+00192 .. LATIN SMALL LETTER F WITH HOOK +.. |forall| unicode:: U+02200 .. FOR ALL +.. |fpartint| unicode:: U+02A0D .. FINITE PART INTEGRAL +.. |ge| unicode:: U+02265 .. GREATER-THAN OR EQUAL TO +.. |hamilt| unicode:: U+0210B .. SCRIPT CAPITAL H +.. |iff| unicode:: U+021D4 .. LEFT RIGHT DOUBLE ARROW +.. |iinfin| unicode:: U+029DC .. INCOMPLETE INFINITY +.. |imped| unicode:: U+001B5 .. LATIN CAPITAL LETTER Z WITH STROKE +.. |infin| unicode:: U+0221E .. INFINITY +.. |infintie| unicode:: U+029DD .. TIE OVER INFINITY +.. |Int| unicode:: U+0222C .. DOUBLE INTEGRAL +.. |int| unicode:: U+0222B .. INTEGRAL +.. |intlarhk| unicode:: U+02A17 .. INTEGRAL WITH LEFTWARDS ARROW WITH HOOK +.. |isin| unicode:: U+02208 .. ELEMENT OF +.. |isindot| unicode:: U+022F5 .. ELEMENT OF WITH DOT ABOVE +.. |isinE| unicode:: U+022F9 .. ELEMENT OF WITH TWO HORIZONTAL STROKES +.. |isins| unicode:: U+022F4 .. SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +.. |isinsv| unicode:: U+022F3 .. ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +.. |isinv| unicode:: U+02208 .. ELEMENT OF +.. |lagran| unicode:: U+02112 .. SCRIPT CAPITAL L +.. |Lang| unicode:: U+0300A .. LEFT DOUBLE ANGLE BRACKET +.. |lang| unicode:: U+02329 .. LEFT-POINTING ANGLE BRACKET +.. |lArr| unicode:: U+021D0 .. LEFTWARDS DOUBLE ARROW +.. |lbbrk| unicode:: U+03014 .. LEFT TORTOISE SHELL BRACKET +.. |le| unicode:: U+02264 .. LESS-THAN OR EQUAL TO +.. |loang| unicode:: U+03018 .. LEFT WHITE TORTOISE SHELL BRACKET +.. |lobrk| unicode:: U+0301A .. LEFT WHITE SQUARE BRACKET +.. |lopar| unicode:: U+02985 .. LEFT WHITE PARENTHESIS +.. |lowast| unicode:: U+02217 .. ASTERISK OPERATOR +.. |minus| unicode:: U+02212 .. MINUS SIGN +.. |mnplus| unicode:: U+02213 .. MINUS-OR-PLUS SIGN +.. |nabla| unicode:: U+02207 .. NABLA +.. |ne| unicode:: U+02260 .. NOT EQUAL TO +.. |nedot| unicode:: U+02250 U+00338 .. APPROACHES THE LIMIT with slash +.. |nhpar| unicode:: U+02AF2 .. PARALLEL WITH HORIZONTAL STROKE +.. |ni| unicode:: U+0220B .. CONTAINS AS MEMBER +.. |nis| unicode:: U+022FC .. SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE +.. |nisd| unicode:: U+022FA .. CONTAINS WITH LONG HORIZONTAL STROKE +.. |niv| unicode:: U+0220B .. CONTAINS AS MEMBER +.. |Not| unicode:: U+02AEC .. DOUBLE STROKE NOT SIGN +.. |notin| unicode:: U+02209 .. NOT AN ELEMENT OF +.. |notindot| unicode:: U+022F5 U+00338 .. ELEMENT OF WITH DOT ABOVE with slash +.. |notinE| unicode:: U+022F9 U+00338 .. ELEMENT OF WITH TWO HORIZONTAL STROKES with slash +.. |notinva| unicode:: U+02209 .. NOT AN ELEMENT OF +.. |notinvb| unicode:: U+022F7 .. SMALL ELEMENT OF WITH OVERBAR +.. |notinvc| unicode:: U+022F6 .. ELEMENT OF WITH OVERBAR +.. |notni| unicode:: U+0220C .. DOES NOT CONTAIN AS MEMBER +.. |notniva| unicode:: U+0220C .. DOES NOT CONTAIN AS MEMBER +.. |notnivb| unicode:: U+022FE .. SMALL CONTAINS WITH OVERBAR +.. |notnivc| unicode:: U+022FD .. CONTAINS WITH OVERBAR +.. |nparsl| unicode:: U+02AFD U+020E5 .. DOUBLE SOLIDUS OPERATOR with reverse slash +.. |npart| unicode:: U+02202 U+00338 .. PARTIAL DIFFERENTIAL with slash +.. |npolint| unicode:: U+02A14 .. LINE INTEGRATION NOT INCLUDING THE POLE +.. |nvinfin| unicode:: U+029DE .. INFINITY NEGATED WITH VERTICAL BAR +.. |olcross| unicode:: U+029BB .. CIRCLE WITH SUPERIMPOSED X +.. |Or| unicode:: U+02A54 .. DOUBLE LOGICAL OR +.. |or| unicode:: U+02228 .. LOGICAL OR +.. |ord| unicode:: U+02A5D .. LOGICAL OR WITH HORIZONTAL DASH +.. |order| unicode:: U+02134 .. SCRIPT SMALL O +.. |oror| unicode:: U+02A56 .. TWO INTERSECTING LOGICAL OR +.. |orslope| unicode:: U+02A57 .. SLOPING LARGE OR +.. |orv| unicode:: U+02A5B .. LOGICAL OR WITH MIDDLE STEM +.. |par| unicode:: U+02225 .. PARALLEL TO +.. |parsl| unicode:: U+02AFD .. DOUBLE SOLIDUS OPERATOR +.. |part| unicode:: U+02202 .. PARTIAL DIFFERENTIAL +.. |permil| unicode:: U+02030 .. PER MILLE SIGN +.. |perp| unicode:: U+022A5 .. UP TACK +.. |pertenk| unicode:: U+02031 .. PER TEN THOUSAND SIGN +.. |phmmat| unicode:: U+02133 .. SCRIPT CAPITAL M +.. |pointint| unicode:: U+02A15 .. INTEGRAL AROUND A POINT OPERATOR +.. |Prime| unicode:: U+02033 .. DOUBLE PRIME +.. |prime| unicode:: U+02032 .. PRIME +.. |profalar| unicode:: U+0232E .. ALL AROUND-PROFILE +.. |profline| unicode:: U+02312 .. ARC +.. |profsurf| unicode:: U+02313 .. SEGMENT +.. |prop| unicode:: U+0221D .. PROPORTIONAL TO +.. |qint| unicode:: U+02A0C .. QUADRUPLE INTEGRAL OPERATOR +.. |qprime| unicode:: U+02057 .. QUADRUPLE PRIME +.. |quatint| unicode:: U+02A16 .. QUATERNION INTEGRAL OPERATOR +.. |radic| unicode:: U+0221A .. SQUARE ROOT +.. |Rang| unicode:: U+0300B .. RIGHT DOUBLE ANGLE BRACKET +.. |rang| unicode:: U+0232A .. RIGHT-POINTING ANGLE BRACKET +.. |rArr| unicode:: U+021D2 .. RIGHTWARDS DOUBLE ARROW +.. |rbbrk| unicode:: U+03015 .. RIGHT TORTOISE SHELL BRACKET +.. |roang| unicode:: U+03019 .. RIGHT WHITE TORTOISE SHELL BRACKET +.. |robrk| unicode:: U+0301B .. RIGHT WHITE SQUARE BRACKET +.. |ropar| unicode:: U+02986 .. RIGHT WHITE PARENTHESIS +.. |rppolint| unicode:: U+02A12 .. LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE +.. |scpolint| unicode:: U+02A13 .. LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE +.. |sim| unicode:: U+0223C .. TILDE OPERATOR +.. |simdot| unicode:: U+02A6A .. TILDE OPERATOR WITH DOT ABOVE +.. |sime| unicode:: U+02243 .. ASYMPTOTICALLY EQUAL TO +.. |smeparsl| unicode:: U+029E4 .. EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE +.. |square| unicode:: U+025A1 .. WHITE SQUARE +.. |squarf| unicode:: U+025AA .. BLACK SMALL SQUARE +.. |strns| unicode:: U+000AF .. MACRON +.. |sub| unicode:: U+02282 .. SUBSET OF +.. |sube| unicode:: U+02286 .. SUBSET OF OR EQUAL TO +.. |sup| unicode:: U+02283 .. SUPERSET OF +.. |supe| unicode:: U+02287 .. SUPERSET OF OR EQUAL TO +.. |tdot| unicode:: U+020DB .. COMBINING THREE DOTS ABOVE +.. |there4| unicode:: U+02234 .. THEREFORE +.. |tint| unicode:: U+0222D .. TRIPLE INTEGRAL +.. |top| unicode:: U+022A4 .. DOWN TACK +.. |topbot| unicode:: U+02336 .. APL FUNCTIONAL SYMBOL I-BEAM +.. |topcir| unicode:: U+02AF1 .. DOWN TACK WITH CIRCLE BELOW +.. |tprime| unicode:: U+02034 .. TRIPLE PRIME +.. |utdot| unicode:: U+022F0 .. UP RIGHT DIAGONAL ELLIPSIS +.. |uwangle| unicode:: U+029A7 .. OBLIQUE ANGLE OPENING DOWN +.. |vangrt| unicode:: U+0299C .. RIGHT ANGLE VARIANT WITH SQUARE +.. |veeeq| unicode:: U+0225A .. EQUIANGULAR TO +.. |Verbar| unicode:: U+02016 .. DOUBLE VERTICAL LINE +.. |wedgeq| unicode:: U+02259 .. ESTIMATES +.. |xnis| unicode:: U+022FB .. CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlalias.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlalias.txt new file mode 100644 index 00000000..49c23ca7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlalias.txt @@ -0,0 +1,554 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |angle| unicode:: U+02220 .. ANGLE +.. |ApplyFunction| unicode:: U+02061 .. FUNCTION APPLICATION +.. |approx| unicode:: U+02248 .. ALMOST EQUAL TO +.. |approxeq| unicode:: U+0224A .. ALMOST EQUAL OR EQUAL TO +.. |Assign| unicode:: U+02254 .. COLON EQUALS +.. |backcong| unicode:: U+0224C .. ALL EQUAL TO +.. |backepsilon| unicode:: U+003F6 .. GREEK REVERSED LUNATE EPSILON SYMBOL +.. |backprime| unicode:: U+02035 .. REVERSED PRIME +.. |backsim| unicode:: U+0223D .. REVERSED TILDE +.. |backsimeq| unicode:: U+022CD .. REVERSED TILDE EQUALS +.. |Backslash| unicode:: U+02216 .. SET MINUS +.. |barwedge| unicode:: U+02305 .. PROJECTIVE +.. |Because| unicode:: U+02235 .. BECAUSE +.. |because| unicode:: U+02235 .. BECAUSE +.. |Bernoullis| unicode:: U+0212C .. SCRIPT CAPITAL B +.. |between| unicode:: U+0226C .. BETWEEN +.. |bigcap| unicode:: U+022C2 .. N-ARY INTERSECTION +.. |bigcirc| unicode:: U+025EF .. LARGE CIRCLE +.. |bigcup| unicode:: U+022C3 .. N-ARY UNION +.. |bigodot| unicode:: U+02A00 .. N-ARY CIRCLED DOT OPERATOR +.. |bigoplus| unicode:: U+02A01 .. N-ARY CIRCLED PLUS OPERATOR +.. |bigotimes| unicode:: U+02A02 .. N-ARY CIRCLED TIMES OPERATOR +.. |bigsqcup| unicode:: U+02A06 .. N-ARY SQUARE UNION OPERATOR +.. |bigstar| unicode:: U+02605 .. BLACK STAR +.. |bigtriangledown| unicode:: U+025BD .. WHITE DOWN-POINTING TRIANGLE +.. |bigtriangleup| unicode:: U+025B3 .. WHITE UP-POINTING TRIANGLE +.. |biguplus| unicode:: U+02A04 .. N-ARY UNION OPERATOR WITH PLUS +.. |bigvee| unicode:: U+022C1 .. N-ARY LOGICAL OR +.. |bigwedge| unicode:: U+022C0 .. N-ARY LOGICAL AND +.. |bkarow| unicode:: U+0290D .. RIGHTWARDS DOUBLE DASH ARROW +.. |blacklozenge| unicode:: U+029EB .. BLACK LOZENGE +.. |blacksquare| unicode:: U+025AA .. BLACK SMALL SQUARE +.. |blacktriangle| unicode:: U+025B4 .. BLACK UP-POINTING SMALL TRIANGLE +.. |blacktriangledown| unicode:: U+025BE .. BLACK DOWN-POINTING SMALL TRIANGLE +.. |blacktriangleleft| unicode:: U+025C2 .. BLACK LEFT-POINTING SMALL TRIANGLE +.. |blacktriangleright| unicode:: U+025B8 .. BLACK RIGHT-POINTING SMALL TRIANGLE +.. |bot| unicode:: U+022A5 .. UP TACK +.. |boxminus| unicode:: U+0229F .. SQUARED MINUS +.. |boxplus| unicode:: U+0229E .. SQUARED PLUS +.. |boxtimes| unicode:: U+022A0 .. SQUARED TIMES +.. |Breve| unicode:: U+002D8 .. BREVE +.. |bullet| unicode:: U+02022 .. BULLET +.. |Bumpeq| unicode:: U+0224E .. GEOMETRICALLY EQUIVALENT TO +.. |bumpeq| unicode:: U+0224F .. DIFFERENCE BETWEEN +.. |CapitalDifferentialD| unicode:: U+02145 .. DOUBLE-STRUCK ITALIC CAPITAL D +.. |Cayleys| unicode:: U+0212D .. BLACK-LETTER CAPITAL C +.. |Cedilla| unicode:: U+000B8 .. CEDILLA +.. |CenterDot| unicode:: U+000B7 .. MIDDLE DOT +.. |centerdot| unicode:: U+000B7 .. MIDDLE DOT +.. |checkmark| unicode:: U+02713 .. CHECK MARK +.. |circeq| unicode:: U+02257 .. RING EQUAL TO +.. |circlearrowleft| unicode:: U+021BA .. ANTICLOCKWISE OPEN CIRCLE ARROW +.. |circlearrowright| unicode:: U+021BB .. CLOCKWISE OPEN CIRCLE ARROW +.. |circledast| unicode:: U+0229B .. CIRCLED ASTERISK OPERATOR +.. |circledcirc| unicode:: U+0229A .. CIRCLED RING OPERATOR +.. |circleddash| unicode:: U+0229D .. CIRCLED DASH +.. |CircleDot| unicode:: U+02299 .. CIRCLED DOT OPERATOR +.. |circledR| unicode:: U+000AE .. REGISTERED SIGN +.. |circledS| unicode:: U+024C8 .. CIRCLED LATIN CAPITAL LETTER S +.. |CircleMinus| unicode:: U+02296 .. CIRCLED MINUS +.. |CirclePlus| unicode:: U+02295 .. CIRCLED PLUS +.. |CircleTimes| unicode:: U+02297 .. CIRCLED TIMES +.. |ClockwiseContourIntegral| unicode:: U+02232 .. CLOCKWISE CONTOUR INTEGRAL +.. |CloseCurlyDoubleQuote| unicode:: U+0201D .. RIGHT DOUBLE QUOTATION MARK +.. |CloseCurlyQuote| unicode:: U+02019 .. RIGHT SINGLE QUOTATION MARK +.. |clubsuit| unicode:: U+02663 .. BLACK CLUB SUIT +.. |coloneq| unicode:: U+02254 .. COLON EQUALS +.. |complement| unicode:: U+02201 .. COMPLEMENT +.. |complexes| unicode:: U+02102 .. DOUBLE-STRUCK CAPITAL C +.. |Congruent| unicode:: U+02261 .. IDENTICAL TO +.. |ContourIntegral| unicode:: U+0222E .. CONTOUR INTEGRAL +.. |Coproduct| unicode:: U+02210 .. N-ARY COPRODUCT +.. |CounterClockwiseContourIntegral| unicode:: U+02233 .. ANTICLOCKWISE CONTOUR INTEGRAL +.. |CupCap| unicode:: U+0224D .. EQUIVALENT TO +.. |curlyeqprec| unicode:: U+022DE .. EQUAL TO OR PRECEDES +.. |curlyeqsucc| unicode:: U+022DF .. EQUAL TO OR SUCCEEDS +.. |curlyvee| unicode:: U+022CE .. CURLY LOGICAL OR +.. |curlywedge| unicode:: U+022CF .. CURLY LOGICAL AND +.. |curvearrowleft| unicode:: U+021B6 .. ANTICLOCKWISE TOP SEMICIRCLE ARROW +.. |curvearrowright| unicode:: U+021B7 .. CLOCKWISE TOP SEMICIRCLE ARROW +.. |dbkarow| unicode:: U+0290F .. RIGHTWARDS TRIPLE DASH ARROW +.. |ddagger| unicode:: U+02021 .. DOUBLE DAGGER +.. |ddotseq| unicode:: U+02A77 .. EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW +.. |Del| unicode:: U+02207 .. NABLA +.. |DiacriticalAcute| unicode:: U+000B4 .. ACUTE ACCENT +.. |DiacriticalDot| unicode:: U+002D9 .. DOT ABOVE +.. |DiacriticalDoubleAcute| unicode:: U+002DD .. DOUBLE ACUTE ACCENT +.. |DiacriticalGrave| unicode:: U+00060 .. GRAVE ACCENT +.. |DiacriticalTilde| unicode:: U+002DC .. SMALL TILDE +.. |Diamond| unicode:: U+022C4 .. DIAMOND OPERATOR +.. |diamond| unicode:: U+022C4 .. DIAMOND OPERATOR +.. |diamondsuit| unicode:: U+02666 .. BLACK DIAMOND SUIT +.. |DifferentialD| unicode:: U+02146 .. DOUBLE-STRUCK ITALIC SMALL D +.. |digamma| unicode:: U+003DD .. GREEK SMALL LETTER DIGAMMA +.. |div| unicode:: U+000F7 .. DIVISION SIGN +.. |divideontimes| unicode:: U+022C7 .. DIVISION TIMES +.. |doteq| unicode:: U+02250 .. APPROACHES THE LIMIT +.. |doteqdot| unicode:: U+02251 .. GEOMETRICALLY EQUAL TO +.. |DotEqual| unicode:: U+02250 .. APPROACHES THE LIMIT +.. |dotminus| unicode:: U+02238 .. DOT MINUS +.. |dotplus| unicode:: U+02214 .. DOT PLUS +.. |dotsquare| unicode:: U+022A1 .. SQUARED DOT OPERATOR +.. |doublebarwedge| unicode:: U+02306 .. PERSPECTIVE +.. |DoubleContourIntegral| unicode:: U+0222F .. SURFACE INTEGRAL +.. |DoubleDot| unicode:: U+000A8 .. DIAERESIS +.. |DoubleDownArrow| unicode:: U+021D3 .. DOWNWARDS DOUBLE ARROW +.. |DoubleLeftArrow| unicode:: U+021D0 .. LEFTWARDS DOUBLE ARROW +.. |DoubleLeftRightArrow| unicode:: U+021D4 .. LEFT RIGHT DOUBLE ARROW +.. |DoubleLeftTee| unicode:: U+02AE4 .. VERTICAL BAR DOUBLE LEFT TURNSTILE +.. |DoubleLongLeftArrow| unicode:: U+027F8 .. LONG LEFTWARDS DOUBLE ARROW +.. |DoubleLongLeftRightArrow| unicode:: U+027FA .. LONG LEFT RIGHT DOUBLE ARROW +.. |DoubleLongRightArrow| unicode:: U+027F9 .. LONG RIGHTWARDS DOUBLE ARROW +.. |DoubleRightArrow| unicode:: U+021D2 .. RIGHTWARDS DOUBLE ARROW +.. |DoubleRightTee| unicode:: U+022A8 .. TRUE +.. |DoubleUpArrow| unicode:: U+021D1 .. UPWARDS DOUBLE ARROW +.. |DoubleUpDownArrow| unicode:: U+021D5 .. UP DOWN DOUBLE ARROW +.. |DoubleVerticalBar| unicode:: U+02225 .. PARALLEL TO +.. |DownArrow| unicode:: U+02193 .. DOWNWARDS ARROW +.. |Downarrow| unicode:: U+021D3 .. DOWNWARDS DOUBLE ARROW +.. |downarrow| unicode:: U+02193 .. DOWNWARDS ARROW +.. |DownArrowUpArrow| unicode:: U+021F5 .. DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW +.. |downdownarrows| unicode:: U+021CA .. DOWNWARDS PAIRED ARROWS +.. |downharpoonleft| unicode:: U+021C3 .. DOWNWARDS HARPOON WITH BARB LEFTWARDS +.. |downharpoonright| unicode:: U+021C2 .. DOWNWARDS HARPOON WITH BARB RIGHTWARDS +.. |DownLeftVector| unicode:: U+021BD .. LEFTWARDS HARPOON WITH BARB DOWNWARDS +.. |DownRightVector| unicode:: U+021C1 .. RIGHTWARDS HARPOON WITH BARB DOWNWARDS +.. |DownTee| unicode:: U+022A4 .. DOWN TACK +.. |DownTeeArrow| unicode:: U+021A7 .. DOWNWARDS ARROW FROM BAR +.. |drbkarow| unicode:: U+02910 .. RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW +.. |Element| unicode:: U+02208 .. ELEMENT OF +.. |emptyset| unicode:: U+02205 .. EMPTY SET +.. |eqcirc| unicode:: U+02256 .. RING IN EQUAL TO +.. |eqcolon| unicode:: U+02255 .. EQUALS COLON +.. |eqsim| unicode:: U+02242 .. MINUS TILDE +.. |eqslantgtr| unicode:: U+02A96 .. SLANTED EQUAL TO OR GREATER-THAN +.. |eqslantless| unicode:: U+02A95 .. SLANTED EQUAL TO OR LESS-THAN +.. |EqualTilde| unicode:: U+02242 .. MINUS TILDE +.. |Equilibrium| unicode:: U+021CC .. RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON +.. |Exists| unicode:: U+02203 .. THERE EXISTS +.. |expectation| unicode:: U+02130 .. SCRIPT CAPITAL E +.. |ExponentialE| unicode:: U+02147 .. DOUBLE-STRUCK ITALIC SMALL E +.. |exponentiale| unicode:: U+02147 .. DOUBLE-STRUCK ITALIC SMALL E +.. |fallingdotseq| unicode:: U+02252 .. APPROXIMATELY EQUAL TO OR THE IMAGE OF +.. |ForAll| unicode:: U+02200 .. FOR ALL +.. |Fouriertrf| unicode:: U+02131 .. SCRIPT CAPITAL F +.. |geq| unicode:: U+02265 .. GREATER-THAN OR EQUAL TO +.. |geqq| unicode:: U+02267 .. GREATER-THAN OVER EQUAL TO +.. |geqslant| unicode:: U+02A7E .. GREATER-THAN OR SLANTED EQUAL TO +.. |gg| unicode:: U+0226B .. MUCH GREATER-THAN +.. |ggg| unicode:: U+022D9 .. VERY MUCH GREATER-THAN +.. |gnapprox| unicode:: U+02A8A .. GREATER-THAN AND NOT APPROXIMATE +.. |gneq| unicode:: U+02A88 .. GREATER-THAN AND SINGLE-LINE NOT EQUAL TO +.. |gneqq| unicode:: U+02269 .. GREATER-THAN BUT NOT EQUAL TO +.. |GreaterEqual| unicode:: U+02265 .. GREATER-THAN OR EQUAL TO +.. |GreaterEqualLess| unicode:: U+022DB .. GREATER-THAN EQUAL TO OR LESS-THAN +.. |GreaterFullEqual| unicode:: U+02267 .. GREATER-THAN OVER EQUAL TO +.. |GreaterLess| unicode:: U+02277 .. GREATER-THAN OR LESS-THAN +.. |GreaterSlantEqual| unicode:: U+02A7E .. GREATER-THAN OR SLANTED EQUAL TO +.. |GreaterTilde| unicode:: U+02273 .. GREATER-THAN OR EQUIVALENT TO +.. |gtrapprox| unicode:: U+02A86 .. GREATER-THAN OR APPROXIMATE +.. |gtrdot| unicode:: U+022D7 .. GREATER-THAN WITH DOT +.. |gtreqless| unicode:: U+022DB .. GREATER-THAN EQUAL TO OR LESS-THAN +.. |gtreqqless| unicode:: U+02A8C .. GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN +.. |gtrless| unicode:: U+02277 .. GREATER-THAN OR LESS-THAN +.. |gtrsim| unicode:: U+02273 .. GREATER-THAN OR EQUIVALENT TO +.. |gvertneqq| unicode:: U+02269 U+0FE00 .. GREATER-THAN BUT NOT EQUAL TO - with vertical stroke +.. |Hacek| unicode:: U+002C7 .. CARON +.. |hbar| unicode:: U+0210F .. PLANCK CONSTANT OVER TWO PI +.. |heartsuit| unicode:: U+02665 .. BLACK HEART SUIT +.. |HilbertSpace| unicode:: U+0210B .. SCRIPT CAPITAL H +.. |hksearow| unicode:: U+02925 .. SOUTH EAST ARROW WITH HOOK +.. |hkswarow| unicode:: U+02926 .. SOUTH WEST ARROW WITH HOOK +.. |hookleftarrow| unicode:: U+021A9 .. LEFTWARDS ARROW WITH HOOK +.. |hookrightarrow| unicode:: U+021AA .. RIGHTWARDS ARROW WITH HOOK +.. |hslash| unicode:: U+0210F .. PLANCK CONSTANT OVER TWO PI +.. |HumpDownHump| unicode:: U+0224E .. GEOMETRICALLY EQUIVALENT TO +.. |HumpEqual| unicode:: U+0224F .. DIFFERENCE BETWEEN +.. |iiiint| unicode:: U+02A0C .. QUADRUPLE INTEGRAL OPERATOR +.. |iiint| unicode:: U+0222D .. TRIPLE INTEGRAL +.. |Im| unicode:: U+02111 .. BLACK-LETTER CAPITAL I +.. |ImaginaryI| unicode:: U+02148 .. DOUBLE-STRUCK ITALIC SMALL I +.. |imagline| unicode:: U+02110 .. SCRIPT CAPITAL I +.. |imagpart| unicode:: U+02111 .. BLACK-LETTER CAPITAL I +.. |Implies| unicode:: U+021D2 .. RIGHTWARDS DOUBLE ARROW +.. |in| unicode:: U+02208 .. ELEMENT OF +.. |integers| unicode:: U+02124 .. DOUBLE-STRUCK CAPITAL Z +.. |Integral| unicode:: U+0222B .. INTEGRAL +.. |intercal| unicode:: U+022BA .. INTERCALATE +.. |Intersection| unicode:: U+022C2 .. N-ARY INTERSECTION +.. |intprod| unicode:: U+02A3C .. INTERIOR PRODUCT +.. |InvisibleComma| unicode:: U+02063 .. INVISIBLE SEPARATOR +.. |InvisibleTimes| unicode:: U+02062 .. INVISIBLE TIMES +.. |langle| unicode:: U+02329 .. LEFT-POINTING ANGLE BRACKET +.. |Laplacetrf| unicode:: U+02112 .. SCRIPT CAPITAL L +.. |lbrace| unicode:: U+0007B .. LEFT CURLY BRACKET +.. |lbrack| unicode:: U+0005B .. LEFT SQUARE BRACKET +.. |LeftAngleBracket| unicode:: U+02329 .. LEFT-POINTING ANGLE BRACKET +.. |LeftArrow| unicode:: U+02190 .. LEFTWARDS ARROW +.. |Leftarrow| unicode:: U+021D0 .. LEFTWARDS DOUBLE ARROW +.. |leftarrow| unicode:: U+02190 .. LEFTWARDS ARROW +.. |LeftArrowBar| unicode:: U+021E4 .. LEFTWARDS ARROW TO BAR +.. |LeftArrowRightArrow| unicode:: U+021C6 .. LEFTWARDS ARROW OVER RIGHTWARDS ARROW +.. |leftarrowtail| unicode:: U+021A2 .. LEFTWARDS ARROW WITH TAIL +.. |LeftCeiling| unicode:: U+02308 .. LEFT CEILING +.. |LeftDoubleBracket| unicode:: U+0301A .. LEFT WHITE SQUARE BRACKET +.. |LeftDownVector| unicode:: U+021C3 .. DOWNWARDS HARPOON WITH BARB LEFTWARDS +.. |LeftFloor| unicode:: U+0230A .. LEFT FLOOR +.. |leftharpoondown| unicode:: U+021BD .. LEFTWARDS HARPOON WITH BARB DOWNWARDS +.. |leftharpoonup| unicode:: U+021BC .. LEFTWARDS HARPOON WITH BARB UPWARDS +.. |leftleftarrows| unicode:: U+021C7 .. LEFTWARDS PAIRED ARROWS +.. |LeftRightArrow| unicode:: U+02194 .. LEFT RIGHT ARROW +.. |Leftrightarrow| unicode:: U+021D4 .. LEFT RIGHT DOUBLE ARROW +.. |leftrightarrow| unicode:: U+02194 .. LEFT RIGHT ARROW +.. |leftrightarrows| unicode:: U+021C6 .. LEFTWARDS ARROW OVER RIGHTWARDS ARROW +.. |leftrightharpoons| unicode:: U+021CB .. LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON +.. |leftrightsquigarrow| unicode:: U+021AD .. LEFT RIGHT WAVE ARROW +.. |LeftTee| unicode:: U+022A3 .. LEFT TACK +.. |LeftTeeArrow| unicode:: U+021A4 .. LEFTWARDS ARROW FROM BAR +.. |leftthreetimes| unicode:: U+022CB .. LEFT SEMIDIRECT PRODUCT +.. |LeftTriangle| unicode:: U+022B2 .. NORMAL SUBGROUP OF +.. |LeftTriangleEqual| unicode:: U+022B4 .. NORMAL SUBGROUP OF OR EQUAL TO +.. |LeftUpVector| unicode:: U+021BF .. UPWARDS HARPOON WITH BARB LEFTWARDS +.. |LeftVector| unicode:: U+021BC .. LEFTWARDS HARPOON WITH BARB UPWARDS +.. |leq| unicode:: U+02264 .. LESS-THAN OR EQUAL TO +.. |leqq| unicode:: U+02266 .. LESS-THAN OVER EQUAL TO +.. |leqslant| unicode:: U+02A7D .. LESS-THAN OR SLANTED EQUAL TO +.. |lessapprox| unicode:: U+02A85 .. LESS-THAN OR APPROXIMATE +.. |lessdot| unicode:: U+022D6 .. LESS-THAN WITH DOT +.. |lesseqgtr| unicode:: U+022DA .. LESS-THAN EQUAL TO OR GREATER-THAN +.. |lesseqqgtr| unicode:: U+02A8B .. LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN +.. |LessEqualGreater| unicode:: U+022DA .. LESS-THAN EQUAL TO OR GREATER-THAN +.. |LessFullEqual| unicode:: U+02266 .. LESS-THAN OVER EQUAL TO +.. |LessGreater| unicode:: U+02276 .. LESS-THAN OR GREATER-THAN +.. |lessgtr| unicode:: U+02276 .. LESS-THAN OR GREATER-THAN +.. |lesssim| unicode:: U+02272 .. LESS-THAN OR EQUIVALENT TO +.. |LessSlantEqual| unicode:: U+02A7D .. LESS-THAN OR SLANTED EQUAL TO +.. |LessTilde| unicode:: U+02272 .. LESS-THAN OR EQUIVALENT TO +.. |ll| unicode:: U+0226A .. MUCH LESS-THAN +.. |llcorner| unicode:: U+0231E .. BOTTOM LEFT CORNER +.. |Lleftarrow| unicode:: U+021DA .. LEFTWARDS TRIPLE ARROW +.. |lmoustache| unicode:: U+023B0 .. UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION +.. |lnapprox| unicode:: U+02A89 .. LESS-THAN AND NOT APPROXIMATE +.. |lneq| unicode:: U+02A87 .. LESS-THAN AND SINGLE-LINE NOT EQUAL TO +.. |lneqq| unicode:: U+02268 .. LESS-THAN BUT NOT EQUAL TO +.. |LongLeftArrow| unicode:: U+027F5 .. LONG LEFTWARDS ARROW +.. |Longleftarrow| unicode:: U+027F8 .. LONG LEFTWARDS DOUBLE ARROW +.. |longleftarrow| unicode:: U+027F5 .. LONG LEFTWARDS ARROW +.. |LongLeftRightArrow| unicode:: U+027F7 .. LONG LEFT RIGHT ARROW +.. |Longleftrightarrow| unicode:: U+027FA .. LONG LEFT RIGHT DOUBLE ARROW +.. |longleftrightarrow| unicode:: U+027F7 .. LONG LEFT RIGHT ARROW +.. |longmapsto| unicode:: U+027FC .. LONG RIGHTWARDS ARROW FROM BAR +.. |LongRightArrow| unicode:: U+027F6 .. LONG RIGHTWARDS ARROW +.. |Longrightarrow| unicode:: U+027F9 .. LONG RIGHTWARDS DOUBLE ARROW +.. |longrightarrow| unicode:: U+027F6 .. LONG RIGHTWARDS ARROW +.. |looparrowleft| unicode:: U+021AB .. LEFTWARDS ARROW WITH LOOP +.. |looparrowright| unicode:: U+021AC .. RIGHTWARDS ARROW WITH LOOP +.. |LowerLeftArrow| unicode:: U+02199 .. SOUTH WEST ARROW +.. |LowerRightArrow| unicode:: U+02198 .. SOUTH EAST ARROW +.. |lozenge| unicode:: U+025CA .. LOZENGE +.. |lrcorner| unicode:: U+0231F .. BOTTOM RIGHT CORNER +.. |Lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS +.. |lvertneqq| unicode:: U+02268 U+0FE00 .. LESS-THAN BUT NOT EQUAL TO - with vertical stroke +.. |maltese| unicode:: U+02720 .. MALTESE CROSS +.. |mapsto| unicode:: U+021A6 .. RIGHTWARDS ARROW FROM BAR +.. |measuredangle| unicode:: U+02221 .. MEASURED ANGLE +.. |Mellintrf| unicode:: U+02133 .. SCRIPT CAPITAL M +.. |MinusPlus| unicode:: U+02213 .. MINUS-OR-PLUS SIGN +.. |mp| unicode:: U+02213 .. MINUS-OR-PLUS SIGN +.. |multimap| unicode:: U+022B8 .. MULTIMAP +.. |napprox| unicode:: U+02249 .. NOT ALMOST EQUAL TO +.. |natural| unicode:: U+0266E .. MUSIC NATURAL SIGN +.. |naturals| unicode:: U+02115 .. DOUBLE-STRUCK CAPITAL N +.. |nearrow| unicode:: U+02197 .. NORTH EAST ARROW +.. |NegativeMediumSpace| unicode:: U+0200B .. ZERO WIDTH SPACE +.. |NegativeThickSpace| unicode:: U+0200B .. ZERO WIDTH SPACE +.. |NegativeThinSpace| unicode:: U+0200B .. ZERO WIDTH SPACE +.. |NegativeVeryThinSpace| unicode:: U+0200B .. ZERO WIDTH SPACE +.. |NestedGreaterGreater| unicode:: U+0226B .. MUCH GREATER-THAN +.. |NestedLessLess| unicode:: U+0226A .. MUCH LESS-THAN +.. |nexists| unicode:: U+02204 .. THERE DOES NOT EXIST +.. |ngeq| unicode:: U+02271 .. NEITHER GREATER-THAN NOR EQUAL TO +.. |ngeqq| unicode:: U+02267 U+00338 .. GREATER-THAN OVER EQUAL TO with slash +.. |ngeqslant| unicode:: U+02A7E U+00338 .. GREATER-THAN OR SLANTED EQUAL TO with slash +.. |ngtr| unicode:: U+0226F .. NOT GREATER-THAN +.. |nLeftarrow| unicode:: U+021CD .. LEFTWARDS DOUBLE ARROW WITH STROKE +.. |nleftarrow| unicode:: U+0219A .. LEFTWARDS ARROW WITH STROKE +.. |nLeftrightarrow| unicode:: U+021CE .. LEFT RIGHT DOUBLE ARROW WITH STROKE +.. |nleftrightarrow| unicode:: U+021AE .. LEFT RIGHT ARROW WITH STROKE +.. |nleq| unicode:: U+02270 .. NEITHER LESS-THAN NOR EQUAL TO +.. |nleqq| unicode:: U+02266 U+00338 .. LESS-THAN OVER EQUAL TO with slash +.. |nleqslant| unicode:: U+02A7D U+00338 .. LESS-THAN OR SLANTED EQUAL TO with slash +.. |nless| unicode:: U+0226E .. NOT LESS-THAN +.. |NonBreakingSpace| unicode:: U+000A0 .. NO-BREAK SPACE +.. |NotCongruent| unicode:: U+02262 .. NOT IDENTICAL TO +.. |NotDoubleVerticalBar| unicode:: U+02226 .. NOT PARALLEL TO +.. |NotElement| unicode:: U+02209 .. NOT AN ELEMENT OF +.. |NotEqual| unicode:: U+02260 .. NOT EQUAL TO +.. |NotEqualTilde| unicode:: U+02242 U+00338 .. MINUS TILDE with slash +.. |NotExists| unicode:: U+02204 .. THERE DOES NOT EXIST +.. |NotGreater| unicode:: U+0226F .. NOT GREATER-THAN +.. |NotGreaterEqual| unicode:: U+02271 .. NEITHER GREATER-THAN NOR EQUAL TO +.. |NotGreaterFullEqual| unicode:: U+02266 U+00338 .. LESS-THAN OVER EQUAL TO with slash +.. |NotGreaterGreater| unicode:: U+0226B U+00338 .. MUCH GREATER THAN with slash +.. |NotGreaterLess| unicode:: U+02279 .. NEITHER GREATER-THAN NOR LESS-THAN +.. |NotGreaterSlantEqual| unicode:: U+02A7E U+00338 .. GREATER-THAN OR SLANTED EQUAL TO with slash +.. |NotGreaterTilde| unicode:: U+02275 .. NEITHER GREATER-THAN NOR EQUIVALENT TO +.. |NotHumpDownHump| unicode:: U+0224E U+00338 .. GEOMETRICALLY EQUIVALENT TO with slash +.. |NotLeftTriangle| unicode:: U+022EA .. NOT NORMAL SUBGROUP OF +.. |NotLeftTriangleEqual| unicode:: U+022EC .. NOT NORMAL SUBGROUP OF OR EQUAL TO +.. |NotLess| unicode:: U+0226E .. NOT LESS-THAN +.. |NotLessEqual| unicode:: U+02270 .. NEITHER LESS-THAN NOR EQUAL TO +.. |NotLessGreater| unicode:: U+02278 .. NEITHER LESS-THAN NOR GREATER-THAN +.. |NotLessLess| unicode:: U+0226A U+00338 .. MUCH LESS THAN with slash +.. |NotLessSlantEqual| unicode:: U+02A7D U+00338 .. LESS-THAN OR SLANTED EQUAL TO with slash +.. |NotLessTilde| unicode:: U+02274 .. NEITHER LESS-THAN NOR EQUIVALENT TO +.. |NotPrecedes| unicode:: U+02280 .. DOES NOT PRECEDE +.. |NotPrecedesEqual| unicode:: U+02AAF U+00338 .. PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash +.. |NotPrecedesSlantEqual| unicode:: U+022E0 .. DOES NOT PRECEDE OR EQUAL +.. |NotReverseElement| unicode:: U+0220C .. DOES NOT CONTAIN AS MEMBER +.. |NotRightTriangle| unicode:: U+022EB .. DOES NOT CONTAIN AS NORMAL SUBGROUP +.. |NotRightTriangleEqual| unicode:: U+022ED .. DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL +.. |NotSquareSubsetEqual| unicode:: U+022E2 .. NOT SQUARE IMAGE OF OR EQUAL TO +.. |NotSquareSupersetEqual| unicode:: U+022E3 .. NOT SQUARE ORIGINAL OF OR EQUAL TO +.. |NotSubset| unicode:: U+02282 U+020D2 .. SUBSET OF with vertical line +.. |NotSubsetEqual| unicode:: U+02288 .. NEITHER A SUBSET OF NOR EQUAL TO +.. |NotSucceeds| unicode:: U+02281 .. DOES NOT SUCCEED +.. |NotSucceedsEqual| unicode:: U+02AB0 U+00338 .. SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash +.. |NotSucceedsSlantEqual| unicode:: U+022E1 .. DOES NOT SUCCEED OR EQUAL +.. |NotSuperset| unicode:: U+02283 U+020D2 .. SUPERSET OF with vertical line +.. |NotSupersetEqual| unicode:: U+02289 .. NEITHER A SUPERSET OF NOR EQUAL TO +.. |NotTilde| unicode:: U+02241 .. NOT TILDE +.. |NotTildeEqual| unicode:: U+02244 .. NOT ASYMPTOTICALLY EQUAL TO +.. |NotTildeFullEqual| unicode:: U+02247 .. NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO +.. |NotTildeTilde| unicode:: U+02249 .. NOT ALMOST EQUAL TO +.. |NotVerticalBar| unicode:: U+02224 .. DOES NOT DIVIDE +.. |nparallel| unicode:: U+02226 .. NOT PARALLEL TO +.. |nprec| unicode:: U+02280 .. DOES NOT PRECEDE +.. |npreceq| unicode:: U+02AAF U+00338 .. PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash +.. |nRightarrow| unicode:: U+021CF .. RIGHTWARDS DOUBLE ARROW WITH STROKE +.. |nrightarrow| unicode:: U+0219B .. RIGHTWARDS ARROW WITH STROKE +.. |nshortmid| unicode:: U+02224 .. DOES NOT DIVIDE +.. |nshortparallel| unicode:: U+02226 .. NOT PARALLEL TO +.. |nsimeq| unicode:: U+02244 .. NOT ASYMPTOTICALLY EQUAL TO +.. |nsubset| unicode:: U+02282 U+020D2 .. SUBSET OF with vertical line +.. |nsubseteq| unicode:: U+02288 .. NEITHER A SUBSET OF NOR EQUAL TO +.. |nsubseteqq| unicode:: U+02AC5 U+00338 .. SUBSET OF ABOVE EQUALS SIGN with slash +.. |nsucc| unicode:: U+02281 .. DOES NOT SUCCEED +.. |nsucceq| unicode:: U+02AB0 U+00338 .. SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash +.. |nsupset| unicode:: U+02283 U+020D2 .. SUPERSET OF with vertical line +.. |nsupseteq| unicode:: U+02289 .. NEITHER A SUPERSET OF NOR EQUAL TO +.. |nsupseteqq| unicode:: U+02AC6 U+00338 .. SUPERSET OF ABOVE EQUALS SIGN with slash +.. |ntriangleleft| unicode:: U+022EA .. NOT NORMAL SUBGROUP OF +.. |ntrianglelefteq| unicode:: U+022EC .. NOT NORMAL SUBGROUP OF OR EQUAL TO +.. |ntriangleright| unicode:: U+022EB .. DOES NOT CONTAIN AS NORMAL SUBGROUP +.. |ntrianglerighteq| unicode:: U+022ED .. DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL +.. |nwarrow| unicode:: U+02196 .. NORTH WEST ARROW +.. |oint| unicode:: U+0222E .. CONTOUR INTEGRAL +.. |OpenCurlyDoubleQuote| unicode:: U+0201C .. LEFT DOUBLE QUOTATION MARK +.. |OpenCurlyQuote| unicode:: U+02018 .. LEFT SINGLE QUOTATION MARK +.. |orderof| unicode:: U+02134 .. SCRIPT SMALL O +.. |parallel| unicode:: U+02225 .. PARALLEL TO +.. |PartialD| unicode:: U+02202 .. PARTIAL DIFFERENTIAL +.. |pitchfork| unicode:: U+022D4 .. PITCHFORK +.. |PlusMinus| unicode:: U+000B1 .. PLUS-MINUS SIGN +.. |pm| unicode:: U+000B1 .. PLUS-MINUS SIGN +.. |Poincareplane| unicode:: U+0210C .. BLACK-LETTER CAPITAL H +.. |prec| unicode:: U+0227A .. PRECEDES +.. |precapprox| unicode:: U+02AB7 .. PRECEDES ABOVE ALMOST EQUAL TO +.. |preccurlyeq| unicode:: U+0227C .. PRECEDES OR EQUAL TO +.. |Precedes| unicode:: U+0227A .. PRECEDES +.. |PrecedesEqual| unicode:: U+02AAF .. PRECEDES ABOVE SINGLE-LINE EQUALS SIGN +.. |PrecedesSlantEqual| unicode:: U+0227C .. PRECEDES OR EQUAL TO +.. |PrecedesTilde| unicode:: U+0227E .. PRECEDES OR EQUIVALENT TO +.. |preceq| unicode:: U+02AAF .. PRECEDES ABOVE SINGLE-LINE EQUALS SIGN +.. |precnapprox| unicode:: U+02AB9 .. PRECEDES ABOVE NOT ALMOST EQUAL TO +.. |precneqq| unicode:: U+02AB5 .. PRECEDES ABOVE NOT EQUAL TO +.. |precnsim| unicode:: U+022E8 .. PRECEDES BUT NOT EQUIVALENT TO +.. |precsim| unicode:: U+0227E .. PRECEDES OR EQUIVALENT TO +.. |primes| unicode:: U+02119 .. DOUBLE-STRUCK CAPITAL P +.. |Proportion| unicode:: U+02237 .. PROPORTION +.. |Proportional| unicode:: U+0221D .. PROPORTIONAL TO +.. |propto| unicode:: U+0221D .. PROPORTIONAL TO +.. |quaternions| unicode:: U+0210D .. DOUBLE-STRUCK CAPITAL H +.. |questeq| unicode:: U+0225F .. QUESTIONED EQUAL TO +.. |rangle| unicode:: U+0232A .. RIGHT-POINTING ANGLE BRACKET +.. |rationals| unicode:: U+0211A .. DOUBLE-STRUCK CAPITAL Q +.. |rbrace| unicode:: U+0007D .. RIGHT CURLY BRACKET +.. |rbrack| unicode:: U+0005D .. RIGHT SQUARE BRACKET +.. |Re| unicode:: U+0211C .. BLACK-LETTER CAPITAL R +.. |realine| unicode:: U+0211B .. SCRIPT CAPITAL R +.. |realpart| unicode:: U+0211C .. BLACK-LETTER CAPITAL R +.. |reals| unicode:: U+0211D .. DOUBLE-STRUCK CAPITAL R +.. |ReverseElement| unicode:: U+0220B .. CONTAINS AS MEMBER +.. |ReverseEquilibrium| unicode:: U+021CB .. LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON +.. |ReverseUpEquilibrium| unicode:: U+0296F .. DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT +.. |RightAngleBracket| unicode:: U+0232A .. RIGHT-POINTING ANGLE BRACKET +.. |RightArrow| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |Rightarrow| unicode:: U+021D2 .. RIGHTWARDS DOUBLE ARROW +.. |rightarrow| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |RightArrowBar| unicode:: U+021E5 .. RIGHTWARDS ARROW TO BAR +.. |RightArrowLeftArrow| unicode:: U+021C4 .. RIGHTWARDS ARROW OVER LEFTWARDS ARROW +.. |rightarrowtail| unicode:: U+021A3 .. RIGHTWARDS ARROW WITH TAIL +.. |RightCeiling| unicode:: U+02309 .. RIGHT CEILING +.. |RightDoubleBracket| unicode:: U+0301B .. RIGHT WHITE SQUARE BRACKET +.. |RightDownVector| unicode:: U+021C2 .. DOWNWARDS HARPOON WITH BARB RIGHTWARDS +.. |RightFloor| unicode:: U+0230B .. RIGHT FLOOR +.. |rightharpoondown| unicode:: U+021C1 .. RIGHTWARDS HARPOON WITH BARB DOWNWARDS +.. |rightharpoonup| unicode:: U+021C0 .. RIGHTWARDS HARPOON WITH BARB UPWARDS +.. |rightleftarrows| unicode:: U+021C4 .. RIGHTWARDS ARROW OVER LEFTWARDS ARROW +.. |rightleftharpoons| unicode:: U+021CC .. RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON +.. |rightrightarrows| unicode:: U+021C9 .. RIGHTWARDS PAIRED ARROWS +.. |rightsquigarrow| unicode:: U+0219D .. RIGHTWARDS WAVE ARROW +.. |RightTee| unicode:: U+022A2 .. RIGHT TACK +.. |RightTeeArrow| unicode:: U+021A6 .. RIGHTWARDS ARROW FROM BAR +.. |rightthreetimes| unicode:: U+022CC .. RIGHT SEMIDIRECT PRODUCT +.. |RightTriangle| unicode:: U+022B3 .. CONTAINS AS NORMAL SUBGROUP +.. |RightTriangleEqual| unicode:: U+022B5 .. CONTAINS AS NORMAL SUBGROUP OR EQUAL TO +.. |RightUpVector| unicode:: U+021BE .. UPWARDS HARPOON WITH BARB RIGHTWARDS +.. |RightVector| unicode:: U+021C0 .. RIGHTWARDS HARPOON WITH BARB UPWARDS +.. |risingdotseq| unicode:: U+02253 .. IMAGE OF OR APPROXIMATELY EQUAL TO +.. |rmoustache| unicode:: U+023B1 .. UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION +.. |Rrightarrow| unicode:: U+021DB .. RIGHTWARDS TRIPLE ARROW +.. |Rsh| unicode:: U+021B1 .. UPWARDS ARROW WITH TIP RIGHTWARDS +.. |searrow| unicode:: U+02198 .. SOUTH EAST ARROW +.. |setminus| unicode:: U+02216 .. SET MINUS +.. |ShortDownArrow| unicode:: U+02193 .. DOWNWARDS ARROW +.. |ShortLeftArrow| unicode:: U+02190 .. LEFTWARDS ARROW +.. |shortmid| unicode:: U+02223 .. DIVIDES +.. |shortparallel| unicode:: U+02225 .. PARALLEL TO +.. |ShortRightArrow| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |ShortUpArrow| unicode:: U+02191 .. UPWARDS ARROW +.. |simeq| unicode:: U+02243 .. ASYMPTOTICALLY EQUAL TO +.. |SmallCircle| unicode:: U+02218 .. RING OPERATOR +.. |smallsetminus| unicode:: U+02216 .. SET MINUS +.. |spadesuit| unicode:: U+02660 .. BLACK SPADE SUIT +.. |Sqrt| unicode:: U+0221A .. SQUARE ROOT +.. |sqsubset| unicode:: U+0228F .. SQUARE IMAGE OF +.. |sqsubseteq| unicode:: U+02291 .. SQUARE IMAGE OF OR EQUAL TO +.. |sqsupset| unicode:: U+02290 .. SQUARE ORIGINAL OF +.. |sqsupseteq| unicode:: U+02292 .. SQUARE ORIGINAL OF OR EQUAL TO +.. |Square| unicode:: U+025A1 .. WHITE SQUARE +.. |SquareIntersection| unicode:: U+02293 .. SQUARE CAP +.. |SquareSubset| unicode:: U+0228F .. SQUARE IMAGE OF +.. |SquareSubsetEqual| unicode:: U+02291 .. SQUARE IMAGE OF OR EQUAL TO +.. |SquareSuperset| unicode:: U+02290 .. SQUARE ORIGINAL OF +.. |SquareSupersetEqual| unicode:: U+02292 .. SQUARE ORIGINAL OF OR EQUAL TO +.. |SquareUnion| unicode:: U+02294 .. SQUARE CUP +.. |Star| unicode:: U+022C6 .. STAR OPERATOR +.. |straightepsilon| unicode:: U+003F5 .. GREEK LUNATE EPSILON SYMBOL +.. |straightphi| unicode:: U+003D5 .. GREEK PHI SYMBOL +.. |Subset| unicode:: U+022D0 .. DOUBLE SUBSET +.. |subset| unicode:: U+02282 .. SUBSET OF +.. |subseteq| unicode:: U+02286 .. SUBSET OF OR EQUAL TO +.. |subseteqq| unicode:: U+02AC5 .. SUBSET OF ABOVE EQUALS SIGN +.. |SubsetEqual| unicode:: U+02286 .. SUBSET OF OR EQUAL TO +.. |subsetneq| unicode:: U+0228A .. SUBSET OF WITH NOT EQUAL TO +.. |subsetneqq| unicode:: U+02ACB .. SUBSET OF ABOVE NOT EQUAL TO +.. |succ| unicode:: U+0227B .. SUCCEEDS +.. |succapprox| unicode:: U+02AB8 .. SUCCEEDS ABOVE ALMOST EQUAL TO +.. |succcurlyeq| unicode:: U+0227D .. SUCCEEDS OR EQUAL TO +.. |Succeeds| unicode:: U+0227B .. SUCCEEDS +.. |SucceedsEqual| unicode:: U+02AB0 .. SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN +.. |SucceedsSlantEqual| unicode:: U+0227D .. SUCCEEDS OR EQUAL TO +.. |SucceedsTilde| unicode:: U+0227F .. SUCCEEDS OR EQUIVALENT TO +.. |succeq| unicode:: U+02AB0 .. SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN +.. |succnapprox| unicode:: U+02ABA .. SUCCEEDS ABOVE NOT ALMOST EQUAL TO +.. |succneqq| unicode:: U+02AB6 .. SUCCEEDS ABOVE NOT EQUAL TO +.. |succnsim| unicode:: U+022E9 .. SUCCEEDS BUT NOT EQUIVALENT TO +.. |succsim| unicode:: U+0227F .. SUCCEEDS OR EQUIVALENT TO +.. |SuchThat| unicode:: U+0220B .. CONTAINS AS MEMBER +.. |Sum| unicode:: U+02211 .. N-ARY SUMMATION +.. |Superset| unicode:: U+02283 .. SUPERSET OF +.. |SupersetEqual| unicode:: U+02287 .. SUPERSET OF OR EQUAL TO +.. |Supset| unicode:: U+022D1 .. DOUBLE SUPERSET +.. |supset| unicode:: U+02283 .. SUPERSET OF +.. |supseteq| unicode:: U+02287 .. SUPERSET OF OR EQUAL TO +.. |supseteqq| unicode:: U+02AC6 .. SUPERSET OF ABOVE EQUALS SIGN +.. |supsetneq| unicode:: U+0228B .. SUPERSET OF WITH NOT EQUAL TO +.. |supsetneqq| unicode:: U+02ACC .. SUPERSET OF ABOVE NOT EQUAL TO +.. |swarrow| unicode:: U+02199 .. SOUTH WEST ARROW +.. |Therefore| unicode:: U+02234 .. THEREFORE +.. |therefore| unicode:: U+02234 .. THEREFORE +.. |thickapprox| unicode:: U+02248 .. ALMOST EQUAL TO +.. |thicksim| unicode:: U+0223C .. TILDE OPERATOR +.. |ThinSpace| unicode:: U+02009 .. THIN SPACE +.. |Tilde| unicode:: U+0223C .. TILDE OPERATOR +.. |TildeEqual| unicode:: U+02243 .. ASYMPTOTICALLY EQUAL TO +.. |TildeFullEqual| unicode:: U+02245 .. APPROXIMATELY EQUAL TO +.. |TildeTilde| unicode:: U+02248 .. ALMOST EQUAL TO +.. |toea| unicode:: U+02928 .. NORTH EAST ARROW AND SOUTH EAST ARROW +.. |tosa| unicode:: U+02929 .. SOUTH EAST ARROW AND SOUTH WEST ARROW +.. |triangle| unicode:: U+025B5 .. WHITE UP-POINTING SMALL TRIANGLE +.. |triangledown| unicode:: U+025BF .. WHITE DOWN-POINTING SMALL TRIANGLE +.. |triangleleft| unicode:: U+025C3 .. WHITE LEFT-POINTING SMALL TRIANGLE +.. |trianglelefteq| unicode:: U+022B4 .. NORMAL SUBGROUP OF OR EQUAL TO +.. |triangleq| unicode:: U+0225C .. DELTA EQUAL TO +.. |triangleright| unicode:: U+025B9 .. WHITE RIGHT-POINTING SMALL TRIANGLE +.. |trianglerighteq| unicode:: U+022B5 .. CONTAINS AS NORMAL SUBGROUP OR EQUAL TO +.. |TripleDot| unicode:: U+020DB .. COMBINING THREE DOTS ABOVE +.. |twoheadleftarrow| unicode:: U+0219E .. LEFTWARDS TWO HEADED ARROW +.. |twoheadrightarrow| unicode:: U+021A0 .. RIGHTWARDS TWO HEADED ARROW +.. |ulcorner| unicode:: U+0231C .. TOP LEFT CORNER +.. |Union| unicode:: U+022C3 .. N-ARY UNION +.. |UnionPlus| unicode:: U+0228E .. MULTISET UNION +.. |UpArrow| unicode:: U+02191 .. UPWARDS ARROW +.. |Uparrow| unicode:: U+021D1 .. UPWARDS DOUBLE ARROW +.. |uparrow| unicode:: U+02191 .. UPWARDS ARROW +.. |UpArrowDownArrow| unicode:: U+021C5 .. UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW +.. |UpDownArrow| unicode:: U+02195 .. UP DOWN ARROW +.. |Updownarrow| unicode:: U+021D5 .. UP DOWN DOUBLE ARROW +.. |updownarrow| unicode:: U+02195 .. UP DOWN ARROW +.. |UpEquilibrium| unicode:: U+0296E .. UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT +.. |upharpoonleft| unicode:: U+021BF .. UPWARDS HARPOON WITH BARB LEFTWARDS +.. |upharpoonright| unicode:: U+021BE .. UPWARDS HARPOON WITH BARB RIGHTWARDS +.. |UpperLeftArrow| unicode:: U+02196 .. NORTH WEST ARROW +.. |UpperRightArrow| unicode:: U+02197 .. NORTH EAST ARROW +.. |upsilon| unicode:: U+003C5 .. GREEK SMALL LETTER UPSILON +.. |UpTee| unicode:: U+022A5 .. UP TACK +.. |UpTeeArrow| unicode:: U+021A5 .. UPWARDS ARROW FROM BAR +.. |upuparrows| unicode:: U+021C8 .. UPWARDS PAIRED ARROWS +.. |urcorner| unicode:: U+0231D .. TOP RIGHT CORNER +.. |varepsilon| unicode:: U+003B5 .. GREEK SMALL LETTER EPSILON +.. |varkappa| unicode:: U+003F0 .. GREEK KAPPA SYMBOL +.. |varnothing| unicode:: U+02205 .. EMPTY SET +.. |varphi| unicode:: U+003C6 .. GREEK SMALL LETTER PHI +.. |varpi| unicode:: U+003D6 .. GREEK PI SYMBOL +.. |varpropto| unicode:: U+0221D .. PROPORTIONAL TO +.. |varrho| unicode:: U+003F1 .. GREEK RHO SYMBOL +.. |varsigma| unicode:: U+003C2 .. GREEK SMALL LETTER FINAL SIGMA +.. |varsubsetneq| unicode:: U+0228A U+0FE00 .. SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members +.. |varsubsetneqq| unicode:: U+02ACB U+0FE00 .. SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members +.. |varsupsetneq| unicode:: U+0228B U+0FE00 .. SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members +.. |varsupsetneqq| unicode:: U+02ACC U+0FE00 .. SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members +.. |vartheta| unicode:: U+003D1 .. GREEK THETA SYMBOL +.. |vartriangleleft| unicode:: U+022B2 .. NORMAL SUBGROUP OF +.. |vartriangleright| unicode:: U+022B3 .. CONTAINS AS NORMAL SUBGROUP +.. |Vee| unicode:: U+022C1 .. N-ARY LOGICAL OR +.. |vee| unicode:: U+02228 .. LOGICAL OR +.. |Vert| unicode:: U+02016 .. DOUBLE VERTICAL LINE +.. |vert| unicode:: U+0007C .. VERTICAL LINE +.. |VerticalBar| unicode:: U+02223 .. DIVIDES +.. |VerticalTilde| unicode:: U+02240 .. WREATH PRODUCT +.. |VeryThinSpace| unicode:: U+0200A .. HAIR SPACE +.. |Wedge| unicode:: U+022C0 .. N-ARY LOGICAL AND +.. |wedge| unicode:: U+02227 .. LOGICAL AND +.. |wp| unicode:: U+02118 .. SCRIPT CAPITAL P +.. |wr| unicode:: U+02240 .. WREATH PRODUCT +.. |zeetrf| unicode:: U+02128 .. BLACK-LETTER CAPITAL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlextra-wide.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlextra-wide.txt new file mode 100644 index 00000000..f45fc8cb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlextra-wide.txt @@ -0,0 +1,113 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |af| unicode:: U+02061 .. FUNCTION APPLICATION +.. |aopf| unicode:: U+1D552 .. MATHEMATICAL DOUBLE-STRUCK SMALL A +.. |asympeq| unicode:: U+0224D .. EQUIVALENT TO +.. |bopf| unicode:: U+1D553 .. MATHEMATICAL DOUBLE-STRUCK SMALL B +.. |copf| unicode:: U+1D554 .. MATHEMATICAL DOUBLE-STRUCK SMALL C +.. |Cross| unicode:: U+02A2F .. VECTOR OR CROSS PRODUCT +.. |DD| unicode:: U+02145 .. DOUBLE-STRUCK ITALIC CAPITAL D +.. |dd| unicode:: U+02146 .. DOUBLE-STRUCK ITALIC SMALL D +.. |dopf| unicode:: U+1D555 .. MATHEMATICAL DOUBLE-STRUCK SMALL D +.. |DownArrowBar| unicode:: U+02913 .. DOWNWARDS ARROW TO BAR +.. |DownBreve| unicode:: U+00311 .. COMBINING INVERTED BREVE +.. |DownLeftRightVector| unicode:: U+02950 .. LEFT BARB DOWN RIGHT BARB DOWN HARPOON +.. |DownLeftTeeVector| unicode:: U+0295E .. LEFTWARDS HARPOON WITH BARB DOWN FROM BAR +.. |DownLeftVectorBar| unicode:: U+02956 .. LEFTWARDS HARPOON WITH BARB DOWN TO BAR +.. |DownRightTeeVector| unicode:: U+0295F .. RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR +.. |DownRightVectorBar| unicode:: U+02957 .. RIGHTWARDS HARPOON WITH BARB DOWN TO BAR +.. |ee| unicode:: U+02147 .. DOUBLE-STRUCK ITALIC SMALL E +.. |EmptySmallSquare| unicode:: U+025FB .. WHITE MEDIUM SQUARE +.. |EmptyVerySmallSquare| unicode:: U+025AB .. WHITE SMALL SQUARE +.. |eopf| unicode:: U+1D556 .. MATHEMATICAL DOUBLE-STRUCK SMALL E +.. |Equal| unicode:: U+02A75 .. TWO CONSECUTIVE EQUALS SIGNS +.. |FilledSmallSquare| unicode:: U+025FC .. BLACK MEDIUM SQUARE +.. |FilledVerySmallSquare| unicode:: U+025AA .. BLACK SMALL SQUARE +.. |fopf| unicode:: U+1D557 .. MATHEMATICAL DOUBLE-STRUCK SMALL F +.. |gopf| unicode:: U+1D558 .. MATHEMATICAL DOUBLE-STRUCK SMALL G +.. |GreaterGreater| unicode:: U+02AA2 .. DOUBLE NESTED GREATER-THAN +.. |Hat| unicode:: U+0005E .. CIRCUMFLEX ACCENT +.. |hopf| unicode:: U+1D559 .. MATHEMATICAL DOUBLE-STRUCK SMALL H +.. |HorizontalLine| unicode:: U+02500 .. BOX DRAWINGS LIGHT HORIZONTAL +.. |ic| unicode:: U+02063 .. INVISIBLE SEPARATOR +.. |ii| unicode:: U+02148 .. DOUBLE-STRUCK ITALIC SMALL I +.. |iopf| unicode:: U+1D55A .. MATHEMATICAL DOUBLE-STRUCK SMALL I +.. |it| unicode:: U+02062 .. INVISIBLE TIMES +.. |jopf| unicode:: U+1D55B .. MATHEMATICAL DOUBLE-STRUCK SMALL J +.. |kopf| unicode:: U+1D55C .. MATHEMATICAL DOUBLE-STRUCK SMALL K +.. |larrb| unicode:: U+021E4 .. LEFTWARDS ARROW TO BAR +.. |LeftDownTeeVector| unicode:: U+02961 .. DOWNWARDS HARPOON WITH BARB LEFT FROM BAR +.. |LeftDownVectorBar| unicode:: U+02959 .. DOWNWARDS HARPOON WITH BARB LEFT TO BAR +.. |LeftRightVector| unicode:: U+0294E .. LEFT BARB UP RIGHT BARB UP HARPOON +.. |LeftTeeVector| unicode:: U+0295A .. LEFTWARDS HARPOON WITH BARB UP FROM BAR +.. |LeftTriangleBar| unicode:: U+029CF .. LEFT TRIANGLE BESIDE VERTICAL BAR +.. |LeftUpDownVector| unicode:: U+02951 .. UP BARB LEFT DOWN BARB LEFT HARPOON +.. |LeftUpTeeVector| unicode:: U+02960 .. UPWARDS HARPOON WITH BARB LEFT FROM BAR +.. |LeftUpVectorBar| unicode:: U+02958 .. UPWARDS HARPOON WITH BARB LEFT TO BAR +.. |LeftVectorBar| unicode:: U+02952 .. LEFTWARDS HARPOON WITH BARB UP TO BAR +.. |LessLess| unicode:: U+02AA1 .. DOUBLE NESTED LESS-THAN +.. |lopf| unicode:: U+1D55D .. MATHEMATICAL DOUBLE-STRUCK SMALL L +.. |mapstodown| unicode:: U+021A7 .. DOWNWARDS ARROW FROM BAR +.. |mapstoleft| unicode:: U+021A4 .. LEFTWARDS ARROW FROM BAR +.. |mapstoup| unicode:: U+021A5 .. UPWARDS ARROW FROM BAR +.. |MediumSpace| unicode:: U+0205F .. MEDIUM MATHEMATICAL SPACE +.. |mopf| unicode:: U+1D55E .. MATHEMATICAL DOUBLE-STRUCK SMALL M +.. |nbump| unicode:: U+0224E U+00338 .. GEOMETRICALLY EQUIVALENT TO with slash +.. |nbumpe| unicode:: U+0224F U+00338 .. DIFFERENCE BETWEEN with slash +.. |nesim| unicode:: U+02242 U+00338 .. MINUS TILDE with slash +.. |NewLine| unicode:: U+0000A .. LINE FEED (LF) +.. |NoBreak| unicode:: U+02060 .. WORD JOINER +.. |nopf| unicode:: U+1D55F .. MATHEMATICAL DOUBLE-STRUCK SMALL N +.. |NotCupCap| unicode:: U+0226D .. NOT EQUIVALENT TO +.. |NotHumpEqual| unicode:: U+0224F U+00338 .. DIFFERENCE BETWEEN with slash +.. |NotLeftTriangleBar| unicode:: U+029CF U+00338 .. LEFT TRIANGLE BESIDE VERTICAL BAR with slash +.. |NotNestedGreaterGreater| unicode:: U+02AA2 U+00338 .. DOUBLE NESTED GREATER-THAN with slash +.. |NotNestedLessLess| unicode:: U+02AA1 U+00338 .. DOUBLE NESTED LESS-THAN with slash +.. |NotRightTriangleBar| unicode:: U+029D0 U+00338 .. VERTICAL BAR BESIDE RIGHT TRIANGLE with slash +.. |NotSquareSubset| unicode:: U+0228F U+00338 .. SQUARE IMAGE OF with slash +.. |NotSquareSuperset| unicode:: U+02290 U+00338 .. SQUARE ORIGINAL OF with slash +.. |NotSucceedsTilde| unicode:: U+0227F U+00338 .. SUCCEEDS OR EQUIVALENT TO with slash +.. |oopf| unicode:: U+1D560 .. MATHEMATICAL DOUBLE-STRUCK SMALL O +.. |OverBar| unicode:: U+000AF .. MACRON +.. |OverBrace| unicode:: U+0FE37 .. PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +.. |OverBracket| unicode:: U+023B4 .. TOP SQUARE BRACKET +.. |OverParenthesis| unicode:: U+0FE35 .. PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +.. |planckh| unicode:: U+0210E .. PLANCK CONSTANT +.. |popf| unicode:: U+1D561 .. MATHEMATICAL DOUBLE-STRUCK SMALL P +.. |Product| unicode:: U+0220F .. N-ARY PRODUCT +.. |qopf| unicode:: U+1D562 .. MATHEMATICAL DOUBLE-STRUCK SMALL Q +.. |rarrb| unicode:: U+021E5 .. RIGHTWARDS ARROW TO BAR +.. |RightDownTeeVector| unicode:: U+0295D .. DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR +.. |RightDownVectorBar| unicode:: U+02955 .. DOWNWARDS HARPOON WITH BARB RIGHT TO BAR +.. |RightTeeVector| unicode:: U+0295B .. RIGHTWARDS HARPOON WITH BARB UP FROM BAR +.. |RightTriangleBar| unicode:: U+029D0 .. VERTICAL BAR BESIDE RIGHT TRIANGLE +.. |RightUpDownVector| unicode:: U+0294F .. UP BARB RIGHT DOWN BARB RIGHT HARPOON +.. |RightUpTeeVector| unicode:: U+0295C .. UPWARDS HARPOON WITH BARB RIGHT FROM BAR +.. |RightUpVectorBar| unicode:: U+02954 .. UPWARDS HARPOON WITH BARB RIGHT TO BAR +.. |RightVectorBar| unicode:: U+02953 .. RIGHTWARDS HARPOON WITH BARB UP TO BAR +.. |ropf| unicode:: U+1D563 .. MATHEMATICAL DOUBLE-STRUCK SMALL R +.. |RoundImplies| unicode:: U+02970 .. RIGHT DOUBLE ARROW WITH ROUNDED HEAD +.. |RuleDelayed| unicode:: U+029F4 .. RULE-DELAYED +.. |sopf| unicode:: U+1D564 .. MATHEMATICAL DOUBLE-STRUCK SMALL S +.. |Tab| unicode:: U+00009 .. CHARACTER TABULATION +.. |ThickSpace| unicode:: U+02009 U+0200A U+0200A .. space of width 5/18 em +.. |topf| unicode:: U+1D565 .. MATHEMATICAL DOUBLE-STRUCK SMALL T +.. |UnderBar| unicode:: U+00332 .. COMBINING LOW LINE +.. |UnderBrace| unicode:: U+0FE38 .. PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +.. |UnderBracket| unicode:: U+023B5 .. BOTTOM SQUARE BRACKET +.. |UnderParenthesis| unicode:: U+0FE36 .. PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +.. |uopf| unicode:: U+1D566 .. MATHEMATICAL DOUBLE-STRUCK SMALL U +.. |UpArrowBar| unicode:: U+02912 .. UPWARDS ARROW TO BAR +.. |Upsilon| unicode:: U+003A5 .. GREEK CAPITAL LETTER UPSILON +.. |VerticalLine| unicode:: U+0007C .. VERTICAL LINE +.. |VerticalSeparator| unicode:: U+02758 .. LIGHT VERTICAL BAR +.. |vopf| unicode:: U+1D567 .. MATHEMATICAL DOUBLE-STRUCK SMALL V +.. |wopf| unicode:: U+1D568 .. MATHEMATICAL DOUBLE-STRUCK SMALL W +.. |xopf| unicode:: U+1D569 .. MATHEMATICAL DOUBLE-STRUCK SMALL X +.. |yopf| unicode:: U+1D56A .. MATHEMATICAL DOUBLE-STRUCK SMALL Y +.. |ZeroWidthSpace| unicode:: U+0200B .. ZERO WIDTH SPACE +.. |zopf| unicode:: U+1D56B .. MATHEMATICAL DOUBLE-STRUCK SMALL Z diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlextra.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlextra.txt new file mode 100644 index 00000000..27262473 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/mmlextra.txt @@ -0,0 +1,87 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |af| unicode:: U+02061 .. FUNCTION APPLICATION +.. |asympeq| unicode:: U+0224D .. EQUIVALENT TO +.. |Cross| unicode:: U+02A2F .. VECTOR OR CROSS PRODUCT +.. |DD| unicode:: U+02145 .. DOUBLE-STRUCK ITALIC CAPITAL D +.. |dd| unicode:: U+02146 .. DOUBLE-STRUCK ITALIC SMALL D +.. |DownArrowBar| unicode:: U+02913 .. DOWNWARDS ARROW TO BAR +.. |DownBreve| unicode:: U+00311 .. COMBINING INVERTED BREVE +.. |DownLeftRightVector| unicode:: U+02950 .. LEFT BARB DOWN RIGHT BARB DOWN HARPOON +.. |DownLeftTeeVector| unicode:: U+0295E .. LEFTWARDS HARPOON WITH BARB DOWN FROM BAR +.. |DownLeftVectorBar| unicode:: U+02956 .. LEFTWARDS HARPOON WITH BARB DOWN TO BAR +.. |DownRightTeeVector| unicode:: U+0295F .. RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR +.. |DownRightVectorBar| unicode:: U+02957 .. RIGHTWARDS HARPOON WITH BARB DOWN TO BAR +.. |ee| unicode:: U+02147 .. DOUBLE-STRUCK ITALIC SMALL E +.. |EmptySmallSquare| unicode:: U+025FB .. WHITE MEDIUM SQUARE +.. |EmptyVerySmallSquare| unicode:: U+025AB .. WHITE SMALL SQUARE +.. |Equal| unicode:: U+02A75 .. TWO CONSECUTIVE EQUALS SIGNS +.. |FilledSmallSquare| unicode:: U+025FC .. BLACK MEDIUM SQUARE +.. |FilledVerySmallSquare| unicode:: U+025AA .. BLACK SMALL SQUARE +.. |GreaterGreater| unicode:: U+02AA2 .. DOUBLE NESTED GREATER-THAN +.. |Hat| unicode:: U+0005E .. CIRCUMFLEX ACCENT +.. |HorizontalLine| unicode:: U+02500 .. BOX DRAWINGS LIGHT HORIZONTAL +.. |ic| unicode:: U+02063 .. INVISIBLE SEPARATOR +.. |ii| unicode:: U+02148 .. DOUBLE-STRUCK ITALIC SMALL I +.. |it| unicode:: U+02062 .. INVISIBLE TIMES +.. |larrb| unicode:: U+021E4 .. LEFTWARDS ARROW TO BAR +.. |LeftDownTeeVector| unicode:: U+02961 .. DOWNWARDS HARPOON WITH BARB LEFT FROM BAR +.. |LeftDownVectorBar| unicode:: U+02959 .. DOWNWARDS HARPOON WITH BARB LEFT TO BAR +.. |LeftRightVector| unicode:: U+0294E .. LEFT BARB UP RIGHT BARB UP HARPOON +.. |LeftTeeVector| unicode:: U+0295A .. LEFTWARDS HARPOON WITH BARB UP FROM BAR +.. |LeftTriangleBar| unicode:: U+029CF .. LEFT TRIANGLE BESIDE VERTICAL BAR +.. |LeftUpDownVector| unicode:: U+02951 .. UP BARB LEFT DOWN BARB LEFT HARPOON +.. |LeftUpTeeVector| unicode:: U+02960 .. UPWARDS HARPOON WITH BARB LEFT FROM BAR +.. |LeftUpVectorBar| unicode:: U+02958 .. UPWARDS HARPOON WITH BARB LEFT TO BAR +.. |LeftVectorBar| unicode:: U+02952 .. LEFTWARDS HARPOON WITH BARB UP TO BAR +.. |LessLess| unicode:: U+02AA1 .. DOUBLE NESTED LESS-THAN +.. |mapstodown| unicode:: U+021A7 .. DOWNWARDS ARROW FROM BAR +.. |mapstoleft| unicode:: U+021A4 .. LEFTWARDS ARROW FROM BAR +.. |mapstoup| unicode:: U+021A5 .. UPWARDS ARROW FROM BAR +.. |MediumSpace| unicode:: U+0205F .. MEDIUM MATHEMATICAL SPACE +.. |nbump| unicode:: U+0224E U+00338 .. GEOMETRICALLY EQUIVALENT TO with slash +.. |nbumpe| unicode:: U+0224F U+00338 .. DIFFERENCE BETWEEN with slash +.. |nesim| unicode:: U+02242 U+00338 .. MINUS TILDE with slash +.. |NewLine| unicode:: U+0000A .. LINE FEED (LF) +.. |NoBreak| unicode:: U+02060 .. WORD JOINER +.. |NotCupCap| unicode:: U+0226D .. NOT EQUIVALENT TO +.. |NotHumpEqual| unicode:: U+0224F U+00338 .. DIFFERENCE BETWEEN with slash +.. |NotLeftTriangleBar| unicode:: U+029CF U+00338 .. LEFT TRIANGLE BESIDE VERTICAL BAR with slash +.. |NotNestedGreaterGreater| unicode:: U+02AA2 U+00338 .. DOUBLE NESTED GREATER-THAN with slash +.. |NotNestedLessLess| unicode:: U+02AA1 U+00338 .. DOUBLE NESTED LESS-THAN with slash +.. |NotRightTriangleBar| unicode:: U+029D0 U+00338 .. VERTICAL BAR BESIDE RIGHT TRIANGLE with slash +.. |NotSquareSubset| unicode:: U+0228F U+00338 .. SQUARE IMAGE OF with slash +.. |NotSquareSuperset| unicode:: U+02290 U+00338 .. SQUARE ORIGINAL OF with slash +.. |NotSucceedsTilde| unicode:: U+0227F U+00338 .. SUCCEEDS OR EQUIVALENT TO with slash +.. |OverBar| unicode:: U+000AF .. MACRON +.. |OverBrace| unicode:: U+0FE37 .. PRESENTATION FORM FOR VERTICAL LEFT CURLY BRACKET +.. |OverBracket| unicode:: U+023B4 .. TOP SQUARE BRACKET +.. |OverParenthesis| unicode:: U+0FE35 .. PRESENTATION FORM FOR VERTICAL LEFT PARENTHESIS +.. |planckh| unicode:: U+0210E .. PLANCK CONSTANT +.. |Product| unicode:: U+0220F .. N-ARY PRODUCT +.. |rarrb| unicode:: U+021E5 .. RIGHTWARDS ARROW TO BAR +.. |RightDownTeeVector| unicode:: U+0295D .. DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR +.. |RightDownVectorBar| unicode:: U+02955 .. DOWNWARDS HARPOON WITH BARB RIGHT TO BAR +.. |RightTeeVector| unicode:: U+0295B .. RIGHTWARDS HARPOON WITH BARB UP FROM BAR +.. |RightTriangleBar| unicode:: U+029D0 .. VERTICAL BAR BESIDE RIGHT TRIANGLE +.. |RightUpDownVector| unicode:: U+0294F .. UP BARB RIGHT DOWN BARB RIGHT HARPOON +.. |RightUpTeeVector| unicode:: U+0295C .. UPWARDS HARPOON WITH BARB RIGHT FROM BAR +.. |RightUpVectorBar| unicode:: U+02954 .. UPWARDS HARPOON WITH BARB RIGHT TO BAR +.. |RightVectorBar| unicode:: U+02953 .. RIGHTWARDS HARPOON WITH BARB UP TO BAR +.. |RoundImplies| unicode:: U+02970 .. RIGHT DOUBLE ARROW WITH ROUNDED HEAD +.. |RuleDelayed| unicode:: U+029F4 .. RULE-DELAYED +.. |Tab| unicode:: U+00009 .. CHARACTER TABULATION +.. |ThickSpace| unicode:: U+02009 U+0200A U+0200A .. space of width 5/18 em +.. |UnderBar| unicode:: U+00332 .. COMBINING LOW LINE +.. |UnderBrace| unicode:: U+0FE38 .. PRESENTATION FORM FOR VERTICAL RIGHT CURLY BRACKET +.. |UnderBracket| unicode:: U+023B5 .. BOTTOM SQUARE BRACKET +.. |UnderParenthesis| unicode:: U+0FE36 .. PRESENTATION FORM FOR VERTICAL RIGHT PARENTHESIS +.. |UpArrowBar| unicode:: U+02912 .. UPWARDS ARROW TO BAR +.. |Upsilon| unicode:: U+003A5 .. GREEK CAPITAL LETTER UPSILON +.. |VerticalLine| unicode:: U+0007C .. VERTICAL LINE +.. |VerticalSeparator| unicode:: U+02758 .. LIGHT VERTICAL BAR +.. |ZeroWidthSpace| unicode:: U+0200B .. ZERO WIDTH SPACE diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/s5defs.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/s5defs.txt new file mode 100644 index 00000000..8aceeac0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/s5defs.txt @@ -0,0 +1,68 @@ +.. Definitions of interpreted text roles (classes) for S5/HTML data. +.. This data file has been placed in the public domain. + +.. Colours + ======= + +.. role:: black +.. role:: gray +.. role:: silver +.. role:: white + +.. role:: maroon +.. role:: red +.. role:: magenta +.. role:: fuchsia +.. role:: pink +.. role:: orange +.. role:: yellow +.. role:: lime +.. role:: green +.. role:: olive +.. role:: teal +.. role:: cyan +.. role:: aqua +.. role:: blue +.. role:: navy +.. role:: purple + + +.. Text Sizes + ========== + +.. role:: huge +.. role:: big +.. role:: small +.. role:: tiny + + +.. Display in Slides (Presentation Mode) Only + ========================================== + +.. role:: slide + :class: slide-display + + +.. Display in Outline Mode Only + ============================ + +.. role:: outline + + +.. Display in Print Only + ===================== + +.. role:: print + + +.. Display in Handout Mode Only + ============================ + +.. role:: handout + + +.. Incremental Display + =================== + +.. role:: incremental +.. default-role:: incremental diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-lat1.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-lat1.txt new file mode 100644 index 00000000..1cae194e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-lat1.txt @@ -0,0 +1,102 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |Aacute| unicode:: U+000C1 .. LATIN CAPITAL LETTER A WITH ACUTE +.. |aacute| unicode:: U+000E1 .. LATIN SMALL LETTER A WITH ACUTE +.. |Acirc| unicode:: U+000C2 .. LATIN CAPITAL LETTER A WITH CIRCUMFLEX +.. |acirc| unicode:: U+000E2 .. LATIN SMALL LETTER A WITH CIRCUMFLEX +.. |acute| unicode:: U+000B4 .. ACUTE ACCENT +.. |AElig| unicode:: U+000C6 .. LATIN CAPITAL LETTER AE +.. |aelig| unicode:: U+000E6 .. LATIN SMALL LETTER AE +.. |Agrave| unicode:: U+000C0 .. LATIN CAPITAL LETTER A WITH GRAVE +.. |agrave| unicode:: U+000E0 .. LATIN SMALL LETTER A WITH GRAVE +.. |Aring| unicode:: U+000C5 .. LATIN CAPITAL LETTER A WITH RING ABOVE +.. |aring| unicode:: U+000E5 .. LATIN SMALL LETTER A WITH RING ABOVE +.. |Atilde| unicode:: U+000C3 .. LATIN CAPITAL LETTER A WITH TILDE +.. |atilde| unicode:: U+000E3 .. LATIN SMALL LETTER A WITH TILDE +.. |Auml| unicode:: U+000C4 .. LATIN CAPITAL LETTER A WITH DIAERESIS +.. |auml| unicode:: U+000E4 .. LATIN SMALL LETTER A WITH DIAERESIS +.. |brvbar| unicode:: U+000A6 .. BROKEN BAR +.. |Ccedil| unicode:: U+000C7 .. LATIN CAPITAL LETTER C WITH CEDILLA +.. |ccedil| unicode:: U+000E7 .. LATIN SMALL LETTER C WITH CEDILLA +.. |cedil| unicode:: U+000B8 .. CEDILLA +.. |cent| unicode:: U+000A2 .. CENT SIGN +.. |copy| unicode:: U+000A9 .. COPYRIGHT SIGN +.. |curren| unicode:: U+000A4 .. CURRENCY SIGN +.. |deg| unicode:: U+000B0 .. DEGREE SIGN +.. |divide| unicode:: U+000F7 .. DIVISION SIGN +.. |Eacute| unicode:: U+000C9 .. LATIN CAPITAL LETTER E WITH ACUTE +.. |eacute| unicode:: U+000E9 .. LATIN SMALL LETTER E WITH ACUTE +.. |Ecirc| unicode:: U+000CA .. LATIN CAPITAL LETTER E WITH CIRCUMFLEX +.. |ecirc| unicode:: U+000EA .. LATIN SMALL LETTER E WITH CIRCUMFLEX +.. |Egrave| unicode:: U+000C8 .. LATIN CAPITAL LETTER E WITH GRAVE +.. |egrave| unicode:: U+000E8 .. LATIN SMALL LETTER E WITH GRAVE +.. |ETH| unicode:: U+000D0 .. LATIN CAPITAL LETTER ETH +.. |eth| unicode:: U+000F0 .. LATIN SMALL LETTER ETH +.. |Euml| unicode:: U+000CB .. LATIN CAPITAL LETTER E WITH DIAERESIS +.. |euml| unicode:: U+000EB .. LATIN SMALL LETTER E WITH DIAERESIS +.. |frac12| unicode:: U+000BD .. VULGAR FRACTION ONE HALF +.. |frac14| unicode:: U+000BC .. VULGAR FRACTION ONE QUARTER +.. |frac34| unicode:: U+000BE .. VULGAR FRACTION THREE QUARTERS +.. |Iacute| unicode:: U+000CD .. LATIN CAPITAL LETTER I WITH ACUTE +.. |iacute| unicode:: U+000ED .. LATIN SMALL LETTER I WITH ACUTE +.. |Icirc| unicode:: U+000CE .. LATIN CAPITAL LETTER I WITH CIRCUMFLEX +.. |icirc| unicode:: U+000EE .. LATIN SMALL LETTER I WITH CIRCUMFLEX +.. |iexcl| unicode:: U+000A1 .. INVERTED EXCLAMATION MARK +.. |Igrave| unicode:: U+000CC .. LATIN CAPITAL LETTER I WITH GRAVE +.. |igrave| unicode:: U+000EC .. LATIN SMALL LETTER I WITH GRAVE +.. |iquest| unicode:: U+000BF .. INVERTED QUESTION MARK +.. |Iuml| unicode:: U+000CF .. LATIN CAPITAL LETTER I WITH DIAERESIS +.. |iuml| unicode:: U+000EF .. LATIN SMALL LETTER I WITH DIAERESIS +.. |laquo| unicode:: U+000AB .. LEFT-POINTING DOUBLE ANGLE QUOTATION MARK +.. |macr| unicode:: U+000AF .. MACRON +.. |micro| unicode:: U+000B5 .. MICRO SIGN +.. |middot| unicode:: U+000B7 .. MIDDLE DOT +.. |nbsp| unicode:: U+000A0 .. NO-BREAK SPACE +.. |not| unicode:: U+000AC .. NOT SIGN +.. |Ntilde| unicode:: U+000D1 .. LATIN CAPITAL LETTER N WITH TILDE +.. |ntilde| unicode:: U+000F1 .. LATIN SMALL LETTER N WITH TILDE +.. |Oacute| unicode:: U+000D3 .. LATIN CAPITAL LETTER O WITH ACUTE +.. |oacute| unicode:: U+000F3 .. LATIN SMALL LETTER O WITH ACUTE +.. |Ocirc| unicode:: U+000D4 .. LATIN CAPITAL LETTER O WITH CIRCUMFLEX +.. |ocirc| unicode:: U+000F4 .. LATIN SMALL LETTER O WITH CIRCUMFLEX +.. |Ograve| unicode:: U+000D2 .. LATIN CAPITAL LETTER O WITH GRAVE +.. |ograve| unicode:: U+000F2 .. LATIN SMALL LETTER O WITH GRAVE +.. |ordf| unicode:: U+000AA .. FEMININE ORDINAL INDICATOR +.. |ordm| unicode:: U+000BA .. MASCULINE ORDINAL INDICATOR +.. |Oslash| unicode:: U+000D8 .. LATIN CAPITAL LETTER O WITH STROKE +.. |oslash| unicode:: U+000F8 .. LATIN SMALL LETTER O WITH STROKE +.. |Otilde| unicode:: U+000D5 .. LATIN CAPITAL LETTER O WITH TILDE +.. |otilde| unicode:: U+000F5 .. LATIN SMALL LETTER O WITH TILDE +.. |Ouml| unicode:: U+000D6 .. LATIN CAPITAL LETTER O WITH DIAERESIS +.. |ouml| unicode:: U+000F6 .. LATIN SMALL LETTER O WITH DIAERESIS +.. |para| unicode:: U+000B6 .. PILCROW SIGN +.. |plusmn| unicode:: U+000B1 .. PLUS-MINUS SIGN +.. |pound| unicode:: U+000A3 .. POUND SIGN +.. |raquo| unicode:: U+000BB .. RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK +.. |reg| unicode:: U+000AE .. REGISTERED SIGN +.. |sect| unicode:: U+000A7 .. SECTION SIGN +.. |shy| unicode:: U+000AD .. SOFT HYPHEN +.. |sup1| unicode:: U+000B9 .. SUPERSCRIPT ONE +.. |sup2| unicode:: U+000B2 .. SUPERSCRIPT TWO +.. |sup3| unicode:: U+000B3 .. SUPERSCRIPT THREE +.. |szlig| unicode:: U+000DF .. LATIN SMALL LETTER SHARP S +.. |THORN| unicode:: U+000DE .. LATIN CAPITAL LETTER THORN +.. |thorn| unicode:: U+000FE .. LATIN SMALL LETTER THORN +.. |times| unicode:: U+000D7 .. MULTIPLICATION SIGN +.. |Uacute| unicode:: U+000DA .. LATIN CAPITAL LETTER U WITH ACUTE +.. |uacute| unicode:: U+000FA .. LATIN SMALL LETTER U WITH ACUTE +.. |Ucirc| unicode:: U+000DB .. LATIN CAPITAL LETTER U WITH CIRCUMFLEX +.. |ucirc| unicode:: U+000FB .. LATIN SMALL LETTER U WITH CIRCUMFLEX +.. |Ugrave| unicode:: U+000D9 .. LATIN CAPITAL LETTER U WITH GRAVE +.. |ugrave| unicode:: U+000F9 .. LATIN SMALL LETTER U WITH GRAVE +.. |uml| unicode:: U+000A8 .. DIAERESIS +.. |Uuml| unicode:: U+000DC .. LATIN CAPITAL LETTER U WITH DIAERESIS +.. |uuml| unicode:: U+000FC .. LATIN SMALL LETTER U WITH DIAERESIS +.. |Yacute| unicode:: U+000DD .. LATIN CAPITAL LETTER Y WITH ACUTE +.. |yacute| unicode:: U+000FD .. LATIN SMALL LETTER Y WITH ACUTE +.. |yen| unicode:: U+000A5 .. YEN SIGN +.. |yuml| unicode:: U+000FF .. LATIN SMALL LETTER Y WITH DIAERESIS diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-special.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-special.txt new file mode 100644 index 00000000..b19c0b51 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-special.txt @@ -0,0 +1,37 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |bdquo| unicode:: U+0201E .. DOUBLE LOW-9 QUOTATION MARK +.. |circ| unicode:: U+002C6 .. MODIFIER LETTER CIRCUMFLEX ACCENT +.. |Dagger| unicode:: U+02021 .. DOUBLE DAGGER +.. |dagger| unicode:: U+02020 .. DAGGER +.. |emsp| unicode:: U+02003 .. EM SPACE +.. |ensp| unicode:: U+02002 .. EN SPACE +.. |euro| unicode:: U+020AC .. EURO SIGN +.. |gt| unicode:: U+0003E .. GREATER-THAN SIGN +.. |ldquo| unicode:: U+0201C .. LEFT DOUBLE QUOTATION MARK +.. |lrm| unicode:: U+0200E .. LEFT-TO-RIGHT MARK +.. |lsaquo| unicode:: U+02039 .. SINGLE LEFT-POINTING ANGLE QUOTATION MARK +.. |lsquo| unicode:: U+02018 .. LEFT SINGLE QUOTATION MARK +.. |lt| unicode:: U+0003C .. LESS-THAN SIGN +.. |mdash| unicode:: U+02014 .. EM DASH +.. |ndash| unicode:: U+02013 .. EN DASH +.. |OElig| unicode:: U+00152 .. LATIN CAPITAL LIGATURE OE +.. |oelig| unicode:: U+00153 .. LATIN SMALL LIGATURE OE +.. |permil| unicode:: U+02030 .. PER MILLE SIGN +.. |quot| unicode:: U+00022 .. QUOTATION MARK +.. |rdquo| unicode:: U+0201D .. RIGHT DOUBLE QUOTATION MARK +.. |rlm| unicode:: U+0200F .. RIGHT-TO-LEFT MARK +.. |rsaquo| unicode:: U+0203A .. SINGLE RIGHT-POINTING ANGLE QUOTATION MARK +.. |rsquo| unicode:: U+02019 .. RIGHT SINGLE QUOTATION MARK +.. |sbquo| unicode:: U+0201A .. SINGLE LOW-9 QUOTATION MARK +.. |Scaron| unicode:: U+00160 .. LATIN CAPITAL LETTER S WITH CARON +.. |scaron| unicode:: U+00161 .. LATIN SMALL LETTER S WITH CARON +.. |thinsp| unicode:: U+02009 .. THIN SPACE +.. |tilde| unicode:: U+002DC .. SMALL TILDE +.. |Yuml| unicode:: U+00178 .. LATIN CAPITAL LETTER Y WITH DIAERESIS +.. |zwj| unicode:: U+0200D .. ZERO WIDTH JOINER +.. |zwnj| unicode:: U+0200C .. ZERO WIDTH NON-JOINER diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-symbol.txt b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-symbol.txt new file mode 100644 index 00000000..1f935905 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/include/xhtml1-symbol.txt @@ -0,0 +1,130 @@ +.. This data file has been placed in the public domain. +.. Derived from the Unicode character mappings available from + <http://www.w3.org/2003/entities/xml/>. + Processed by unicode2rstsubs.py, part of Docutils: + <https://docutils.sourceforge.io>. + +.. |alefsym| unicode:: U+02135 .. ALEF SYMBOL +.. |Alpha| unicode:: U+00391 .. GREEK CAPITAL LETTER ALPHA +.. |alpha| unicode:: U+003B1 .. GREEK SMALL LETTER ALPHA +.. |and| unicode:: U+02227 .. LOGICAL AND +.. |ang| unicode:: U+02220 .. ANGLE +.. |asymp| unicode:: U+02248 .. ALMOST EQUAL TO +.. |Beta| unicode:: U+00392 .. GREEK CAPITAL LETTER BETA +.. |beta| unicode:: U+003B2 .. GREEK SMALL LETTER BETA +.. |bull| unicode:: U+02022 .. BULLET +.. |cap| unicode:: U+02229 .. INTERSECTION +.. |Chi| unicode:: U+003A7 .. GREEK CAPITAL LETTER CHI +.. |chi| unicode:: U+003C7 .. GREEK SMALL LETTER CHI +.. |clubs| unicode:: U+02663 .. BLACK CLUB SUIT +.. |cong| unicode:: U+02245 .. APPROXIMATELY EQUAL TO +.. |crarr| unicode:: U+021B5 .. DOWNWARDS ARROW WITH CORNER LEFTWARDS +.. |cup| unicode:: U+0222A .. UNION +.. |dArr| unicode:: U+021D3 .. DOWNWARDS DOUBLE ARROW +.. |darr| unicode:: U+02193 .. DOWNWARDS ARROW +.. |Delta| unicode:: U+00394 .. GREEK CAPITAL LETTER DELTA +.. |delta| unicode:: U+003B4 .. GREEK SMALL LETTER DELTA +.. |diams| unicode:: U+02666 .. BLACK DIAMOND SUIT +.. |empty| unicode:: U+02205 .. EMPTY SET +.. |Epsilon| unicode:: U+00395 .. GREEK CAPITAL LETTER EPSILON +.. |epsilon| unicode:: U+003B5 .. GREEK SMALL LETTER EPSILON +.. |equiv| unicode:: U+02261 .. IDENTICAL TO +.. |Eta| unicode:: U+00397 .. GREEK CAPITAL LETTER ETA +.. |eta| unicode:: U+003B7 .. GREEK SMALL LETTER ETA +.. |exist| unicode:: U+02203 .. THERE EXISTS +.. |fnof| unicode:: U+00192 .. LATIN SMALL LETTER F WITH HOOK +.. |forall| unicode:: U+02200 .. FOR ALL +.. |frasl| unicode:: U+02044 .. FRACTION SLASH +.. |Gamma| unicode:: U+00393 .. GREEK CAPITAL LETTER GAMMA +.. |gamma| unicode:: U+003B3 .. GREEK SMALL LETTER GAMMA +.. |ge| unicode:: U+02265 .. GREATER-THAN OR EQUAL TO +.. |hArr| unicode:: U+021D4 .. LEFT RIGHT DOUBLE ARROW +.. |harr| unicode:: U+02194 .. LEFT RIGHT ARROW +.. |hearts| unicode:: U+02665 .. BLACK HEART SUIT +.. |hellip| unicode:: U+02026 .. HORIZONTAL ELLIPSIS +.. |image| unicode:: U+02111 .. BLACK-LETTER CAPITAL I +.. |infin| unicode:: U+0221E .. INFINITY +.. |int| unicode:: U+0222B .. INTEGRAL +.. |Iota| unicode:: U+00399 .. GREEK CAPITAL LETTER IOTA +.. |iota| unicode:: U+003B9 .. GREEK SMALL LETTER IOTA +.. |isin| unicode:: U+02208 .. ELEMENT OF +.. |Kappa| unicode:: U+0039A .. GREEK CAPITAL LETTER KAPPA +.. |kappa| unicode:: U+003BA .. GREEK SMALL LETTER KAPPA +.. |Lambda| unicode:: U+0039B .. GREEK CAPITAL LETTER LAMDA +.. |lambda| unicode:: U+003BB .. GREEK SMALL LETTER LAMDA +.. |lang| unicode:: U+02329 .. LEFT-POINTING ANGLE BRACKET +.. |lArr| unicode:: U+021D0 .. LEFTWARDS DOUBLE ARROW +.. |larr| unicode:: U+02190 .. LEFTWARDS ARROW +.. |lceil| unicode:: U+02308 .. LEFT CEILING +.. |le| unicode:: U+02264 .. LESS-THAN OR EQUAL TO +.. |lfloor| unicode:: U+0230A .. LEFT FLOOR +.. |lowast| unicode:: U+02217 .. ASTERISK OPERATOR +.. |loz| unicode:: U+025CA .. LOZENGE +.. |minus| unicode:: U+02212 .. MINUS SIGN +.. |Mu| unicode:: U+0039C .. GREEK CAPITAL LETTER MU +.. |mu| unicode:: U+003BC .. GREEK SMALL LETTER MU +.. |nabla| unicode:: U+02207 .. NABLA +.. |ne| unicode:: U+02260 .. NOT EQUAL TO +.. |ni| unicode:: U+0220B .. CONTAINS AS MEMBER +.. |notin| unicode:: U+02209 .. NOT AN ELEMENT OF +.. |nsub| unicode:: U+02284 .. NOT A SUBSET OF +.. |Nu| unicode:: U+0039D .. GREEK CAPITAL LETTER NU +.. |nu| unicode:: U+003BD .. GREEK SMALL LETTER NU +.. |oline| unicode:: U+0203E .. OVERLINE +.. |Omega| unicode:: U+003A9 .. GREEK CAPITAL LETTER OMEGA +.. |omega| unicode:: U+003C9 .. GREEK SMALL LETTER OMEGA +.. |Omicron| unicode:: U+0039F .. GREEK CAPITAL LETTER OMICRON +.. |omicron| unicode:: U+003BF .. GREEK SMALL LETTER OMICRON +.. |oplus| unicode:: U+02295 .. CIRCLED PLUS +.. |or| unicode:: U+02228 .. LOGICAL OR +.. |otimes| unicode:: U+02297 .. CIRCLED TIMES +.. |part| unicode:: U+02202 .. PARTIAL DIFFERENTIAL +.. |perp| unicode:: U+022A5 .. UP TACK +.. |Phi| unicode:: U+003A6 .. GREEK CAPITAL LETTER PHI +.. |phi| unicode:: U+003D5 .. GREEK PHI SYMBOL +.. |Pi| unicode:: U+003A0 .. GREEK CAPITAL LETTER PI +.. |pi| unicode:: U+003C0 .. GREEK SMALL LETTER PI +.. |piv| unicode:: U+003D6 .. GREEK PI SYMBOL +.. |Prime| unicode:: U+02033 .. DOUBLE PRIME +.. |prime| unicode:: U+02032 .. PRIME +.. |prod| unicode:: U+0220F .. N-ARY PRODUCT +.. |prop| unicode:: U+0221D .. PROPORTIONAL TO +.. |Psi| unicode:: U+003A8 .. GREEK CAPITAL LETTER PSI +.. |psi| unicode:: U+003C8 .. GREEK SMALL LETTER PSI +.. |radic| unicode:: U+0221A .. SQUARE ROOT +.. |rang| unicode:: U+0232A .. RIGHT-POINTING ANGLE BRACKET +.. |rArr| unicode:: U+021D2 .. RIGHTWARDS DOUBLE ARROW +.. |rarr| unicode:: U+02192 .. RIGHTWARDS ARROW +.. |rceil| unicode:: U+02309 .. RIGHT CEILING +.. |real| unicode:: U+0211C .. BLACK-LETTER CAPITAL R +.. |rfloor| unicode:: U+0230B .. RIGHT FLOOR +.. |Rho| unicode:: U+003A1 .. GREEK CAPITAL LETTER RHO +.. |rho| unicode:: U+003C1 .. GREEK SMALL LETTER RHO +.. |sdot| unicode:: U+022C5 .. DOT OPERATOR +.. |Sigma| unicode:: U+003A3 .. GREEK CAPITAL LETTER SIGMA +.. |sigma| unicode:: U+003C3 .. GREEK SMALL LETTER SIGMA +.. |sigmaf| unicode:: U+003C2 .. GREEK SMALL LETTER FINAL SIGMA +.. |sim| unicode:: U+0223C .. TILDE OPERATOR +.. |spades| unicode:: U+02660 .. BLACK SPADE SUIT +.. |sub| unicode:: U+02282 .. SUBSET OF +.. |sube| unicode:: U+02286 .. SUBSET OF OR EQUAL TO +.. |sum| unicode:: U+02211 .. N-ARY SUMMATION +.. |sup| unicode:: U+02283 .. SUPERSET OF +.. |supe| unicode:: U+02287 .. SUPERSET OF OR EQUAL TO +.. |Tau| unicode:: U+003A4 .. GREEK CAPITAL LETTER TAU +.. |tau| unicode:: U+003C4 .. GREEK SMALL LETTER TAU +.. |there4| unicode:: U+02234 .. THEREFORE +.. |Theta| unicode:: U+00398 .. GREEK CAPITAL LETTER THETA +.. |theta| unicode:: U+003B8 .. GREEK SMALL LETTER THETA +.. |thetasym| unicode:: U+003D1 .. GREEK THETA SYMBOL +.. |trade| unicode:: U+02122 .. TRADE MARK SIGN +.. |uArr| unicode:: U+021D1 .. UPWARDS DOUBLE ARROW +.. |uarr| unicode:: U+02191 .. UPWARDS ARROW +.. |upsih| unicode:: U+003D2 .. GREEK UPSILON WITH HOOK SYMBOL +.. |Upsilon| unicode:: U+003A5 .. GREEK CAPITAL LETTER UPSILON +.. |upsilon| unicode:: U+003C5 .. GREEK SMALL LETTER UPSILON +.. |weierp| unicode:: U+02118 .. SCRIPT CAPITAL P +.. |Xi| unicode:: U+0039E .. GREEK CAPITAL LETTER XI +.. |xi| unicode:: U+003BE .. GREEK SMALL LETTER XI +.. |Zeta| unicode:: U+00396 .. GREEK CAPITAL LETTER ZETA +.. |zeta| unicode:: U+003B6 .. GREEK SMALL LETTER ZETA diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/__init__.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/__init__.py new file mode 100644 index 00000000..a8bfd231 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/__init__.py @@ -0,0 +1,40 @@ +# $Id: __init__.py 9026 2022-03-04 15:57:13Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# Internationalization details are documented in +# <https://docutils.sourceforge.io/docs/howto/i18n.html>. + +""" +This package contains modules for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +from docutils.languages import LanguageImporter + + +class RstLanguageImporter(LanguageImporter): + """Import language modules. + + When called with a BCP 47 language tag, instances return a module + with localisations for "directive" and "role" names for from + `docutils.parsers.rst.languages` or the PYTHONPATH. + + If there is no matching module, warn (if a `reporter` is passed) + and return None. + """ + packages = ('docutils.parsers.rst.languages.', '') + warn_msg = 'rST localisation for language "%s" not found.' + fallback = None + + def check_content(self, module): + """Check if we got an rST language module.""" + if not (isinstance(module.directives, dict) + and isinstance(module.roles, dict)): + raise ImportError + + +get_language = RstLanguageImporter() diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/af.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/af.py new file mode 100644 index 00000000..31cb4ebf --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/af.py @@ -0,0 +1,108 @@ +# $Id: af.py 9417 2023-06-27 20:04:54Z milde $ +# Author: Jannie Hofmeyr <jhsh@sun.ac.za> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Afrikaans-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + 'aandag': 'attention', + 'versigtig': 'caution', + 'code (translation required)': 'code', + 'gevaar': 'danger', + 'fout': 'error', + 'wenk': 'hint', + 'belangrik': 'important', + 'nota': 'note', + 'tip': 'tip', # hint and tip both have the same translation: wenk + 'waarskuwing': 'warning', + 'advies': 'admonition', + 'vermaning': 'admonition', # sic! kept for backwards compatibiltity + 'kantstreep': 'sidebar', + 'onderwerp': 'topic', + 'lynblok': 'line-block', + 'math (translation required)': 'math', + 'parsed-literal (translation required)': 'parsed-literal', + 'rubriek': 'rubric', + 'epigraaf': 'epigraph', + 'hoogtepunte': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'vrae': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'table (translation required)': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + 'meta': 'meta', + # 'beeldkaart': 'imagemap', + 'beeld': 'image', + 'figuur': 'figure', + 'insluiting': 'include', + 'rou': 'raw', + 'vervang': 'replace', + 'unicode': 'unicode', # should this be translated? unikode + 'datum': 'date', + 'klas': 'class', + 'role (translation required)': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'inhoud': 'contents', + 'sectnum': 'sectnum', + 'section-numbering': 'sectnum', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'voetnote': 'footnotes', + # 'aanhalings': 'citations', + 'teikennotas': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Afrikaans name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'afkorting': 'abbreviation', + 'ab': 'abbreviation', + 'akroniem': 'acronym', + 'ac': 'acronym', + 'code (translation required)': 'code', + 'indeks': 'index', + 'i': 'index', + 'voetskrif': 'subscript', + 'sub': 'subscript', + 'boskrif': 'superscript', + 'sup': 'superscript', + 'titelverwysing': 'title-reference', + 'titel': 'title-reference', + 't': 'title-reference', + 'pep-verwysing': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-verwysing': 'rfc-reference', + 'rfc': 'rfc-reference', + 'nadruk': 'emphasis', + 'sterk': 'strong', + 'literal (translation required)': 'literal', + 'math (translation required)': 'math', + 'benoemde verwysing': 'named-reference', + 'anonieme verwysing': 'anonymous-reference', + 'voetnootverwysing': 'footnote-reference', + 'aanhalingverwysing': 'citation-reference', + 'vervangingsverwysing': 'substitution-reference', + 'teiken': 'target', + 'uri-verwysing': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'rou': 'raw', + } +"""Mapping of Afrikaans role names to canonical names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ar.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ar.py new file mode 100644 index 00000000..71be92a5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ar.py @@ -0,0 +1,99 @@ +# $Id: fa.py 4564 2016-08-10 11:48:42Z +# Author: Shahin <me@5hah.in> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Arabic-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +directives = { + # language-dependent: fixed + 'تنبيه': 'attention', + 'احتیاط': 'caution', + 'كود': 'code', + 'خطر': 'danger', + 'خطأ': 'error', + 'تلميح': 'hint', + 'مهم': 'important', + 'ملاحظة': 'note', + 'نصيحة': 'tip', + 'تحذير': 'warning', + 'تذكير': 'admonition', + 'شريط-جانبي': 'sidebar', + 'موضوع': 'topic', + 'قالب-سطري': 'line-block', + 'لفظ-حرفي': 'parsed-literal', + 'معيار': 'rubric', + 'فكرة-الكتاب': 'epigraph', + 'تمييز': 'highlights', + 'نقل-قول': 'pull-quote', + 'ترکیب': 'compound', + 'وعاء': 'container', + # 'questions': 'questions', + 'جدول': 'table', + 'جدول-csv': 'csv-table', + 'جدول-قوائم': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + 'ميتا': 'meta', + 'رياضيات': 'math', + # 'imagemap': 'imagemap', + 'صورة': 'image', + 'رسم-توضيحي': 'figure', + 'تضمين': 'include', + 'خام': 'raw', + 'تبديل': 'replace', + 'یونیکد': 'unicode', + 'تاریخ': 'date', + 'كائن': 'class', + 'قانون': 'role', + 'قانون-افتراضي': 'default-role', + 'عنوان': 'title', + 'المحتوى': 'contents', + 'رقم-الفصل': 'sectnum', + 'رقم-القسم': 'sectnum', + 'رأس-الصفحة': 'header', + 'هامش': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + '': 'target-notes', +} +"""Arabic name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'اختصار': 'abbreviation', + 'اختزال': 'acronym', + 'كود': 'code', + 'فهرس': 'index', + 'خفض': 'subscript', + 'رفع': 'superscript', + 'عنوان-مرجع': 'title-reference', + 'مرجع-pep': 'pep-reference', + 'rfc-مرجع': 'rfc-reference', + 'تأكيد': 'emphasis', + 'عريض': 'strong', + 'لفظی': 'literal', + 'رياضيات': 'math', + 'مرجع-مسمى': 'named-reference', + 'مرجع-مجهول': 'anonymous-reference', + 'مرجع-هامشي': 'footnote-reference', + 'مرجع-منقول': 'citation-reference', + 'مرجع-معوض': 'substitution-reference', + 'هدف': 'target', + 'منبع-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'خام': 'raw', +} +"""Mapping of Arabic role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ca.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ca.py new file mode 100644 index 00000000..8c8eae40 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ca.py @@ -0,0 +1,130 @@ +# $Id: ca.py 9457 2023-10-02 16:25:50Z milde $ +# Authors: Ivan Vilata i Balaguer <ivan@selidor.net>; +# Antoni Bella Pérez <antonibella5@yahoo.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, +# please read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +# These translations can be used without changes for +# Valencian variant of Catalan (use language tag "ca-valencia"). +# Checked by a native speaker of Valentian. + +""" +Catalan-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'atenció': 'attention', + 'compte': 'caution', + 'perill': 'danger', + 'error': 'error', + 'suggeriment': 'hint', + 'important': 'important', + 'nota': 'note', + 'consell': 'tip', + 'avís': 'warning', + 'advertiment': 'admonition', + 'nota-al-marge': 'sidebar', + 'nota-marge': 'sidebar', + 'tema': 'topic', + 'bloc-de-línies': 'line-block', + 'bloc-línies': 'line-block', + 'literal-analitzat': 'parsed-literal', + 'codi': 'code', + 'bloc-de-codi': 'code', + 'matemàtiques': 'math', + 'rúbrica': 'rubric', + 'epígraf': 'epigraph', + 'sumari': 'highlights', + 'cita-destacada': 'pull-quote', + 'compost': 'compound', + 'contenidor': 'container', + 'taula': 'table', + 'taula-csv': 'csv-table', + 'taula-llista': 'list-table', + 'meta': 'meta', + # 'imagemap': 'imagemap', + 'imatge': 'image', + 'figura': 'figure', + 'inclou': 'include', + 'incloure': 'include', + 'cru': 'raw', + 'reemplaça': 'replace', + 'reemplaçar': 'replace', + 'unicode': 'unicode', + 'data': 'date', + 'classe': 'class', + 'rol': 'role', + 'rol-predeterminat': 'default-role', + 'títol': 'title', + 'contingut': 'contents', + 'numsec': 'sectnum', + 'numeració-de-seccions': 'sectnum', + 'numeració-seccions': 'sectnum', + 'capçalera': 'header', + 'peu-de-pàgina': 'footer', + 'peu-pàgina': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'notes-amb-destinacions': 'target-notes', + 'notes-destinacions': 'target-notes', + 'directiva-de-prova-de-restructuredtext': 'restructuredtext-test-directive'} # noqa:E501 +"""Catalan name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'abreviatura': 'abbreviation', + 'abreviació': 'abbreviation', + 'abrev': 'abbreviation', + 'ab': 'abbreviation', + 'acrònim': 'acronym', + 'ac': 'acronym', + 'codi': 'code', + 'èmfasi': 'emphasis', + 'literal': 'literal', + 'matemàtiques': 'math', + 'referència-a-pep': 'pep-reference', + 'referència-pep': 'pep-reference', + 'pep': 'pep-reference', + 'referència-a-rfc': 'rfc-reference', + 'referència-rfc': 'rfc-reference', + 'rfc': 'rfc-reference', + 'destacat': 'strong', + 'subíndex': 'subscript', + 'sub': 'subscript', + 'superíndex': 'superscript', + 'sup': 'superscript', + 'referència-a-títol': 'title-reference', + 'referència-títol': 'title-reference', + 'títol': 'title-reference', + 't': 'title-reference', + 'cru': 'raw', + # the following roles are not implemented in Docutils + 'índex': 'index', + 'i': 'index', + 'referència-anònima': 'anonymous-reference', + 'referència-a-cita': 'citation-reference', + 'referència-cita': 'citation-reference', + 'referència-a-nota-al-peu': 'footnote-reference', + 'referència-nota-al-peu': 'footnote-reference', + 'referència-amb-nom': 'named-reference', + 'referència-nom': 'named-reference', + 'referència-a-substitució': 'substitution-reference', + 'referència-substitució': 'substitution-reference', + 'referència-a-uri': 'uri-reference', + 'referència-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'destinació': 'target', + } +"""Mapping of Catalan role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/cs.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/cs.py new file mode 100644 index 00000000..70274d28 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/cs.py @@ -0,0 +1,110 @@ +# $Id: cs.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Marek Blaha <mb@dat.cz> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Czech-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'pozor': 'attention', + # jak rozlisit caution a warning? + 'caution (translation required)': 'caution', + 'code (translation required)': 'code', + 'nebezpečí': 'danger', + 'chyba': 'error', + 'rada': 'hint', + 'důležité': 'important', + 'poznámka': 'note', + 'tip (translation required)': 'tip', + 'varování': 'warning', + 'admonition (translation required)': 'admonition', + 'sidebar (translation required)': 'sidebar', + 'téma': 'topic', + 'line-block (translation required)': 'line-block', + 'parsed-literal (translation required)': 'parsed-literal', + 'oddíl': 'rubric', + 'moto': 'epigraph', + 'highlights (translation required)': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'questions': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'table (translation required)': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + 'math (translation required)': 'math', + 'meta (translation required)': 'meta', + # 'imagemap': 'imagemap', + 'image (translation required)': 'image', # obrazek + 'figure (translation required)': 'figure', # a tady? + 'include (translation required)': 'include', + 'raw (translation required)': 'raw', + 'replace (translation required)': 'replace', + 'unicode (translation required)': 'unicode', + 'datum': 'date', + 'třída': 'class', + 'role (translation required)': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'obsah': 'contents', + 'sectnum (translation required)': 'sectnum', + 'section-numbering (translation required)': 'sectnum', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'target-notes (translation required)': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Czech name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'abbreviation (translation required)': 'abbreviation', + 'ab (translation required)': 'abbreviation', + 'acronym (translation required)': 'acronym', + 'ac (translation required)': 'acronym', + 'code (translation required)': 'code', + 'index (translation required)': 'index', + 'i (translation required)': 'index', + 'subscript (translation required)': 'subscript', + 'sub (translation required)': 'subscript', + 'superscript (translation required)': 'superscript', + 'sup (translation required)': 'superscript', + 'title-reference (translation required)': 'title-reference', + 'title (translation required)': 'title-reference', + 't (translation required)': 'title-reference', + 'pep-reference (translation required)': 'pep-reference', + 'pep (translation required)': 'pep-reference', + 'rfc-reference (translation required)': 'rfc-reference', + 'rfc (translation required)': 'rfc-reference', + 'emphasis (translation required)': 'emphasis', + 'strong (translation required)': 'strong', + 'literal (translation required)': 'literal', + 'math (translation required)': 'math', + 'named-reference (translation required)': 'named-reference', + 'anonymous-reference (translation required)': 'anonymous-reference', + 'footnote-reference (translation required)': 'footnote-reference', + 'citation-reference (translation required)': 'citation-reference', + 'substitution-reference (translation required)': 'substitution-reference', + 'target (translation required)': 'target', + 'uri-reference (translation required)': 'uri-reference', + 'uri (translation required)': 'uri-reference', + 'url (translation required)': 'uri-reference', + 'raw (translation required)': 'raw', + } +"""Mapping of Czech role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/da.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/da.py new file mode 100644 index 00000000..31a250e1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/da.py @@ -0,0 +1,114 @@ +# $Id: da.py 9417 2023-06-27 20:04:54Z milde $ +# Author: E D +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Danish-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'giv agt': 'attention', + 'pas på': 'caution', + 'kode': 'code', + 'kode-blok': 'code', + 'kildekode': 'code', + 'fare': 'danger', + 'fejl': 'error', + 'vink': 'hint', + 'vigtigt': 'important', + 'bemærk': 'note', + 'tips': 'tip', + 'advarsel': 'warning', + 'varsel': 'admonition', + 'formaning': 'admonition', # sic! kept for backwards compatibiltity + 'sidebjælke': 'sidebar', + 'emne': 'topic', + 'linje-blok': 'line-block', + 'linie-blok': 'line-block', + 'parset-literal': 'parsed-literal', + 'rubrik': 'rubric', + 'epigraf': 'epigraph', + 'fremhævninger': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'questions': 'questions', + 'tabel': 'table', + 'csv-tabel': 'csv-table', + 'liste-tabel': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'billede': 'image', + 'figur': 'figure', + 'inkludér': 'include', + 'inkluder': 'include', + 'rå': 'raw', + 'erstat': 'replace', + 'unicode': 'unicode', + 'dato': 'date', + 'klasse': 'class', + 'rolle': 'role', + 'forvalgt-rolle': 'default-role', + 'titel': 'title', + 'indhold': 'contents', + 'sektnum': 'sectnum', + 'sektions-nummerering': 'sectnum', + 'sidehovede': 'header', + 'sidefod': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'target-notes (translation required)': 'target-notes', + 'restructuredtext-test-direktiv': 'restructuredtext-test-directive'} +"""Danish name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'forkortelse': 'abbreviation', + 'fork': 'abbreviation', + 'akronym': 'acronym', + 'ac (translation required)': 'acronym', + 'kode': 'code', + 'indeks': 'index', + 'i': 'index', + 'subscript (translation required)': 'subscript', + 'sub (translation required)': 'subscript', + 'superscript (translation required)': 'superscript', + 'sup (translation required)': 'superscript', + 'titel-reference': 'title-reference', + 'titel': 'title-reference', + 't': 'title-reference', + 'pep-reference': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-reference': 'rfc-reference', + 'rfc': 'rfc-reference', + 'emfase': 'emphasis', + 'kraftig': 'strong', + 'literal': 'literal', + 'math (translation required)': 'math', + 'navngivet-reference': 'named-reference', + 'anonym-reference': 'anonymous-reference', + 'fodnote-reference': 'footnote-reference', + 'citation-reference (translation required)': 'citation-reference', + 'substitutions-reference': 'substitution-reference', + 'target (translation required)': 'target', + 'uri-reference': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'rå': 'raw', + } +"""Mapping of Danish role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/de.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/de.py new file mode 100644 index 00000000..4b7ef8e0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/de.py @@ -0,0 +1,107 @@ +# $Id: de.py 9428 2023-07-07 06:50:26Z milde $ +# Authors: Engelbert Gruber <grubert@users.sourceforge.net>; +# Lea Wiemann <LeWiemann@gmail.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +German-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + 'warnhinweis': 'admonition', # or, more generally, 'anmerkung'? + 'ermahnung': 'admonition', # sic! kept for backwards compatibiltity + 'achtung': 'attention', + 'vorsicht': 'caution', + 'code': 'code', + 'gefahr': 'danger', + 'fehler': 'error', + 'hinweis': 'hint', # Wink + 'wichtig': 'important', + 'notiz': 'note', + 'tipp': 'tip', + 'warnung': 'warning', + 'kasten': 'sidebar', + 'seitenkasten': 'sidebar', # kept for backwards compatibiltity + 'seitenleiste': 'sidebar', + 'thema': 'topic', + 'zeilenblock': 'line-block', + 'parsed-literal (translation required)': 'parsed-literal', + 'rubrik': 'rubric', + 'epigraph': 'epigraph', + 'highlights': 'highlights', + 'pull-quote': 'pull-quote', # commonly used in German too + 'seitenansprache': 'pull-quote', + # cf. http://www.typografie.info/2/wiki.php?title=Seitenansprache + 'zusammengesetzt': 'compound', + 'verbund': 'compound', + 'container': 'container', + # 'fragen': 'questions', + 'tabelle': 'table', + 'csv-tabelle': 'csv-table', + 'listentabelle': 'list-table', + 'mathe': 'math', + 'formel': 'math', + 'meta': 'meta', + # 'imagemap': 'imagemap', + 'bild': 'image', + 'abbildung': 'figure', + 'unverändert': 'raw', + 'roh': 'raw', + 'einfügen': 'include', + 'ersetzung': 'replace', + 'ersetzen': 'replace', + 'ersetze': 'replace', + 'unicode': 'unicode', + 'datum': 'date', + 'klasse': 'class', + 'rolle': 'role', + 'standardrolle': 'default-role', + 'titel': 'title', + 'inhalt': 'contents', + 'kapitelnummerierung': 'sectnum', + 'abschnittsnummerierung': 'sectnum', + 'linkziel-fußnoten': 'target-notes', + 'kopfzeilen': 'header', + 'fußzeilen': 'footer', + # 'fußnoten': 'footnotes', + # 'zitate': 'citations', + } +"""German name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'abkürzung': 'abbreviation', + 'akronym': 'acronym', + 'code': 'code', + 'index': 'index', + 'tiefgestellt': 'subscript', + 'hochgestellt': 'superscript', + 'titel-referenz': 'title-reference', + 'pep-referenz': 'pep-reference', + 'rfc-referenz': 'rfc-reference', + 'betonung': 'emphasis', # for backwards compatibility + 'betont': 'emphasis', + 'fett': 'strong', + 'wörtlich': 'literal', + 'mathe': 'math', + 'benannte-referenz': 'named-reference', + 'unbenannte-referenz': 'anonymous-reference', + 'fußnoten-referenz': 'footnote-reference', + 'zitat-referenz': 'citation-reference', + 'ersetzungs-referenz': 'substitution-reference', + 'ziel': 'target', + 'uri-referenz': 'uri-reference', + 'unverändert': 'raw', + 'roh': 'raw', + } +"""Mapping of German role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/en.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/en.py new file mode 100644 index 00000000..7b9319e6 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/en.py @@ -0,0 +1,114 @@ +# $Id: en.py 9417 2023-06-27 20:04:54Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +English-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'attention': 'attention', + 'caution': 'caution', + 'danger': 'danger', + 'error': 'error', + 'hint': 'hint', + 'important': 'important', + 'note': 'note', + 'tip': 'tip', + 'warning': 'warning', + 'admonition': 'admonition', # advice/advisory/remark, not reprimand + 'sidebar': 'sidebar', + 'topic': 'topic', + 'line-block': 'line-block', + 'parsed-literal': 'parsed-literal', + 'code': 'code', + 'code-block': 'code', + 'sourcecode': 'code', + 'math': 'math', + 'rubric': 'rubric', + 'epigraph': 'epigraph', + 'highlights': 'highlights', + 'pull-quote': 'pull-quote', + 'compound': 'compound', + 'container': 'container', + 'table': 'table', + 'csv-table': 'csv-table', + 'list-table': 'list-table', + 'meta': 'meta', + # 'imagemap': 'imagemap', + 'image': 'image', + 'figure': 'figure', + 'include': 'include', + 'raw': 'raw', + 'replace': 'replace', + 'unicode': 'unicode', + 'date': 'date', + 'class': 'class', + 'role': 'role', + 'default-role': 'default-role', + 'title': 'title', + 'contents': 'contents', + 'sectnum': 'sectnum', + 'section-numbering': 'sectnum', + 'header': 'header', + 'footer': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'target-notes': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Mapping of English directive name to registered directive names + +Cf. https://docutils.sourceforge.io/docs/ref/rst/directives.html +and `_directive_registry` in ``directives/__init__.py``. +""" + +roles = { + # language-dependent: fixed + 'abbreviation': 'abbreviation', + 'ab': 'abbreviation', + 'acronym': 'acronym', + 'ac': 'acronym', + 'code': 'code', + 'emphasis': 'emphasis', + 'literal': 'literal', + 'math': 'math', + 'pep-reference': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-reference': 'rfc-reference', + 'rfc': 'rfc-reference', + 'strong': 'strong', + 'subscript': 'subscript', + 'sub': 'subscript', + 'superscript': 'superscript', + 'sup': 'superscript', + 'title-reference': 'title-reference', + 'title': 'title-reference', + 't': 'title-reference', + 'raw': 'raw', + # the following roles are not implemented in Docutils + 'index': 'index', + 'i': 'index', + 'anonymous-reference': 'anonymous-reference', + 'citation-reference': 'citation-reference', + 'footnote-reference': 'footnote-reference', + 'named-reference': 'named-reference', + 'substitution-reference': 'substitution-reference', + 'uri-reference': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'target': 'target', + } +"""Mapping of English role names to canonical role names for interpreted text. + +Cf. https://docutils.sourceforge.io/docs/ref/rst/roles.html +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/eo.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/eo.py new file mode 100644 index 00000000..1db6694f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/eo.py @@ -0,0 +1,119 @@ +# $Id: eo.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Marcelo Huerta San Martin <richieadler@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Esperanto-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'atentu': 'attention', + 'zorgu': 'caution', + 'code (translation required)': 'code', + 'dangxero': 'danger', + 'danĝero': 'danger', + 'eraro': 'error', + 'spuro': 'hint', + 'grava': 'important', + 'noto': 'note', + 'helpeto': 'tip', + 'averto': 'warning', + 'sciigo': 'admonition', + 'admono': 'admonition', # sic! kept for backwards compatibiltity + 'flankteksto': 'sidebar', + 'temo': 'topic', + 'linea-bloko': 'line-block', + 'analizota-literalo': 'parsed-literal', + 'rubriko': 'rubric', + 'epigrafo': 'epigraph', + 'elstarajxoj': 'highlights', + 'elstaraĵoj': 'highlights', + 'ekstera-citajxo': 'pull-quote', + 'ekstera-citaĵo': 'pull-quote', + 'kombinajxo': 'compound', + 'kombinaĵo': 'compound', + 'tekstingo': 'container', + 'enhavilo': 'container', + # 'questions': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'tabelo': 'table', + 'tabelo-vdk': 'csv-table', # "valoroj disigitaj per komoj" + 'tabelo-csv': 'csv-table', + 'tabelo-lista': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'bildo': 'image', + 'figuro': 'figure', + 'inkludi': 'include', + 'senanaliza': 'raw', + 'anstatauxi': 'replace', + 'anstataŭi': 'replace', + 'unicode': 'unicode', + 'dato': 'date', + 'klaso': 'class', + 'rolo': 'role', + 'preterlasita-rolo': 'default-role', + 'titolo': 'title', + 'enhavo': 'contents', + 'seknum': 'sectnum', + 'sekcia-numerado': 'sectnum', + 'kapsekcio': 'header', + 'piedsekcio': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'celaj-notoj': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Esperanto name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'mallongigo': 'abbreviation', + 'mall': 'abbreviation', + 'komenclitero': 'acronym', + 'kl': 'acronym', + 'code (translation required)': 'code', + 'indekso': 'index', + 'i': 'index', + 'subskribo': 'subscript', + 'sub': 'subscript', + 'supraskribo': 'superscript', + 'sup': 'superscript', + 'titola-referenco': 'title-reference', + 'titolo': 'title-reference', + 't': 'title-reference', + 'pep-referenco': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-referenco': 'rfc-reference', + 'rfc': 'rfc-reference', + 'emfazo': 'emphasis', + 'forta': 'strong', + 'litera': 'literal', + 'math (translation required)': 'math', + 'nomita-referenco': 'named-reference', + 'nenomita-referenco': 'anonymous-reference', + 'piednota-referenco': 'footnote-reference', + 'citajxo-referenco': 'citation-reference', + 'citaĵo-referenco': 'citation-reference', + 'anstatauxa-referenco': 'substitution-reference', + 'anstataŭa-referenco': 'substitution-reference', + 'celo': 'target', + 'uri-referenco': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'senanaliza': 'raw', +} +"""Mapping of Esperanto role names to canonical names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/es.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/es.py new file mode 100644 index 00000000..b7dda6c9 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/es.py @@ -0,0 +1,123 @@ +# $Id: es.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Marcelo Huerta San Martín <richieadler@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Spanish-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +directives = { + 'atención': 'attention', + 'atencion': 'attention', + 'precaución': 'caution', + 'code (translation required)': 'code', + 'precaucion': 'caution', + 'peligro': 'danger', + 'error': 'error', + 'sugerencia': 'hint', + 'importante': 'important', + 'nota': 'note', + 'consejo': 'tip', + 'advertencia': 'warning', + 'aviso': 'admonition', + 'exhortacion': 'admonition', # sic! kept for backwards compatibiltity + 'exhortación': 'admonition', # sic! kept for backwards compatibiltity + 'nota-al-margen': 'sidebar', + 'tema': 'topic', + 'bloque-de-lineas': 'line-block', + 'bloque-de-líneas': 'line-block', + 'literal-evaluado': 'parsed-literal', + 'firma': 'rubric', + 'epígrafe': 'epigraph', + 'epigrafe': 'epigraph', + 'destacado': 'highlights', + 'cita-destacada': 'pull-quote', + 'combinacion': 'compound', + 'combinación': 'compound', + 'contenedor': 'container', + # 'questions': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'tabla': 'table', + 'tabla-vsc': 'csv-table', + 'tabla-csv': 'csv-table', + 'tabla-lista': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'imagen': 'image', + 'figura': 'figure', + 'incluir': 'include', + 'sin-analisis': 'raw', + 'sin-análisis': 'raw', + 'reemplazar': 'replace', + 'unicode': 'unicode', + 'fecha': 'date', + 'clase': 'class', + 'rol': 'role', + 'rol-por-omision': 'default-role', + 'rol-por-omisión': 'default-role', + 'titulo': 'title', + 'título': 'title', + 'contenido': 'contents', + 'numseccion': 'sectnum', + 'numsección': 'sectnum', + 'numeracion-seccion': 'sectnum', + 'numeración-sección': 'sectnum', + 'notas-destino': 'target-notes', + 'cabecera': 'header', + 'pie': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Spanish name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'abreviatura': 'abbreviation', + 'ab': 'abbreviation', + 'acronimo': 'acronym', + 'ac': 'acronym', + 'code (translation required)': 'code', + 'indice': 'index', + 'i': 'index', + 'subindice': 'subscript', + 'subíndice': 'subscript', + 'superindice': 'superscript', + 'superíndice': 'superscript', + 'referencia-titulo': 'title-reference', + 'titulo': 'title-reference', + 't': 'title-reference', + 'referencia-pep': 'pep-reference', + 'pep': 'pep-reference', + 'referencia-rfc': 'rfc-reference', + 'rfc': 'rfc-reference', + 'enfasis': 'emphasis', + 'énfasis': 'emphasis', + 'destacado': 'strong', + 'literal': 'literal', # "literal" is also a word in Spanish :-) + 'math (translation required)': 'math', + 'referencia-con-nombre': 'named-reference', + 'referencia-anonima': 'anonymous-reference', + 'referencia-anónima': 'anonymous-reference', + 'referencia-nota-al-pie': 'footnote-reference', + 'referencia-cita': 'citation-reference', + 'referencia-sustitucion': 'substitution-reference', + 'referencia-sustitución': 'substitution-reference', + 'destino': 'target', + 'referencia-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'sin-analisis': 'raw', + 'sin-análisis': 'raw', +} +"""Mapping of Spanish role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fa.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fa.py new file mode 100644 index 00000000..420a315e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fa.py @@ -0,0 +1,102 @@ +# $Id: fa.py 4564 2016-08-10 11:48:42Z +# Author: Shahin <me@5hah.in> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Persian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'توجه': 'attention', + 'احتیاط': 'caution', + 'کد': 'code', + 'بلوک-کد': 'code', + 'کد-منبع': 'code', + 'خطر': 'danger', + 'خطا': 'error', + 'راهنما': 'hint', + 'مهم': 'important', + 'یادداشت': 'note', + 'نکته': 'tip', + 'اخطار': 'warning', + 'تذکر': 'admonition', + 'نوار-کناری': 'sidebar', + 'موضوع': 'topic', + 'بلوک-خط': 'line-block', + 'تلفظ-پردازش-شده': 'parsed-literal', + 'سر-فصل': 'rubric', + 'کتیبه': 'epigraph', + 'نکات-برجسته': 'highlights', + 'نقل-قول': 'pull-quote', + 'ترکیب': 'compound', + 'ظرف': 'container', + # 'questions': 'questions', + 'جدول': 'table', + 'جدول-csv': 'csv-table', + 'جدول-لیست': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + 'متا': 'meta', + 'ریاضی': 'math', + # 'imagemap': 'imagemap', + 'تصویر': 'image', + 'شکل': 'figure', + 'شامل': 'include', + 'خام': 'raw', + 'جایگزین': 'replace', + 'یونیکد': 'unicode', + 'تاریخ': 'date', + 'کلاس': 'class', + 'قانون': 'role', + 'قانون-پیشفرض': 'default-role', + 'عنوان': 'title', + 'محتوا': 'contents', + 'شماره-فصل': 'sectnum', + 'شمارهگذاری-فصل': 'sectnum', + 'سرآیند': 'header', + 'پاصفحه': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'یادداشت-هدف': 'target-notes', +} +"""Persian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'مخفف': 'abbreviation', + 'سرنام': 'acronym', + 'کد': 'code', + 'شاخص': 'index', + 'زیرنویس': 'subscript', + 'بالانویس': 'superscript', + 'عنوان': 'title-reference', + 'نیرو': 'pep-reference', + 'rfc-reference (translation required)': 'rfc-reference', + 'تاکید': 'emphasis', + 'قوی': 'strong', + 'لفظی': 'literal', + 'ریاضی': 'math', + 'منبع-نامگذاری': 'named-reference', + 'منبع-ناشناس': 'anonymous-reference', + 'منبع-پانویس': 'footnote-reference', + 'منبع-نقلفول': 'citation-reference', + 'منبع-جایگزینی': 'substitution-reference', + 'هدف': 'target', + 'منبع-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'خام': 'raw', +} +"""Mapping of Persian role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fi.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fi.py new file mode 100644 index 00000000..88653ac5 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fi.py @@ -0,0 +1,98 @@ +# $Id: fi.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Asko Soukka <asko.soukka@iki.fi> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Finnish-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'huomio': 'attention', + 'varo': 'caution', + 'code (translation required)': 'code', + 'vaara': 'danger', + 'virhe': 'error', + 'vihje': 'hint', + 'tärkeää': 'important', + 'huomautus': 'note', + 'neuvo': 'tip', + 'varoitus': 'warning', + 'kehotus': 'admonition', # sic! advice/advisory/remark, not reprimand + 'sivupalkki': 'sidebar', + 'aihe': 'topic', + 'rivi': 'line-block', + 'tasalevyinen': 'parsed-literal', + 'ohje': 'rubric', + 'epigraafi': 'epigraph', + 'kohokohdat': 'highlights', + 'lainaus': 'pull-quote', + 'taulukko': 'table', + 'csv-taulukko': 'csv-table', + 'list-table (translation required)': 'list-table', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'kysymykset': 'questions', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'kuvakartta': 'imagemap', + 'kuva': 'image', + 'kaavio': 'figure', + 'sisällytä': 'include', + 'raaka': 'raw', + 'korvaa': 'replace', + 'unicode': 'unicode', + 'päiväys': 'date', + 'luokka': 'class', + 'rooli': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'sisällys': 'contents', + 'kappale': 'sectnum', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'alaviitteet': 'footnotes', + # 'viitaukset': 'citations', + 'target-notes (translation required)': 'target-notes'} +"""Finnish name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'lyhennys': 'abbreviation', + 'akronyymi': 'acronym', + 'kirjainsana': 'acronym', + 'code (translation required)': 'code', + 'hakemisto': 'index', + 'luettelo': 'index', + 'alaindeksi': 'subscript', + 'indeksi': 'subscript', + 'yläindeksi': 'superscript', + 'title-reference (translation required)': 'title-reference', + 'title (translation required)': 'title-reference', + 'pep-reference (translation required)': 'pep-reference', + 'rfc-reference (translation required)': 'rfc-reference', + 'korostus': 'emphasis', + 'vahvistus': 'strong', + 'tasalevyinen': 'literal', + 'math (translation required)': 'math', + 'named-reference (translation required)': 'named-reference', + 'anonymous-reference (translation required)': 'anonymous-reference', + 'footnote-reference (translation required)': 'footnote-reference', + 'citation-reference (translation required)': 'citation-reference', + 'substitution-reference (translation required)': 'substitution-reference', + 'kohde': 'target', + 'uri-reference (translation required)': 'uri-reference', + 'raw (translation required)': 'raw', + } +"""Mapping of Finnish role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fr.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fr.py new file mode 100644 index 00000000..cd4ca9db --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/fr.py @@ -0,0 +1,108 @@ +# $Id: fr.py 9417 2023-06-27 20:04:54Z milde $ +# Authors: David Goodger <goodger@python.org>; William Dode +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +French-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + 'attention': 'attention', + 'précaution': 'caution', + 'danger': 'danger', + 'erreur': 'error', + 'conseil': 'hint', + 'important': 'important', + 'note': 'note', + 'astuce': 'tip', + 'avertissement': 'warning', + 'annonce': 'admonition', + 'admonition': 'admonition', # sic! kept for backwards compatibiltity + # suggestions: annonce, avis, indication, remarque, renseignement + # see also https://sourceforge.net/p/docutils/bugs/453/ + 'encadré': 'sidebar', + 'sujet': 'topic', + 'bloc-textuel': 'line-block', + 'bloc-interprété': 'parsed-literal', + 'code-interprété': 'parsed-literal', + 'code': 'code', + 'math (translation required)': 'math', + 'intertitre': 'rubric', + 'exergue': 'epigraph', + 'épigraphe': 'epigraph', + 'chapeau': 'highlights', + 'accroche': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + 'tableau': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + 'méta': 'meta', + # 'imagemap (translation required)': 'imagemap', + 'image': 'image', + 'figure': 'figure', + 'inclure': 'include', + 'brut': 'raw', + 'remplacer': 'replace', + 'remplace': 'replace', + 'unicode': 'unicode', + 'date': 'date', + 'classe': 'class', + 'role (translation required)': 'role', + 'default-role (translation required)': 'default-role', + 'titre (translation required)': 'title', + 'sommaire': 'contents', + 'table-des-matières': 'contents', + 'sectnum': 'sectnum', + 'section-numérotée': 'sectnum', + 'liens': 'target-notes', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'footnotes (translation required)': 'footnotes', + # 'citations (translation required)': 'citations', + } +"""Mapping of French directive names to registered directive names + +Cf. https://docutils.sourceforge.io/docs/ref/rst/directives.html +and `_directive_registry` in ``directives/__init__.py``. +""" + +roles = { + 'abréviation': 'abbreviation', + 'acronyme': 'acronym', + 'sigle': 'acronym', + 'code': 'code', + 'emphase': 'emphasis', + 'littéral': 'literal', + 'math (translation required)': 'math', + 'pep-référence': 'pep-reference', + 'rfc-référence': 'rfc-reference', + 'fort': 'strong', + 'indice': 'subscript', + 'ind': 'subscript', + 'exposant': 'superscript', + 'exp': 'superscript', + 'titre-référence': 'title-reference', + 'titre': 'title-reference', + 'brut': 'raw', + # the following roles are not implemented in Docutils + 'index': 'index', + 'nommée-référence': 'named-reference', + 'anonyme-référence': 'anonymous-reference', + 'note-référence': 'footnote-reference', + 'citation-référence': 'citation-reference', + 'substitution-référence': 'substitution-reference', + 'lien': 'target', + 'uri-référence': 'uri-reference', + } +"""Mapping of French role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/gl.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/gl.py new file mode 100644 index 00000000..837c3f1f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/gl.py @@ -0,0 +1,106 @@ +# Author: David Goodger +# Contact: goodger@users.sourceforge.net +# Revision: $Revision: 4229 $ +# Date: $Date: 2005-12-23 00:46:16 +0100 (Fri, 23 Dec 2005) $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Galician-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'atención': 'attention', + 'advertencia': 'caution', + 'code (translation required)': 'code', + 'perigo': 'danger', + 'erro': 'error', + 'pista': 'hint', + 'importante': 'important', + 'nota': 'note', + 'consello': 'tip', + 'aviso': 'warning', + 'admonición': 'admonition', # sic! advice/advisory/remark, not reprimand + 'barra lateral': 'sidebar', + 'tópico': 'topic', + 'bloque-liña': 'line-block', + 'literal-analizado': 'parsed-literal', + 'rúbrica': 'rubric', + 'epígrafe': 'epigraph', + 'realzados': 'highlights', + 'coller-citación': 'pull-quote', + 'compor': 'compound', + 'recipiente': 'container', + 'táboa': 'table', + 'táboa-csv': 'csv-table', + 'táboa-listaxe': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + 'imaxe': 'image', + 'figura': 'figure', + 'incluír': 'include', + 'cru': 'raw', + 'substituír': 'replace', + 'unicode': 'unicode', + 'data': 'date', + 'clase': 'class', + 'regra': 'role', + 'regra-predeterminada': 'default-role', + 'título': 'title', + 'contido': 'contents', + 'seccnum': 'sectnum', + 'sección-numerar': 'sectnum', + 'cabeceira': 'header', + 'pé de páxina': 'footer', + 'notas-destino': 'target-notes', + 'texto restruturado-proba-directiva': 'restructuredtext-test-directive', + } +"""Galician name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'abreviatura': 'abbreviation', + 'ab': 'abbreviation', + 'acrónimo': 'acronym', + 'ac': 'acronym', + 'code (translation required)': 'code', + 'índice': 'index', + 'i': 'index', + 'subíndice': 'subscript', + 'sub': 'subscript', + 'superíndice': 'superscript', + 'sup': 'superscript', + 'referencia título': 'title-reference', + 'título': 'title-reference', + 't': 'title-reference', + 'referencia-pep': 'pep-reference', + 'pep': 'pep-reference', + 'referencia-rfc': 'rfc-reference', + 'rfc': 'rfc-reference', + 'énfase': 'emphasis', + 'forte': 'strong', + 'literal': 'literal', + 'math (translation required)': 'math', + 'referencia-nome': 'named-reference', + 'referencia-anónimo': 'anonymous-reference', + 'referencia-nota ao pé': 'footnote-reference', + 'referencia-citación': 'citation-reference', + 'referencia-substitución': 'substitution-reference', + 'destino': 'target', + 'referencia-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'cru': 'raw', + } +"""Mapping of Galician role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/he.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/he.py new file mode 100644 index 00000000..7a5f3bae --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/he.py @@ -0,0 +1,110 @@ +# Author: Meir Kriheli +# Id: $Id: he.py 9452 2023-09-27 00:11:54Z milde $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +English-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'תשומת לב': 'attention', + 'זהירות': 'caution', + 'code (translation required)': 'code', + 'סכנה': 'danger', + 'שגיאה': 'error', + 'רמז': 'hint', + 'חשוב': 'important', + 'הערה': 'note', + 'טיפ': 'tip', + 'אזהרה': 'warning', + 'admonition': 'admonition', + 'sidebar': 'sidebar', + 'topic': 'topic', + 'line-block': 'line-block', + 'parsed-literal': 'parsed-literal', + 'rubric': 'rubric', + 'epigraph': 'epigraph', + 'highlights': 'highlights', + 'pull-quote': 'pull-quote', + 'compound': 'compound', + 'container': 'container', + 'table': 'table', + 'csv-table': 'csv-table', + 'list-table': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + 'תמונה': 'image', + 'figure': 'figure', + 'include': 'include', + 'raw': 'raw', + 'replace': 'replace', + 'unicode': 'unicode', + 'date': 'date', + 'סגנון': 'class', + 'role': 'role', + 'default-role': 'default-role', + 'title': 'title', + 'תוכן': 'contents', + 'sectnum': 'sectnum', + 'section-numbering': 'sectnum', + 'header': 'header', + 'footer': 'footer', + 'target-notes': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive', + # 'questions': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + # 'imagemap': 'imagemap', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + } +"""English name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'abbreviation': 'abbreviation', + 'ab': 'abbreviation', + 'acronym': 'acronym', + 'ac': 'acronym', + 'code (translation required)': 'code', + 'index': 'index', + 'i': 'index', + 'תחתי': 'subscript', + 'sub': 'subscript', + 'עילי': 'superscript', + 'sup': 'superscript', + 'title-reference': 'title-reference', + 'title': 'title-reference', + 't': 'title-reference', + 'pep-reference': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-reference': 'rfc-reference', + 'rfc': 'rfc-reference', + 'emphasis': 'emphasis', + 'strong': 'strong', + 'literal': 'literal', + 'math (translation required)': 'math', + 'named-reference': 'named-reference', + 'anonymous-reference': 'anonymous-reference', + 'footnote-reference': 'footnote-reference', + 'citation-reference': 'citation-reference', + 'substitution-reference': 'substitution-reference', + 'target': 'target', + 'uri-reference': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'raw': 'raw', + } +"""Mapping of English role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/it.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/it.py new file mode 100644 index 00000000..ee5ba829 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/it.py @@ -0,0 +1,99 @@ +# $Id: it.py 9417 2023-06-27 20:04:54Z milde $ +# Authors: Nicola Larosa <docutils@tekNico.net>; +# Lele Gaifax <lele@seldati.it> +# Copyright: This module has been placed in the public domain. + +# Beware: the italian translation of the reStructuredText documentation +# at http://docit.bice.dyndns.org/static/ReST, in particular +# http://docit.bice.dyndns.org/static/ReST/ref/rst/directives.html, needs +# to be synced with the content of this file. + +""" +Italian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + 'attenzione': 'attention', + 'cautela': 'caution', + 'code (translation required)': 'code', + 'pericolo': 'danger', + 'errore': 'error', + 'suggerimento': 'hint', + 'importante': 'important', + 'nota': 'note', + 'consiglio': 'tip', + 'avvertenza': 'warning', + 'avviso': 'admonition', + 'ammonizione': 'admonition', # sic! kept for backards compatibility + 'riquadro': 'sidebar', + 'argomento': 'topic', + 'blocco-di-righe': 'line-block', + 'blocco-interpretato': 'parsed-literal', + 'rubrica': 'rubric', + 'epigrafe': 'epigraph', + 'punti-salienti': 'highlights', + 'estratto-evidenziato': 'pull-quote', + 'composito': 'compound', + 'container (translation required)': 'container', + # 'questions': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'tabella': 'table', + 'tabella-csv': 'csv-table', + 'tabella-elenco': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'immagine': 'image', + 'figura': 'figure', + 'includi': 'include', + 'grezzo': 'raw', + 'sostituisci': 'replace', + 'unicode': 'unicode', + 'data': 'date', + 'classe': 'class', + 'ruolo': 'role', + 'ruolo-predefinito': 'default-role', + 'titolo': 'title', + 'indice': 'contents', + 'contenuti': 'contents', + 'seznum': 'sectnum', + 'sezioni-autonumerate': 'sectnum', + 'annota-riferimenti-esterni': 'target-notes', + 'intestazione': 'header', + 'piede-pagina': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Italian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'abbreviazione': 'abbreviation', + 'acronimo': 'acronym', + 'code (translation required)': 'code', + 'indice': 'index', + 'deponente': 'subscript', + 'esponente': 'superscript', + 'riferimento-titolo': 'title-reference', + 'riferimento-pep': 'pep-reference', + 'riferimento-rfc': 'rfc-reference', + 'enfasi': 'emphasis', + 'forte': 'strong', + 'letterale': 'literal', + 'math (translation required)': 'math', + 'riferimento-con-nome': 'named-reference', + 'riferimento-anonimo': 'anonymous-reference', + 'riferimento-nota': 'footnote-reference', + 'riferimento-citazione': 'citation-reference', + 'riferimento-sostituzione': 'substitution-reference', + 'destinazione': 'target', + 'riferimento-uri': 'uri-reference', + 'grezzo': 'raw', + } +"""Mapping of Italian role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ja.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ja.py new file mode 100644 index 00000000..eef1549d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ja.py @@ -0,0 +1,119 @@ +# $Id: ja.py 9030 2022-03-05 23:28:32Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Japanese-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +# Corrections to these translations are welcome! +# 間違いがあれば、どうぞ正しい翻訳を教えて下さい。 + +directives = { + # language-dependent: fixed + '注目': 'attention', + '注意': 'caution', + 'code (translation required)': 'code', + '危険': 'danger', + 'エラー': 'error', + 'ヒント': 'hint', + '重要': 'important', + '備考': 'note', + '通報': 'tip', + '警告': 'warning', + '戒告': 'admonition', + 'サイドバー': 'sidebar', + 'トピック': 'topic', + 'ラインブロック': 'line-block', + 'パーズドリテラル': 'parsed-literal', + 'ルブリック': 'rubric', + 'エピグラフ': 'epigraph', + '題言': 'epigraph', + 'ハイライト': 'highlights', + '見所': 'highlights', + 'プルクオート': 'pull-quote', + '合成': 'compound', + 'コンテナー': 'container', + '容器': 'container', + '表': 'table', + 'csv表': 'csv-table', + 'リスト表': 'list-table', + # '質問': 'questions', + # '問答': 'questions', + # 'faq': 'questions', + 'math (translation required)': 'math', + 'メタ': 'meta', + # 'イメージマプ': 'imagemap', + 'イメージ': 'image', + '画像': 'image', + 'フィグア': 'figure', + '図版': 'figure', + 'インクルード': 'include', + '含む': 'include', + '組み込み': 'include', + '生': 'raw', + '原': 'raw', + '換える': 'replace', + '取り換える': 'replace', + '掛け替える': 'replace', + 'ユニコード': 'unicode', + '日付': 'date', + 'クラス': 'class', + 'ロール': 'role', + '役': 'role', + 'ディフォルトロール': 'default-role', + '既定役': 'default-role', + 'タイトル': 'title', + '題': 'title', # 題名 件名 + '目次': 'contents', + '節数': 'sectnum', + 'ヘッダ': 'header', + 'フッタ': 'footer', + # '脚注': 'footnotes', # 脚註? + # 'サイテーション': 'citations', # 出典 引証 引用 + 'ターゲットノート': 'target-notes', # 的注 的脚注 + } +"""Japanese name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + '略': 'abbreviation', + '頭字語': 'acronym', + 'code (translation required)': 'code', + 'インデックス': 'index', + '索引': 'index', + '添字': 'subscript', + '下付': 'subscript', + '下': 'subscript', + '上付': 'superscript', + '上': 'superscript', + '題参照': 'title-reference', + 'pep参照': 'pep-reference', + 'rfc参照': 'rfc-reference', + '強調': 'emphasis', + '強い': 'strong', + 'リテラル': 'literal', + '整形済み': 'literal', + 'math (translation required)': 'math', + '名付参照': 'named-reference', + '無名参照': 'anonymous-reference', + '脚注参照': 'footnote-reference', + '出典参照': 'citation-reference', + '代入参照': 'substitution-reference', + '的': 'target', + 'uri参照': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + '生': 'raw', + } +"""Mapping of Japanese role names to canonical role names for interpreted +text.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ka.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ka.py new file mode 100644 index 00000000..e7f18ab7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ka.py @@ -0,0 +1,90 @@ +# $Id: ka.py 9444 2023-08-23 12:02:41Z grubert $ +# Author: Temuri Doghonadze <temuri.doghonadze@gmail.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Georgian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +directives = { + 'ხაზების-ბლოკი': 'line-block', + 'მეტა': 'meta', + 'მათემატიკა': 'math', + 'დამუშავებული-ლიტერალი': 'parsed-literal', + 'გამოყოფილი-ციტატა': 'pull-quote', + 'კოდი': 'code', + 'შერეული': 'compound', + 'კონტეინერი': 'container', + 'ცხრილი': 'table', + 'csv-ცხრილი': 'csv-table', + 'ჩამონათვალი-ცხრილი': 'list-table', + 'დაუმუშავებელი': 'raw', + 'ჩანაცვლება': 'replace', + 'restructuredtext-ის-სატესტო-დირექტივა': 'restructuredtext-test-directive', + 'სამიზნე-შენიშვნები': 'target-notes', + 'უნიკოდი': 'unicode', + 'თარიღი': 'date', + 'გვერდითი-პანელი': 'sidebar', + 'მნიშვნელოვანი': 'important', + 'ჩასმა': 'include', + 'ყურადღება': 'attention', + 'გამოკვეთა': 'highlights', + 'შენიშვნა': 'admonition', + 'გამოსახულება': 'image', + 'კლასი': 'class', + 'როლი': 'role', + 'ნაგულისხმევი-როლი': 'default-role', + 'სათაური': 'title', + 'განყ-ნომერი': 'sectnum', + 'განყ-ნომერი': 'sectnum', + 'საფრთხე': 'danger', + 'ფრთხილად': 'caution', + 'შეცდომა': 'error', + 'მინიშნება': 'tip', + 'ყურადღებით': 'warning', + 'აღნიშვნა': 'note', + 'ფიგურა': 'figure', + 'რუბრიკა': 'rubric', + 'რჩევა': 'hint', + 'შემცველობა': 'contents', + 'თემა': 'topic', + 'ეპიგრაფი': 'epigraph', + 'თავსართი': 'header', + 'ქვედა კოლონტიტული': 'footer', + } +"""Georgian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'აკრონიმი': 'acronym', + 'კოდი': 'code', + 'ანონიმური-მიმართვა': 'anonymous-reference', + 'სიტყვასიტყვითი': 'literal', + 'მათემატიკა': 'math', + 'ზედა-ინდექსი': 'superscript', + 'მახვილი': 'emphasis', + 'სახელიანი-მიმართვა': 'named-reference', + 'ინდექსი': 'index', + 'ქვედა-ინდექსი': 'subscript', + 'სქელი-ფონტი': 'strong', + 'აბრევიატურა': 'abbreviation', + 'ჩანაცვლების-მიმართვა': 'substitution-reference', + 'pep-მიმართვა': 'pep-reference', + 'rfc-მიმართვა ': 'rfc-reference', + 'uri-მიმართვა': 'uri-reference', + 'title-მიმართვა': 'title-reference', + 'ქვედა-კოლონტიტულზე-მიმართვა': 'footnote-reference', + 'ციტატაზე-მიმართვა': 'citation-reference', + 'სამიზნე': 'target', + 'დაუმუშავებელი': 'raw', + } +"""Mapping of Georgian role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ko.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ko.py new file mode 100644 index 00000000..434fea12 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ko.py @@ -0,0 +1,111 @@ +# $Id: ko.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Thomas SJ Kang <thomas.kangsj@ujuc.kr> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Korean-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + '집중': 'attention', + '주의': 'caution', + '코드': 'code', + '코드-블록': 'code', + '소스코드': 'code', + '위험': 'danger', + '오류': 'error', + '실마리': 'hint', + '중요한': 'important', + '비고': 'note', + '팁': 'tip', + '경고': 'warning', + '권고': 'admonition', + '사이드바': 'sidebar', + '주제': 'topic', + '라인-블록': 'line-block', + '파싱된-리터럴': 'parsed-literal', + '지시문': 'rubric', + '제명': 'epigraph', + '하이라이트': 'highlights', + '발췌문': 'pull-quote', + '합성어': 'compound', + '컨테이너': 'container', + # '질문': 'questions', + '표': 'table', + 'csv-표': 'csv-table', + 'list-표': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + '메타': 'meta', + '수학': 'math', + # '이미지맵': 'imagemap', + '이미지': 'image', + '도표': 'figure', + '포함': 'include', + 'raw': 'raw', + '대신하다': 'replace', + '유니코드': 'unicode', + '날짜': 'date', + '클래스': 'class', + '역할': 'role', + '기본-역할': 'default-role', + '제목': 'title', + '내용': 'contents', + 'sectnum': 'sectnum', + '섹션-번호-매기기': 'sectnum', + '머리말': 'header', + '꼬리말': 'footer', + # '긱주': 'footnotes', + # '인용구': 'citations', + '목표-노트': 'target-notes', + 'restructuredtext 테스트 지시어': 'restructuredtext-test-directive'} +"""Korean name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + '약어': 'abbreviation', + 'ab': 'abbreviation', + '두음문자': 'acronym', + 'ac': 'acronym', + '코드': 'code', + '색인': 'index', + 'i': 'index', + '다리-글자': 'subscript', + 'sub': 'subscript', + '어깨-글자': 'superscript', + 'sup': 'superscript', + '제목-참조': 'title-reference', + '제목': 'title-reference', + 't': 'title-reference', + 'pep-참조': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-참조': 'rfc-reference', + 'rfc': 'rfc-reference', + '강조': 'emphasis', + '굵게': 'strong', + '기울기': 'literal', + '수학': 'math', + '명명된-참조': 'named-reference', + '익명-참조': 'anonymous-reference', + '각주-참조': 'footnote-reference', + '인용-참조': 'citation-reference', + '대리-참조': 'substitution-reference', + '대상': 'target', + 'uri-참조': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'raw': 'raw', + } +"""Mapping of Korean role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/lt.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/lt.py new file mode 100644 index 00000000..7d324d67 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/lt.py @@ -0,0 +1,109 @@ +# $Id: lt.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Dalius Dobravolskas <dalius.do...@gmail.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Lithuanian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'dėmesio': 'attention', + 'atsargiai': 'caution', + 'code (translation required)': 'code', + 'pavojinga': 'danger', + 'klaida': 'error', + 'užuomina': 'hint', + 'svarbu': 'important', + 'pastaba': 'note', + 'patarimas': 'tip', + 'įspėjimas': 'warning', + 'perspėjimas': 'admonition', + 'šoninė-juosta': 'sidebar', + 'tema': 'topic', + 'linijinis-blokas': 'line-block', + 'išanalizuotas-literalas': 'parsed-literal', + 'rubrika': 'rubric', + 'epigrafas': 'epigraph', + 'pagridiniai-momentai': 'highlights', + 'atitraukta-citata': 'pull-quote', + 'sudėtinis-darinys': 'compound', + 'konteineris': 'container', + # 'questions': 'questions', + 'lentelė': 'table', + 'csv-lentelė': 'csv-table', + 'sąrašo-lentelė': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + 'meta': 'meta', + 'matematika': 'math', + # 'imagemap': 'imagemap', + 'paveiksliukas': 'image', + 'iliustracija': 'figure', + 'pridėti': 'include', + 'žalia': 'raw', + 'pakeisti': 'replace', + 'unikodas': 'unicode', + 'data': 'date', + 'klasė': 'class', + 'rolė': 'role', + 'numatytoji-rolė': 'default-role', + 'titulas': 'title', + 'turinys': 'contents', + 'seknum': 'sectnum', + 'sekcijos-numeravimas': 'sectnum', + 'antraštė': 'header', + 'poraštė': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'nutaikytos-pastaba': 'target-notes', + 'restructuredtext-testinė-direktyva': 'restructuredtext-test-directive'} +"""Lithuanian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'santrumpa': 'abbreviation', + 'sa': 'abbreviation', + 'akronimas': 'acronym', + 'ak': 'acronym', + 'code (translation required)': 'code', + 'indeksas': 'index', + 'i': 'index', + 'apatinis-indeksas': 'subscript', + 'sub': 'subscript', + 'viršutinis-indeksas': 'superscript', + 'sup': 'superscript', + 'antrašės-nuoroda': 'title-reference', + 'antraštė': 'title-reference', + 'a': 'title-reference', + 'pep-nuoroda': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-nuoroda': 'rfc-reference', + 'rfc': 'rfc-reference', + 'paryškinimas': 'emphasis', + 'sustiprintas': 'strong', + 'literalas': 'literal', + 'matematika': 'math', + 'vardinė-nuoroda': 'named-reference', + 'anoniminė-nuoroda': 'anonymous-reference', + 'išnašos-nuoroda': 'footnote-reference', + 'citatos-nuoroda': 'citation-reference', + 'pakeitimo-nuoroda': 'substitution-reference', + 'taikinys': 'target', + 'uri-nuoroda': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'žalia': 'raw', + } +"""Mapping of English role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/lv.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/lv.py new file mode 100644 index 00000000..18e4dc49 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/lv.py @@ -0,0 +1,108 @@ +# $Id: lv.py 9030 2022-03-05 23:28:32Z milde $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Latvian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'uzmanību': 'attention', + 'piesardzību': 'caution', + 'kods': 'code', + 'koda-bloks': 'code', + 'pirmkods': 'code', + 'bīstami': 'danger', + 'kļūda': 'error', + 'ieteikums': 'hint', + 'svarīgi': 'important', + 'piezīme': 'note', + 'padoms': 'tip', + 'brīdinājums': 'warning', + 'aizrādījums': 'admonition', + 'sānjosla': 'sidebar', + 'tēma': 'topic', + 'rindu-bloks': 'line-block', + 'parsēts-literālis': 'parsed-literal', + 'rubrika': 'rubric', + 'epigrāfs': 'epigraph', + 'apskats': 'highlights', + 'izvilkuma-citāts': 'pull-quote', + 'savienojums': 'compound', + 'konteiners': 'container', + # 'questions': 'questions', + 'tabula': 'table', + 'csv-tabula': 'csv-table', + 'sarakstveida-tabula': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + 'meta': 'meta', + 'matemātika': 'math', + # 'imagemap': 'imagemap', + 'attēls': 'image', + 'figūra': 'figure', + 'ietvert': 'include', + 'burtiski': 'raw', + 'aizvieto': 'replace', + 'unicode': 'unicode', + 'datums': 'date', + 'klase': 'class', + 'role': 'role', + 'noklusējuma-role': 'default-role', + 'virsraksts': 'title', + 'saturs': 'contents', + 'numurēt-sekcijas': 'sectnum', + 'galvene': 'header', + 'kājene': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'atsauces-apakšā': 'target-notes', + 'restructuredtext-testa-direktīva': 'restructuredtext-test-directive'} +"""English name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'saīsinājums': 'abbreviation', + 'īsi': 'abbreviation', + 'akronīms': 'acronym', + 'kods': 'code', + 'indekss': 'index', + 'i': 'index', + 'apakšraksts': 'subscript', + 'apakšā': 'subscript', + 'augšraksts': 'superscript', + 'augšā': 'superscript', + 'virsraksta-atsauce': 'title-reference', + 'virsraksts': 'title-reference', + 'v': 'title-reference', + 'atsauce-uz-pep': 'pep-reference', + 'pep': 'pep-reference', + 'atsauce-uz-rfc': 'rfc-reference', + 'rfc': 'rfc-reference', + 'izcēlums': 'emphasis', + 'blīvs': 'strong', + 'literālis': 'literal', + 'matemātika': 'math', + 'nosaukta-atsauce': 'named-reference', + 'nenosaukta-atsauce': 'anonymous-reference', + 'kājenes-atsauce': 'footnote-reference', + 'citātā-atsauce': 'citation-reference', + 'aizvietojuma-atsauce': 'substitution-reference', + 'mēr''kis': 'target', + 'atsauce-uz-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'burtiski': 'raw', + } +"""Mapping of English role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/nl.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/nl.py new file mode 100644 index 00000000..762ddcf2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/nl.py @@ -0,0 +1,114 @@ +# $Id: nl.py 9417 2023-06-27 20:04:54Z milde $ +# Author: Martijn Pieters <mjpieters@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Dutch-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'attentie': 'attention', + 'let-op': 'caution', + 'code (translation required)': 'code', + 'gevaar': 'danger', + 'fout': 'error', + 'hint': 'hint', + 'belangrijk': 'important', + 'opmerking': 'note', + 'tip': 'tip', + 'waarschuwing': 'warning', + 'advies': 'admonition', + 'aanmaning': 'admonition', # sic! kept for backwards compatibiltity + 'katern': 'sidebar', + 'onderwerp': 'topic', + 'lijn-blok': 'line-block', + 'letterlijk-ontleed': 'parsed-literal', + 'rubriek': 'rubric', + 'opschrift': 'epigraph', + 'hoogtepunten': 'highlights', + 'pull-quote': 'pull-quote', # Dutch printers use the english term + 'samenstelling': 'compound', + 'verbinding': 'compound', + 'container (translation required)': 'container', + # 'vragen': 'questions', + 'tabel': 'table', + 'csv-tabel': 'csv-table', + 'lijst-tabel': 'list-table', + # 'veelgestelde-vragen': 'questions', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'beeld': 'image', + 'figuur': 'figure', + 'opnemen': 'include', + 'onbewerkt': 'raw', + 'vervang': 'replace', + 'vervanging': 'replace', + 'unicode': 'unicode', + 'datum': 'date', + 'klasse': 'class', + 'rol': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'inhoud': 'contents', + 'sectnum': 'sectnum', + 'sectie-nummering': 'sectnum', + 'hoofdstuk-nummering': 'sectnum', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'voetnoten': 'footnotes', + # 'citaten': 'citations', + 'verwijzing-voetnoten': 'target-notes', + 'restructuredtext-test-instructie': 'restructuredtext-test-directive'} +"""Dutch name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'afkorting': 'abbreviation', + # 'ab': 'abbreviation', + 'acroniem': 'acronym', + 'ac': 'acronym', + 'code (translation required)': 'code', + 'index': 'index', + 'i': 'index', + 'inferieur': 'subscript', + 'inf': 'subscript', + 'superieur': 'superscript', + 'sup': 'superscript', + 'titel-referentie': 'title-reference', + 'titel': 'title-reference', + 't': 'title-reference', + 'pep-referentie': 'pep-reference', + 'pep': 'pep-reference', + 'rfc-referentie': 'rfc-reference', + 'rfc': 'rfc-reference', + 'nadruk': 'emphasis', + 'extra': 'strong', + 'extra-nadruk': 'strong', + 'vet': 'strong', + 'letterlijk': 'literal', + 'math (translation required)': 'math', + 'benoemde-referentie': 'named-reference', + 'anonieme-referentie': 'anonymous-reference', + 'voetnoot-referentie': 'footnote-reference', + 'citaat-referentie': 'citation-reference', + 'substitie-reference': 'substitution-reference', + 'verwijzing': 'target', + 'uri-referentie': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'onbewerkt': 'raw', + } +"""Mapping of Dutch role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/pl.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/pl.py new file mode 100644 index 00000000..9aac2d42 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/pl.py @@ -0,0 +1,101 @@ +# $Id$ +# Author: Robert Wojciechowicz <rw@smsnet.pl> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Polish-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'uwaga': 'attention', + 'ostrożnie': 'caution', + 'code (translation required)': 'code', + 'niebezpieczeństwo': 'danger', + 'błąd': 'error', + 'wskazówka': 'hint', + 'ważne': 'important', + 'przypis': 'note', + 'rada': 'tip', + 'ostrzeżenie': 'warning', + 'zauważenie': 'admonition', # remark + 'upomnienie': 'admonition', # sic! kept for backwards compatibiltity + 'ramka': 'sidebar', + 'temat': 'topic', + 'blok-linii': 'line-block', + 'sparsowany-literał': 'parsed-literal', + 'rubryka': 'rubric', + 'epigraf': 'epigraph', + 'highlights': 'highlights', # FIXME no polish equivalent? + 'pull-quote': 'pull-quote', # FIXME no polish equivalent? + 'złożony': 'compound', + 'kontener': 'container', + # 'questions': 'questions', + 'tabela': 'table', + 'tabela-csv': 'csv-table', + 'tabela-listowa': 'list-table', + # 'qa': 'questions', + # 'faq': 'questions', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'obraz': 'image', + 'rycina': 'figure', + 'dołącz': 'include', + 'surowe': 'raw', + 'zastąp': 'replace', + 'unikod': 'unicode', + 'data': 'date', + 'klasa': 'class', + 'rola': 'role', + 'rola-domyślna': 'default-role', + 'tytuł': 'title', + 'treść': 'contents', + 'sectnum': 'sectnum', + 'numeracja-sekcji': 'sectnum', + 'nagłówek': 'header', + 'stopka': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + 'target-notes': 'target-notes', # FIXME no polish equivalent? + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Polish name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'skrót': 'abbreviation', + 'akronim': 'acronym', + 'code (translation required)': 'code', + 'indeks': 'index', + 'indeks-dolny': 'subscript', + 'indeks-górny': 'superscript', + 'referencja-tytuł': 'title-reference', + 'referencja-pep': 'pep-reference', + 'referencja-rfc': 'rfc-reference', + 'podkreślenie': 'emphasis', + 'wytłuszczenie': 'strong', + 'dosłownie': 'literal', + 'math (translation required)': 'math', + 'referencja-nazwana': 'named-reference', + 'referencja-anonimowa': 'anonymous-reference', + 'referencja-przypis': 'footnote-reference', + 'referencja-cytat': 'citation-reference', + 'referencja-podstawienie': 'substitution-reference', + 'cel': 'target', + 'referencja-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'surowe': 'raw', + } +"""Mapping of Polish role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/pt_br.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/pt_br.py new file mode 100644 index 00000000..45a670db --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/pt_br.py @@ -0,0 +1,110 @@ +# $Id: pt_br.py 9452 2023-09-27 00:11:54Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Brazilian Portuguese-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'atenção': 'attention', + 'cuidado': 'caution', + 'code (translation required)': 'code', + 'perigo': 'danger', + 'erro': 'error', + 'sugestão': 'hint', + 'importante': 'important', + 'nota': 'note', + 'dica': 'tip', + 'aviso': 'warning', + 'advertência': 'admonition', + 'exortação': 'admonition', # sic! advice/advisory/remark, not reprimand + 'barra-lateral': 'sidebar', + 'tópico': 'topic', + 'bloco-de-linhas': 'line-block', + 'literal-interpretado': 'parsed-literal', + 'rubrica': 'rubric', + 'epígrafo': 'epigraph', + 'destaques': 'highlights', + 'citação-destacada': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'perguntas': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'table (translation required)': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'imagem': 'image', + 'figura': 'figure', + 'inclusão': 'include', + 'cru': 'raw', + 'substituição': 'replace', + 'unicode': 'unicode', + 'data': 'date', + 'classe': 'class', + 'role (translation required)': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'índice': 'contents', + 'numsec': 'sectnum', + 'numeração-de-seções': 'sectnum', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'notas-de-rorapé': 'footnotes', + # 'citações': 'citations', + 'links-no-rodapé': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Brazilian Portuguese name to registered (in directives/__init__.py) +directive name mapping.""" + +roles = { + # language-dependent: fixed + 'abbreviação': 'abbreviation', + 'ab': 'abbreviation', + 'acrônimo': 'acronym', + 'ac': 'acronym', + 'code (translation required)': 'code', + 'índice-remissivo': 'index', + 'i': 'index', + 'subscrito': 'subscript', + 'sub': 'subscript', + 'sobrescrito': 'superscript', + 'sob': 'superscript', + 'referência-a-título': 'title-reference', + 'título': 'title-reference', + 't': 'title-reference', + 'referência-a-pep': 'pep-reference', + 'pep': 'pep-reference', + 'referência-a-rfc': 'rfc-reference', + 'rfc': 'rfc-reference', + 'ênfase': 'emphasis', + 'forte': 'strong', + 'literal': 'literal', + 'math (translation required)': 'math', # translation required? + 'referência-por-nome': 'named-reference', + 'referência-anônima': 'anonymous-reference', + 'referência-a-nota-de-rodapé': 'footnote-reference', + 'referência-a-citação': 'citation-reference', + 'referência-a-substituição': 'substitution-reference', + 'alvo': 'target', + 'referência-a-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'cru': 'raw', + } +"""Mapping of Brazilian Portuguese role names to canonical role names +for interpreted text.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ru.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ru.py new file mode 100644 index 00000000..7c84cd02 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/ru.py @@ -0,0 +1,90 @@ +# $Id: ru.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Roman Suzi <rnd@onego.ru> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Russian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +directives = { + 'блок-строк': 'line-block', + 'meta': 'meta', + 'математика': 'math', + 'обработанный-литерал': 'parsed-literal', + 'выделенная-цитата': 'pull-quote', + 'код': 'code', + 'compound (translation required)': 'compound', + 'контейнер': 'container', + 'таблица': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + 'сырой': 'raw', + 'замена': 'replace', + 'тестовая-директива-restructuredtext': 'restructuredtext-test-directive', + 'целевые-сноски': 'target-notes', + 'unicode': 'unicode', + 'дата': 'date', + 'боковая-полоса': 'sidebar', + 'важно': 'important', + 'включать': 'include', + 'внимание': 'attention', + 'выделение': 'highlights', + 'замечание': 'admonition', + 'изображение': 'image', + 'класс': 'class', + 'роль': 'role', + 'default-role (translation required)': 'default-role', + 'титул': 'title', + 'номер-раздела': 'sectnum', + 'нумерация-разделов': 'sectnum', + 'опасно': 'danger', + 'осторожно': 'caution', + 'ошибка': 'error', + 'подсказка': 'tip', + 'предупреждение': 'warning', + 'примечание': 'note', + 'рисунок': 'figure', + 'рубрика': 'rubric', + 'совет': 'hint', + 'содержание': 'contents', + 'тема': 'topic', + 'эпиграф': 'epigraph', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + } +"""Russian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'акроним': 'acronym', + 'код': 'code', + 'анонимная-ссылка': 'anonymous-reference', + 'буквально': 'literal', + 'математика': 'math', + 'верхний-индекс': 'superscript', + 'выделение': 'emphasis', + 'именованная-ссылка': 'named-reference', + 'индекс': 'index', + 'нижний-индекс': 'subscript', + 'сильное-выделение': 'strong', + 'сокращение': 'abbreviation', + 'ссылка-замена': 'substitution-reference', + 'ссылка-на-pep': 'pep-reference', + 'ссылка-на-rfc': 'rfc-reference', + 'ссылка-на-uri': 'uri-reference', + 'ссылка-на-заглавие': 'title-reference', + 'ссылка-на-сноску': 'footnote-reference', + 'цитатная-ссылка': 'citation-reference', + 'цель': 'target', + 'сырой': 'raw', + } +"""Mapping of Russian role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/sk.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/sk.py new file mode 100644 index 00000000..7b1cef82 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/sk.py @@ -0,0 +1,96 @@ +# $Id: sk.py 9452 2023-09-27 00:11:54Z milde $ +# Author: Miroslav Vasko <zemiak@zoznam.sk> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Slovak-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + 'pozor': 'attention', + 'opatrne': 'caution', + 'code (translation required)': 'code', + 'nebezpe\xe8enstvo': 'danger', + 'chyba': 'error', + 'rada': 'hint', + 'd\xf4le\x9eit\xe9': 'important', + 'pozn\xe1mka': 'note', + 'tip (translation required)': 'tip', + 'varovanie': 'warning', + 'admonition (translation required)': 'admonition', + 'sidebar (translation required)': 'sidebar', + 't\xe9ma': 'topic', + 'blok-riadkov': 'line-block', + 'parsed-literal': 'parsed-literal', + 'rubric (translation required)': 'rubric', + 'epigraph (translation required)': 'epigraph', + 'highlights (translation required)': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'questions': 'questions', + # 'qa': 'questions', + # 'faq': 'questions', + 'table (translation required)': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + 'meta': 'meta', + 'math (translation required)': 'math', + # 'imagemap': 'imagemap', + 'obr\xe1zok': 'image', + 'tvar': 'figure', + 'vlo\x9ei\x9d': 'include', + 'raw (translation required)': 'raw', + 'nahradi\x9d': 'replace', + 'unicode': 'unicode', + 'dátum': 'date', + 'class (translation required)': 'class', + 'role (translation required)': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'obsah': 'contents', + '\xe8as\x9d': 'sectnum', + '\xe8as\x9d-\xe8\xedslovanie': 'sectnum', + 'cie\xbeov\xe9-pozn\xe1mky': 'target-notes', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'footnotes': 'footnotes', + # 'citations': 'citations', + } +"""Slovak name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'abbreviation (translation required)': 'abbreviation', + 'acronym (translation required)': 'acronym', + 'code (translation required)': 'code', + 'index (translation required)': 'index', + 'subscript (translation required)': 'subscript', + 'superscript (translation required)': 'superscript', + 'title-reference (translation required)': 'title-reference', + 'pep-reference (translation required)': 'pep-reference', + 'rfc-reference (translation required)': 'rfc-reference', + 'emphasis (translation required)': 'emphasis', + 'strong (translation required)': 'strong', + 'literal (translation required)': 'literal', + 'math (translation required)': 'math', + 'named-reference (translation required)': 'named-reference', + 'anonymous-reference (translation required)': 'anonymous-reference', + 'footnote-reference (translation required)': 'footnote-reference', + 'citation-reference (translation required)': 'citation-reference', + 'substitution-reference (translation required)': 'substitution-reference', # noqa:E501 + 'target (translation required)': 'target', + 'uri-reference (translation required)': 'uri-reference', + 'raw (translation required)': 'raw', + } +"""Mapping of Slovak role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/sv.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/sv.py new file mode 100644 index 00000000..e6b11aea --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/sv.py @@ -0,0 +1,96 @@ +# $Id: sv.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Adam Chodorowski <chodorowski@users.sourceforge.net> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Swedish language mappings for language-dependent features of reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +directives = { + 'observera': 'attention', + 'akta': 'caution', # also 'försiktigt' + 'kod': 'code', + 'fara': 'danger', + 'fel': 'error', + 'vink': 'hint', # also 'hint' + 'viktigt': 'important', + 'notera': 'note', + 'tips': 'tip', + 'varning': 'warning', + 'anmärkning': 'admonition', # literal 'tillrättavisning', 'förmaning' + 'sidorad': 'sidebar', + 'ämne': 'topic', + 'tema': 'topic', + 'rad-block': 'line-block', + # 'tolkad-bokstavlig'? + 'parsed-literal (translation required)': 'parsed-literal', + 'rubrik': 'rubric', + 'epigraf': 'epigraph', + 'höjdpunkter': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + 'sammansatt': 'compound', + 'container': 'container', + # 'frågor': 'questions', + # NOTE: A bit long, but recommended by http://www.nada.kth.se/dataterm/: + # 'frågor-och-svar': 'questions', + # 'vanliga-frågor': 'questions', + 'tabell': 'table', + 'csv-tabell': 'csv-table', + 'list-tabell': 'list-table', + 'meta': 'meta', + 'matematik': 'math', + # 'bildkarta': 'imagemap', # FIXME: Translation might be too literal. + 'bild': 'image', + 'figur': 'figure', + 'inkludera': 'include', + 'rå': 'raw', + 'ersätta': 'replace', + 'unicode': 'unicode', + 'datum': 'date', + 'klass': 'class', + 'roll': 'role', + 'standardroll': 'default-role', + 'titel': 'title', + 'innehåll': 'contents', + 'sektionsnumrering': 'sectnum', + 'target-notes (translation required)': 'target-notes', + 'sidhuvud': 'header', + 'sidfot': 'footer', + # 'fotnoter': 'footnotes', + # 'citeringar': 'citations', + } +"""Swedish name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'förkortning': 'abbreviation', + 'akronym': 'acronym', + 'kod': 'code', + 'index': 'index', + 'nedsänkt': 'subscript', + 'upphöjd': 'superscript', + 'titel-referens': 'title-reference', + 'pep-referens': 'pep-reference', + 'rfc-referens': 'rfc-reference', + 'betoning': 'emphasis', + 'stark': 'strong', + 'bokstavlig': 'literal', # also 'ordagranna' + 'matematik': 'math', + 'namngiven-referens': 'named-reference', + 'anonym-referens': 'anonymous-reference', + 'fotnot-referens': 'footnote-reference', + 'citat-referens': 'citation-reference', + 'ersättnings-referens': 'substitution-reference', + 'mål': 'target', + 'uri-referens': 'uri-reference', + 'rå': 'raw', + } +"""Mapping of Swedish role names to canonical role names for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/uk.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/uk.py new file mode 100644 index 00000000..9e74dc32 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/uk.py @@ -0,0 +1,91 @@ +# $Id: uk.py 9114 2022-07-28 17:06:10Z milde $ +# Author: Dmytro Kazanzhy <dkazanzhy@gmail.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be +# translated for each language: one in docutils/languages, the other in +# docutils/parsers/rst/languages. + +""" +Ukrainian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + +directives = { + 'блок-строк': 'line-block', + 'мета': 'meta', + 'математика': 'math', + 'оброблений-літерал': 'parsed-literal', + 'виділена-цитата': 'pull-quote', + 'код': 'code', + 'складений абзац': 'compound', + 'контейнер': 'container', + 'таблиця': 'table', + 'таблиця-csv': 'csv-table', + 'таблиця-списків': 'list-table', + 'сирий': 'raw', + 'заміна': 'replace', + 'тестова-директива-restructuredtext': 'restructuredtext-test-directive', + 'цільові-виноски': 'target-notes', + 'юнікод': 'unicode', + 'дата': 'date', + 'бічна-панель': 'sidebar', + 'важливо': 'important', + 'включати': 'include', + 'увага': 'attention', + 'виділення': 'highlights', + 'зауваження': 'admonition', + 'зображення': 'image', + 'клас': 'class', + 'роль': 'role', + 'роль-за-замовчуванням': 'default-role', + 'заголовок': 'title', + 'номер-розділу': 'sectnum', + 'нумерація-розділів': 'sectnum', + 'небезпечно': 'danger', + 'обережно': 'caution', + 'помилка': 'error', + 'підказка': 'tip', + 'попередження': 'warning', + 'примітка': 'note', + 'малюнок': 'figure', + 'рубрика': 'rubric', + 'порада': 'hint', + 'зміст': 'contents', + 'тема': 'topic', + 'епіграф': 'epigraph', + 'верхній колонтитул': 'header', + 'нижній колонтитул': 'footer', + } +"""Ukrainian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + 'акронім': 'acronym', + 'код': 'code', + 'анонімне-посилання': 'anonymous-reference', + 'буквально': 'literal', + 'математика': 'math', + 'верхній-індекс': 'superscript', + 'наголос': 'emphasis', + 'іменоване-посилання': 'named-reference', + 'індекс': 'index', + 'нижній-індекс': 'subscript', + 'жирне-накреслення': 'strong', + 'скорочення': 'abbreviation', + 'посилання-заміна': 'substitution-reference', + 'посилання-на-pep': 'pep-reference', + 'посилання-на-rfc': 'rfc-reference', + 'посилання-на-uri': 'uri-reference', + 'посилання-на-заголовок': 'title-reference', + 'посилання-на-зноску': 'footnote-reference', + 'посилання-на-цитату': 'citation-reference', + 'ціль': 'target', + 'сирий': 'raw', + } +"""Mapping of Ukrainian role names to canonical role names +for interpreted text. +""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/zh_cn.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/zh_cn.py new file mode 100644 index 00000000..fbb6fd8c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/zh_cn.py @@ -0,0 +1,104 @@ +# $Id: zh_cn.py 9030 2022-03-05 23:28:32Z milde $ +# Author: Panjunyong <panjy@zopechina.com> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Simplified Chinese language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + '注意': 'attention', + '小心': 'caution', + 'code (translation required)': 'code', + '危险': 'danger', + '错误': 'error', + '提示': 'hint', + '重要': 'important', + '注解': 'note', + '技巧': 'tip', + '警告': 'warning', + '忠告': 'admonition', + '侧框': 'sidebar', + '主题': 'topic', + 'line-block (translation required)': 'line-block', + 'parsed-literal (translation required)': 'parsed-literal', + '醒目': 'rubric', + '铭文': 'epigraph', + '要点': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + '复合': 'compound', + '容器': 'container', + # 'questions (translation required)': 'questions', + '表格': 'table', + 'csv表格': 'csv-table', + '列表表格': 'list-table', + # 'qa (translation required)': 'questions', + # 'faq (translation required)': 'questions', + '元数据': 'meta', + 'math (translation required)': 'math', + # 'imagemap (translation required)': 'imagemap', + '图片': 'image', + '图例': 'figure', + '包含': 'include', + '原文': 'raw', + '代替': 'replace', + '统一码': 'unicode', + '日期': 'date', + '类型': 'class', + '角色': 'role', + '默认角色': 'default-role', + '标题': 'title', + '目录': 'contents', + '章节序号': 'sectnum', + '题头': 'header', + '页脚': 'footer', + # 'footnotes (translation required)': 'footnotes', + # 'citations (translation required)': 'citations', + 'target-notes (translation required)': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Simplified Chinese name to registered (in directives/__init__.py) +directive name mapping.""" + +roles = { + # language-dependent: fixed + '缩写': 'abbreviation', + '简称': 'acronym', + 'code (translation required)': 'code', + 'index (translation required)': 'index', + 'i (translation required)': 'index', + '下标': 'subscript', + '上标': 'superscript', + 'title-reference (translation required)': 'title-reference', + 'title (translation required)': 'title-reference', + 't (translation required)': 'title-reference', + 'pep-reference (translation required)': 'pep-reference', + 'pep (translation required)': 'pep-reference', + 'rfc-reference (translation required)': 'rfc-reference', + 'rfc (translation required)': 'rfc-reference', + '强调': 'emphasis', + '加粗': 'strong', + '字面': 'literal', + 'math (translation required)': 'math', + 'named-reference (translation required)': 'named-reference', + 'anonymous-reference (translation required)': 'anonymous-reference', + 'footnote-reference (translation required)': 'footnote-reference', + 'citation-reference (translation required)': 'citation-reference', + 'substitution-reference (translation required)': 'substitution-reference', + 'target (translation required)': 'target', + 'uri-reference (translation required)': 'uri-reference', + 'uri (translation required)': 'uri-reference', + 'url (translation required)': 'uri-reference', + 'raw (translation required)': 'raw', + } +"""Mapping of Simplified Chinese role names to canonical role names +for interpreted text.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/zh_tw.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/zh_tw.py new file mode 100644 index 00000000..126255d0 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/languages/zh_tw.py @@ -0,0 +1,109 @@ +# $Id: zh_tw.py 9030 2022-03-05 23:28:32Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <https://docutils.sourceforge.io/docs/howto/i18n.html>. +# Two files must be translated for each language: one in docutils/languages, +# the other in docutils/parsers/rst/languages. + +""" +Traditional Chinese language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'attention (translation required)': 'attention', + 'caution (translation required)': 'caution', + 'code (translation required)': 'code', + 'danger (translation required)': 'danger', + 'error (translation required)': 'error', + 'hint (translation required)': 'hint', + 'important (translation required)': 'important', + 'note (translation required)': 'note', + 'tip (translation required)': 'tip', + 'warning (translation required)': 'warning', + 'admonition (translation required)': 'admonition', + 'sidebar (translation required)': 'sidebar', + 'topic (translation required)': 'topic', + 'line-block (translation required)': 'line-block', + 'parsed-literal (translation required)': 'parsed-literal', + 'rubric (translation required)': 'rubric', + 'epigraph (translation required)': 'epigraph', + 'highlights (translation required)': 'highlights', + 'pull-quote (translation required)': 'pull-quote', + 'compound (translation required)': 'compound', + 'container (translation required)': 'container', + # 'questions (translation required)': 'questions', + 'table (translation required)': 'table', + 'csv-table (translation required)': 'csv-table', + 'list-table (translation required)': 'list-table', + # 'qa (translation required)': 'questions', + # 'faq (translation required)': 'questions', + 'meta (translation required)': 'meta', + 'math (translation required)': 'math', + # 'imagemap (translation required)': 'imagemap', + 'image (translation required)': 'image', + 'figure (translation required)': 'figure', + 'include (translation required)': 'include', + 'raw (translation required)': 'raw', + 'replace (translation required)': 'replace', + 'unicode (translation required)': 'unicode', + '日期': 'date', + 'class (translation required)': 'class', + 'role (translation required)': 'role', + 'default-role (translation required)': 'default-role', + 'title (translation required)': 'title', + 'contents (translation required)': 'contents', + 'sectnum (translation required)': 'sectnum', + 'section-numbering (translation required)': 'sectnum', + 'header (translation required)': 'header', + 'footer (translation required)': 'footer', + # 'footnotes (translation required)': 'footnotes', + # 'citations (translation required)': 'citations', + 'target-notes (translation required)': 'target-notes', + 'restructuredtext-test-directive': 'restructuredtext-test-directive'} +"""Traditional Chinese name to registered (in directives/__init__.py) +directive name mapping.""" + +roles = { + # language-dependent: fixed + 'abbreviation (translation required)': 'abbreviation', + 'ab (translation required)': 'abbreviation', + 'acronym (translation required)': 'acronym', + 'ac (translation required)': 'acronym', + 'code (translation required)': 'code', + 'index (translation required)': 'index', + 'i (translation required)': 'index', + 'subscript (translation required)': 'subscript', + 'sub (translation required)': 'subscript', + 'superscript (translation required)': 'superscript', + 'sup (translation required)': 'superscript', + 'title-reference (translation required)': 'title-reference', + 'title (translation required)': 'title-reference', + 't (translation required)': 'title-reference', + 'pep-reference (translation required)': 'pep-reference', + 'pep (translation required)': 'pep-reference', + 'rfc-reference (translation required)': 'rfc-reference', + 'rfc (translation required)': 'rfc-reference', + 'emphasis (translation required)': 'emphasis', + 'strong (translation required)': 'strong', + 'literal (translation required)': 'literal', + 'math (translation required)': 'math', + 'named-reference (translation required)': 'named-reference', + 'anonymous-reference (translation required)': 'anonymous-reference', + 'footnote-reference (translation required)': 'footnote-reference', + 'citation-reference (translation required)': 'citation-reference', + 'substitution-reference (translation required)': 'substitution-reference', + 'target (translation required)': 'target', + 'uri-reference (translation required)': 'uri-reference', + 'uri (translation required)': 'uri-reference', + 'url (translation required)': 'uri-reference', + 'raw (translation required)': 'raw', + } +"""Mapping of Traditional Chinese role names to canonical role names for +interpreted text.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/roles.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/roles.py new file mode 100644 index 00000000..657a86ef --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/roles.py @@ -0,0 +1,439 @@ +# $Id: roles.py 9037 2022-03-05 23:31:10Z milde $ +# Author: Edward Loper <edloper@gradient.cis.upenn.edu> +# Copyright: This module has been placed in the public domain. + +""" +This module defines standard interpreted text role functions, a registry for +interpreted text roles, and an API for adding to and retrieving from the +registry. See also `Creating reStructuredText Interpreted Text Roles`__. + +__ https://docutils.sourceforge.io/docs/ref/rst/roles.html + + +The interface for interpreted role functions is as follows:: + + def role_fn(name, rawtext, text, lineno, inliner, + options=None, content=None): + code... + + # Set function attributes for customization: + role_fn.options = ... + role_fn.content = ... + +Parameters: + +- ``name`` is the local name of the interpreted text role, the role name + actually used in the document. + +- ``rawtext`` is a string containing the entire interpreted text construct. + Return it as a ``problematic`` node linked to a system message if there is a + problem. + +- ``text`` is the interpreted text content, with backslash escapes converted + to nulls (``\x00``). + +- ``lineno`` is the line number where the text block containing the + interpreted text begins. + +- ``inliner`` is the Inliner object that called the role function. + It defines the following useful attributes: ``reporter``, + ``problematic``, ``memo``, ``parent``, ``document``. + +- ``options``: A dictionary of directive options for customization, to be + interpreted by the role function. Used for additional attributes for the + generated elements and other functionality. + +- ``content``: A list of strings, the directive content for customization + ("role" directive). To be interpreted by the role function. + +Function attributes for customization, interpreted by the "role" directive: + +- ``options``: A dictionary, mapping known option names to conversion + functions such as `int` or `float`. ``None`` or an empty dict implies no + options to parse. Several directive option conversion functions are defined + in the `directives` module. + + All role functions implicitly support the "class" option, unless disabled + with an explicit ``{'class': None}``. + +- ``content``: A boolean; true if content is allowed. Client code must handle + the case where content is required but not supplied (an empty content list + will be supplied). + +Note that unlike directives, the "arguments" function attribute is not +supported for role customization. Directive arguments are handled by the +"role" directive itself. + +Interpreted role functions return a tuple of two values: + +- A list of nodes which will be inserted into the document tree at the + point where the interpreted role was encountered (can be an empty + list). + +- A list of system messages, which will be inserted into the document tree + immediately after the end of the current inline block (can also be empty). +""" + +__docformat__ = 'reStructuredText' + + +from docutils import nodes +from docutils.parsers.rst import directives +from docutils.parsers.rst.languages import en as _fallback_language_module +from docutils.utils.code_analyzer import Lexer, LexerError + +DEFAULT_INTERPRETED_ROLE = 'title-reference' +"""The canonical name of the default interpreted role. + +This role is used when no role is specified for a piece of interpreted text. +""" + +_role_registry = {} +"""Mapping of canonical role names to role functions. + +Language-dependent role names are defined in the ``language`` subpackage. +""" + +_roles = {} +"""Mapping of local or language-dependent interpreted text role names to role +functions.""" + + +def role(role_name, language_module, lineno, reporter): + """ + Locate and return a role function from its language-dependent name, along + with a list of system messages. + + If the role is not found in the current language, check English. Return a + 2-tuple: role function (``None`` if the named role cannot be found) and a + list of system messages. + """ + normname = role_name.lower() + messages = [] + msg_text = [] + + if normname in _roles: + return _roles[normname], messages + + if role_name: + canonicalname = None + try: + canonicalname = language_module.roles[normname] + except AttributeError as error: + msg_text.append('Problem retrieving role entry from language ' + 'module %r: %s.' % (language_module, error)) + except KeyError: + msg_text.append('No role entry for "%s" in module "%s".' + % (role_name, language_module.__name__)) + else: + canonicalname = DEFAULT_INTERPRETED_ROLE + + # If we didn't find it, try English as a fallback. + if not canonicalname: + try: + canonicalname = _fallback_language_module.roles[normname] + msg_text.append('Using English fallback for role "%s".' + % role_name) + except KeyError: + msg_text.append('Trying "%s" as canonical role name.' + % role_name) + # The canonical name should be an English name, but just in case: + canonicalname = normname + + # Collect any messages that we generated. + if msg_text: + message = reporter.info('\n'.join(msg_text), line=lineno) + messages.append(message) + + # Look the role up in the registry, and return it. + if canonicalname in _role_registry: + role_fn = _role_registry[canonicalname] + register_local_role(normname, role_fn) + return role_fn, messages + return None, messages # Error message will be generated by caller. + + +def register_canonical_role(name, role_fn): + """ + Register an interpreted text role by its canonical name. + + :Parameters: + - `name`: The canonical name of the interpreted role. + - `role_fn`: The role function. See the module docstring. + """ + set_implicit_options(role_fn) + _role_registry[name.lower()] = role_fn + + +def register_local_role(name, role_fn): + """ + Register an interpreted text role by its local or language-dependent name. + + :Parameters: + - `name`: The local or language-dependent name of the interpreted role. + - `role_fn`: The role function. See the module docstring. + """ + set_implicit_options(role_fn) + _roles[name.lower()] = role_fn + + +def set_implicit_options(role_fn): + """ + Add customization options to role functions, unless explicitly set or + disabled. + """ + if not hasattr(role_fn, 'options') or role_fn.options is None: + role_fn.options = {'class': directives.class_option} + elif 'class' not in role_fn.options: + role_fn.options['class'] = directives.class_option + + +def register_generic_role(canonical_name, node_class): + """For roles which simply wrap a given `node_class` around the text.""" + role = GenericRole(canonical_name, node_class) + register_canonical_role(canonical_name, role) + + +class GenericRole: + """ + Generic interpreted text role. + + The interpreted text is simply wrapped with the provided node class. + """ + + def __init__(self, role_name, node_class): + self.name = role_name + self.node_class = node_class + + def __call__(self, role, rawtext, text, lineno, inliner, + options=None, content=None): + options = normalized_role_options(options) + return [self.node_class(rawtext, text, **options)], [] + + +class CustomRole: + """Wrapper for custom interpreted text roles.""" + + def __init__(self, role_name, base_role, options=None, content=None): + self.name = role_name + self.base_role = base_role + self.options = getattr(base_role, 'options', None) + self.content = getattr(base_role, 'content', None) + self.supplied_options = options + self.supplied_content = content + + def __call__(self, role, rawtext, text, lineno, inliner, + options=None, content=None): + opts = normalized_role_options(self.supplied_options) + try: + opts.update(options) + except TypeError: # options may be ``None`` + pass + # pass concatenation of content from instance and call argument: + supplied_content = self.supplied_content or [] + content = content or [] + delimiter = ['\n'] if supplied_content and content else [] + return self.base_role(role, rawtext, text, lineno, inliner, + options=opts, + content=supplied_content+delimiter+content) + + +def generic_custom_role(role, rawtext, text, lineno, inliner, + options=None, content=None): + """Base for custom roles if no other base role is specified.""" + # Once nested inline markup is implemented, this and other methods should + # recursively call inliner.nested_parse(). + options = normalized_role_options(options) + return [nodes.inline(rawtext, text, **options)], [] + + +generic_custom_role.options = {'class': directives.class_option} + + +###################################################################### +# Define and register the standard roles: +###################################################################### + +register_generic_role('abbreviation', nodes.abbreviation) +register_generic_role('acronym', nodes.acronym) +register_generic_role('emphasis', nodes.emphasis) +register_generic_role('literal', nodes.literal) +register_generic_role('strong', nodes.strong) +register_generic_role('subscript', nodes.subscript) +register_generic_role('superscript', nodes.superscript) +register_generic_role('title-reference', nodes.title_reference) + + +def pep_reference_role(role, rawtext, text, lineno, inliner, + options=None, content=None): + options = normalized_role_options(options) + try: + pepnum = int(nodes.unescape(text)) + if pepnum < 0 or pepnum > 9999: + raise ValueError + except ValueError: + msg = inliner.reporter.error( + 'PEP number must be a number from 0 to 9999; "%s" is invalid.' + % text, line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + # Base URL mainly used by inliner.pep_reference; so this is correct: + ref = (inliner.document.settings.pep_base_url + + inliner.document.settings.pep_file_url_template % pepnum) + return [nodes.reference(rawtext, 'PEP ' + text, refuri=ref, **options)], [] + + +register_canonical_role('pep-reference', pep_reference_role) + + +def rfc_reference_role(role, rawtext, text, lineno, inliner, + options=None, content=None): + options = normalized_role_options(options) + if "#" in text: + rfcnum, section = nodes.unescape(text).split("#", 1) + else: + rfcnum, section = nodes.unescape(text), None + try: + rfcnum = int(rfcnum) + if rfcnum < 1: + raise ValueError + except ValueError: + msg = inliner.reporter.error( + 'RFC number must be a number greater than or equal to 1; ' + '"%s" is invalid.' % text, line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + # Base URL mainly used by inliner.rfc_reference, so this is correct: + ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum + if section is not None: + ref += "#" + section + node = nodes.reference(rawtext, 'RFC '+str(rfcnum), refuri=ref, **options) + return [node], [] + + +register_canonical_role('rfc-reference', rfc_reference_role) + + +def raw_role(role, rawtext, text, lineno, inliner, options=None, content=None): + options = normalized_role_options(options) + if not inliner.document.settings.raw_enabled: + msg = inliner.reporter.warning('raw (and derived) roles disabled') + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + if 'format' not in options: + msg = inliner.reporter.error( + 'No format (Writer name) is associated with this role: "%s".\n' + 'The "raw" role cannot be used directly.\n' + 'Instead, use the "role" directive to create a new role with ' + 'an associated format.' % role, line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + node = nodes.raw(rawtext, nodes.unescape(text, True), **options) + node.source, node.line = inliner.reporter.get_source_and_line(lineno) + return [node], [] + + +raw_role.options = {'format': directives.unchanged} + +register_canonical_role('raw', raw_role) + + +def code_role(role, rawtext, text, lineno, inliner, + options=None, content=None): + options = normalized_role_options(options) + language = options.get('language', '') + classes = ['code'] + if 'classes' in options: + classes.extend(options['classes']) + if language and language not in classes: + classes.append(language) + try: + tokens = Lexer(nodes.unescape(text, True), language, + inliner.document.settings.syntax_highlight) + except LexerError as error: + msg = inliner.reporter.warning(error) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + node = nodes.literal(rawtext, '', classes=classes) + + # analyse content and add nodes for every token + for classes, value in tokens: + if classes: + node += nodes.inline(value, value, classes=classes) + else: + # insert as Text to decrease the verbosity of the output + node += nodes.Text(value) + + return [node], [] + + +code_role.options = {'language': directives.unchanged} + +register_canonical_role('code', code_role) + + +def math_role(role, rawtext, text, lineno, inliner, + options=None, content=None): + options = normalized_role_options(options) + text = nodes.unescape(text, True) # raw text without inline role markup + node = nodes.math(rawtext, text, **options) + return [node], [] + + +register_canonical_role('math', math_role) + + +###################################################################### +# Register roles that are currently unimplemented. +###################################################################### + +def unimplemented_role(role, rawtext, text, lineno, inliner, + options=None, content=None): + msg = inliner.reporter.error( + 'Interpreted text role "%s" not implemented.' % role, line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + + +register_canonical_role('index', unimplemented_role) +register_canonical_role('named-reference', unimplemented_role) +register_canonical_role('anonymous-reference', unimplemented_role) +register_canonical_role('uri-reference', unimplemented_role) +register_canonical_role('footnote-reference', unimplemented_role) +register_canonical_role('citation-reference', unimplemented_role) +register_canonical_role('substitution-reference', unimplemented_role) +register_canonical_role('target', unimplemented_role) + +# This should remain unimplemented, for testing purposes: +register_canonical_role('restructuredtext-unimplemented-role', + unimplemented_role) + + +def set_classes(options): + """Deprecated. Obsoleted by ``normalized_role_options()``.""" + # TODO: Change use in directives.py and uncomment. + # warnings.warn('The auxiliary function roles.set_classes() is obsoleted' + # ' by roles.normalized_role_options() and will be removed' + # ' in Docutils 0.21 or later', DeprecationWarning, stacklevel=2) + if options and 'class' in options: + assert 'classes' not in options + options['classes'] = options['class'] + del options['class'] + + +def normalized_role_options(options): + """ + Return normalized dictionary of role options. + + * ``None`` is replaced by an empty dictionary. + * The key 'class' is renamed to 'classes'. + """ + if options is None: + return {} + result = options.copy() + if 'class' in result: + assert 'classes' not in result + result['classes'] = result['class'] + del result['class'] + return result diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/states.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/states.py new file mode 100644 index 00000000..e2b25c7c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/states.py @@ -0,0 +1,3145 @@ +# $Id: states.py 9500 2023-12-14 22:38:49Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This is the ``docutils.parsers.rst.states`` module, the core of +the reStructuredText parser. It defines the following: + +:Classes: + - `RSTStateMachine`: reStructuredText parser's entry point. + - `NestedStateMachine`: recursive StateMachine. + - `RSTState`: reStructuredText State superclass. + - `Inliner`: For parsing inline markup. + - `Body`: Generic classifier of the first line of a block. + - `SpecializedBody`: Superclass for compound element members. + - `BulletList`: Second and subsequent bullet_list list_items + - `DefinitionList`: Second+ definition_list_items. + - `EnumeratedList`: Second+ enumerated_list list_items. + - `FieldList`: Second+ fields. + - `OptionList`: Second+ option_list_items. + - `RFC2822List`: Second+ RFC2822-style fields. + - `ExtensionOptions`: Parses directive option fields. + - `Explicit`: Second+ explicit markup constructs. + - `SubstitutionDef`: For embedded directives in substitution definitions. + - `Text`: Classifier of second line of a text block. + - `SpecializedText`: Superclass for continuation lines of Text-variants. + - `Definition`: Second line of potential definition_list_item. + - `Line`: Second line of overlined section title or transition marker. + - `Struct`: An auxiliary collection class. + +:Exception classes: + - `MarkupError` + - `ParserError` + - `MarkupMismatch` + +:Functions: + - `escape2null()`: Return a string, escape-backslashes converted to nulls. + - `unescape()`: Return a string, nulls removed or restored to backslashes. + +:Attributes: + - `state_classes`: set of State classes used with `RSTStateMachine`. + +Parser Overview +=============== + +The reStructuredText parser is implemented as a recursive state machine, +examining its input one line at a time. To understand how the parser works, +please first become familiar with the `docutils.statemachine` module. In the +description below, references are made to classes defined in this module; +please see the individual classes for details. + +Parsing proceeds as follows: + +1. The state machine examines each line of input, checking each of the + transition patterns of the state `Body`, in order, looking for a match. + The implicit transitions (blank lines and indentation) are checked before + any others. The 'text' transition is a catch-all (matches anything). + +2. The method associated with the matched transition pattern is called. + + A. Some transition methods are self-contained, appending elements to the + document tree (`Body.doctest` parses a doctest block). The parser's + current line index is advanced to the end of the element, and parsing + continues with step 1. + + B. Other transition methods trigger the creation of a nested state machine, + whose job is to parse a compound construct ('indent' does a block quote, + 'bullet' does a bullet list, 'overline' does a section [first checking + for a valid section header], etc.). + + - In the case of lists and explicit markup, a one-off state machine is + created and run to parse contents of the first item. + + - A new state machine is created and its initial state is set to the + appropriate specialized state (`BulletList` in the case of the + 'bullet' transition; see `SpecializedBody` for more detail). This + state machine is run to parse the compound element (or series of + explicit markup elements), and returns as soon as a non-member element + is encountered. For example, the `BulletList` state machine ends as + soon as it encounters an element which is not a list item of that + bullet list. The optional omission of inter-element blank lines is + enabled by this nested state machine. + + - The current line index is advanced to the end of the elements parsed, + and parsing continues with step 1. + + C. The result of the 'text' transition depends on the next line of text. + The current state is changed to `Text`, under which the second line is + examined. If the second line is: + + - Indented: The element is a definition list item, and parsing proceeds + similarly to step 2.B, using the `DefinitionList` state. + + - A line of uniform punctuation characters: The element is a section + header; again, parsing proceeds as in step 2.B, and `Body` is still + used. + + - Anything else: The element is a paragraph, which is examined for + inline markup and appended to the parent element. Processing + continues with step 1. +""" + +__docformat__ = 'reStructuredText' + + +import re +from types import FunctionType, MethodType + +from docutils import nodes, statemachine, utils +from docutils import ApplicationError, DataError +from docutils.statemachine import StateMachineWS, StateWS +from docutils.nodes import fully_normalize_name as normalize_name +from docutils.nodes import unescape, whitespace_normalize_name +import docutils.parsers.rst +from docutils.parsers.rst import directives, languages, tableparser, roles +from docutils.utils import escape2null, column_width +from docutils.utils import punctuation_chars, roman, urischemes +from docutils.utils import split_escaped_whitespace + + +class MarkupError(DataError): pass +class UnknownInterpretedRoleError(DataError): pass +class InterpretedRoleNotImplementedError(DataError): pass +class ParserError(ApplicationError): pass +class MarkupMismatch(Exception): pass + + +class Struct: + + """Stores data attributes for dotted-attribute access.""" + + def __init__(self, **keywordargs): + self.__dict__.update(keywordargs) + + +class RSTStateMachine(StateMachineWS): + + """ + reStructuredText's master StateMachine. + + The entry point to reStructuredText parsing is the `run()` method. + """ + + def run(self, input_lines, document, input_offset=0, match_titles=True, + inliner=None): + """ + Parse `input_lines` and modify the `document` node in place. + + Extend `StateMachineWS.run()`: set up parse-global data and + run the StateMachine. + """ + self.language = languages.get_language( + document.settings.language_code, document.reporter) + self.match_titles = match_titles + if inliner is None: + inliner = Inliner() + inliner.init_customizations(document.settings) + self.memo = Struct(document=document, + reporter=document.reporter, + language=self.language, + title_styles=[], + section_level=0, + section_bubble_up_kludge=False, + inliner=inliner) + self.document = document + self.attach_observer(document.note_source) + self.reporter = self.memo.reporter + self.node = document + results = StateMachineWS.run(self, input_lines, input_offset, + input_source=document['source']) + assert results == [], 'RSTStateMachine.run() results should be empty!' + self.node = self.memo = None # remove unneeded references + + +class NestedStateMachine(StateMachineWS): + + """ + StateMachine run from within other StateMachine runs, to parse nested + document structures. + """ + + def run(self, input_lines, input_offset, memo, node, match_titles=True): + """ + Parse `input_lines` and populate a `docutils.nodes.document` instance. + + Extend `StateMachineWS.run()`: set up document-wide data. + """ + self.match_titles = match_titles + self.memo = memo + self.document = memo.document + self.attach_observer(self.document.note_source) + self.reporter = memo.reporter + self.language = memo.language + self.node = node + results = StateMachineWS.run(self, input_lines, input_offset) + assert results == [], ('NestedStateMachine.run() results should be ' + 'empty!') + return results + + +class RSTState(StateWS): + + """ + reStructuredText State superclass. + + Contains methods used by all State subclasses. + """ + + nested_sm = NestedStateMachine + nested_sm_cache = [] + + def __init__(self, state_machine, debug=False): + self.nested_sm_kwargs = {'state_classes': state_classes, + 'initial_state': 'Body'} + StateWS.__init__(self, state_machine, debug) + + def runtime_init(self): + StateWS.runtime_init(self) + memo = self.state_machine.memo + self.memo = memo + self.reporter = memo.reporter + self.inliner = memo.inliner + self.document = memo.document + self.parent = self.state_machine.node + # enable the reporter to determine source and source-line + if not hasattr(self.reporter, 'get_source_and_line'): + self.reporter.get_source_and_line = self.state_machine.get_source_and_line # noqa:E501 + + def goto_line(self, abs_line_offset): + """ + Jump to input line `abs_line_offset`, ignoring jumps past the end. + """ + try: + self.state_machine.goto_line(abs_line_offset) + except EOFError: + pass + + def no_match(self, context, transitions): + """ + Override `StateWS.no_match` to generate a system message. + + This code should never be run. + """ + self.reporter.severe( + 'Internal error: no transition pattern match. State: "%s"; ' + 'transitions: %s; context: %s; current line: %r.' + % (self.__class__.__name__, transitions, context, + self.state_machine.line)) + return context, None, [] + + def bof(self, context): + """Called at beginning of file.""" + return [], [] + + def nested_parse(self, block, input_offset, node, match_titles=False, + state_machine_class=None, state_machine_kwargs=None): + """ + Create a new StateMachine rooted at `node` and run it over the input + `block`. + """ + use_default = 0 + if state_machine_class is None: + state_machine_class = self.nested_sm + use_default += 1 + if state_machine_kwargs is None: + state_machine_kwargs = self.nested_sm_kwargs + use_default += 1 + block_length = len(block) + + state_machine = None + if use_default == 2: + try: + state_machine = self.nested_sm_cache.pop() + except IndexError: + pass + if not state_machine: + state_machine = state_machine_class(debug=self.debug, + **state_machine_kwargs) + state_machine.run(block, input_offset, memo=self.memo, + node=node, match_titles=match_titles) + if use_default == 2: + self.nested_sm_cache.append(state_machine) + else: + state_machine.unlink() + new_offset = state_machine.abs_line_offset() + # No `block.parent` implies disconnected -- lines aren't in sync: + if block.parent and (len(block) - block_length) != 0: + # Adjustment for block if modified in nested parse: + self.state_machine.next_line(len(block) - block_length) + return new_offset + + def nested_list_parse(self, block, input_offset, node, initial_state, + blank_finish, + blank_finish_state=None, + extra_settings={}, + match_titles=False, + state_machine_class=None, + state_machine_kwargs=None): + """ + Create a new StateMachine rooted at `node` and run it over the input + `block`. Also keep track of optional intermediate blank lines and the + required final one. + """ + if state_machine_class is None: + state_machine_class = self.nested_sm + if state_machine_kwargs is None: + state_machine_kwargs = self.nested_sm_kwargs.copy() + state_machine_kwargs['initial_state'] = initial_state + state_machine = state_machine_class(debug=self.debug, + **state_machine_kwargs) + if blank_finish_state is None: + blank_finish_state = initial_state + state_machine.states[blank_finish_state].blank_finish = blank_finish + for key, value in extra_settings.items(): + setattr(state_machine.states[initial_state], key, value) + state_machine.run(block, input_offset, memo=self.memo, + node=node, match_titles=match_titles) + blank_finish = state_machine.states[blank_finish_state].blank_finish + state_machine.unlink() + return state_machine.abs_line_offset(), blank_finish + + def section(self, title, source, style, lineno, messages): + """Check for a valid subsection and create one if it checks out.""" + if self.check_subsection(source, style, lineno): + self.new_subsection(title, lineno, messages) + + def check_subsection(self, source, style, lineno): + """ + Check for a valid subsection header. Return True or False. + + When a new section is reached that isn't a subsection of the current + section, back up the line count (use ``previous_line(-x)``), then + ``raise EOFError``. The current StateMachine will finish, then the + calling StateMachine can re-examine the title. This will work its way + back up the calling chain until the correct section level isreached. + + @@@ Alternative: Evaluate the title, store the title info & level, and + back up the chain until that level is reached. Store in memo? Or + return in results? + + :Exception: `EOFError` when a sibling or supersection encountered. + """ + memo = self.memo + title_styles = memo.title_styles + mylevel = memo.section_level + try: # check for existing title style + level = title_styles.index(style) + 1 + except ValueError: # new title style + if len(title_styles) == memo.section_level: # new subsection + title_styles.append(style) + return True + else: # not at lowest level + self.parent += self.title_inconsistent(source, lineno) + return False + if level <= mylevel: # sibling or supersection + memo.section_level = level # bubble up to parent section + if len(style) == 2: + memo.section_bubble_up_kludge = True + # back up 2 lines for underline title, 3 for overline title + self.state_machine.previous_line(len(style) + 1) + raise EOFError # let parent section re-evaluate + if level == mylevel + 1: # immediate subsection + return True + else: # invalid subsection + self.parent += self.title_inconsistent(source, lineno) + return False + + def title_inconsistent(self, sourcetext, lineno): + error = self.reporter.severe( + 'Title level inconsistent:', nodes.literal_block('', sourcetext), + line=lineno) + return error + + def new_subsection(self, title, lineno, messages): + """Append new subsection to document tree. On return, check level.""" + memo = self.memo + mylevel = memo.section_level + memo.section_level += 1 + section_node = nodes.section() + self.parent += section_node + textnodes, title_messages = self.inline_text(title, lineno) + titlenode = nodes.title(title, '', *textnodes) + name = normalize_name(titlenode.astext()) + section_node['names'].append(name) + section_node += titlenode + section_node += messages + section_node += title_messages + self.document.note_implicit_target(section_node, section_node) + offset = self.state_machine.line_offset + 1 + absoffset = self.state_machine.abs_line_offset() + 1 + newabsoffset = self.nested_parse( + self.state_machine.input_lines[offset:], input_offset=absoffset, + node=section_node, match_titles=True) + self.goto_line(newabsoffset) + if memo.section_level <= mylevel: # can't handle next section? + raise EOFError # bubble up to supersection + # reset section_level; next pass will detect it properly + memo.section_level = mylevel + + def paragraph(self, lines, lineno): + """ + Return a list (paragraph & messages) & a boolean: literal_block next? + """ + data = '\n'.join(lines).rstrip() + if re.search(r'(?<!\\)(\\\\)*::$', data): + if len(data) == 2: + return [], 1 + elif data[-3] in ' \n': + text = data[:-3].rstrip() + else: + text = data[:-1] + literalnext = 1 + else: + text = data + literalnext = 0 + textnodes, messages = self.inline_text(text, lineno) + p = nodes.paragraph(data, '', *textnodes) + p.source, p.line = self.state_machine.get_source_and_line(lineno) + return [p] + messages, literalnext + + def inline_text(self, text, lineno): + """ + Return 2 lists: nodes (text and inline elements), and system_messages. + """ + nodes, messages = self.inliner.parse(text, lineno, + self.memo, self.parent) + return nodes, messages + + def unindent_warning(self, node_name): + # the actual problem is one line below the current line + lineno = self.state_machine.abs_line_number() + 1 + return self.reporter.warning('%s ends without a blank line; ' + 'unexpected unindent.' % node_name, + line=lineno) + + +def build_regexp(definition, compile=True): + """ + Build, compile and return a regular expression based on `definition`. + + :Parameter: `definition`: a 4-tuple (group name, prefix, suffix, parts), + where "parts" is a list of regular expressions and/or regular + expression definitions to be joined into an or-group. + """ + name, prefix, suffix, parts = definition + part_strings = [] + for part in parts: + if isinstance(part, tuple): + part_strings.append(build_regexp(part, None)) + else: + part_strings.append(part) + or_group = '|'.join(part_strings) + regexp = '%(prefix)s(?P<%(name)s>%(or_group)s)%(suffix)s' % locals() + if compile: + return re.compile(regexp) + else: + return regexp + + +class Inliner: + + """ + Parse inline markup; call the `parse()` method. + """ + + def __init__(self): + self.implicit_dispatch = [] + """List of (pattern, bound method) tuples, used by + `self.implicit_inline`.""" + + def init_customizations(self, settings): + # lookahead and look-behind expressions for inline markup rules + if getattr(settings, 'character_level_inline_markup', False): + start_string_prefix = '(^|(?<!\x00))' + end_string_suffix = '' + else: + start_string_prefix = ('(^|(?<=\\s|[%s%s]))' % + (punctuation_chars.openers, + punctuation_chars.delimiters)) + end_string_suffix = ('($|(?=\\s|[\x00%s%s%s]))' % + (punctuation_chars.closing_delimiters, + punctuation_chars.delimiters, + punctuation_chars.closers)) + args = locals().copy() + args.update(vars(self.__class__)) + + parts = ('initial_inline', start_string_prefix, '', + [ + ('start', '', self.non_whitespace_after, # simple start-strings + [r'\*\*', # strong + r'\*(?!\*)', # emphasis but not strong + r'``', # literal + r'_`', # inline internal target + r'\|(?!\|)'] # substitution reference + ), + ('whole', '', end_string_suffix, # whole constructs + [ # reference name & end-string + r'(?P<refname>%s)(?P<refend>__?)' % self.simplename, + ('footnotelabel', r'\[', r'(?P<fnend>\]_)', + [r'[0-9]+', # manually numbered + r'\#(%s)?' % self.simplename, # auto-numbered (w/ label?) + r'\*', # auto-symbol + r'(?P<citationlabel>%s)' % self.simplename, # citation ref + ] + ) + ] + ), + ('backquote', # interpreted text or phrase reference + '(?P<role>(:%s:)?)' % self.simplename, # optional role + self.non_whitespace_after, + ['`(?!`)'] # but not literal + ) + ] + ) + self.start_string_prefix = start_string_prefix + self.end_string_suffix = end_string_suffix + self.parts = parts + + self.patterns = Struct( + initial=build_regexp(parts), + emphasis=re.compile(self.non_whitespace_escape_before + + r'(\*)' + end_string_suffix), + strong=re.compile(self.non_whitespace_escape_before + + r'(\*\*)' + end_string_suffix), + interpreted_or_phrase_ref=re.compile( + r""" + %(non_unescaped_whitespace_escape_before)s + ( + ` + (?P<suffix> + (?P<role>:%(simplename)s:)? + (?P<refend>__?)? + ) + ) + %(end_string_suffix)s + """ % args, re.VERBOSE), + embedded_link=re.compile( + r""" + ( + (?:[ \n]+|^) # spaces or beginning of line/string + < # open bracket + %(non_whitespace_after)s + (([^<>]|\x00[<>])+) # anything but unescaped angle brackets + %(non_whitespace_escape_before)s + > # close bracket + ) + $ # end of string + """ % args, re.VERBOSE), + literal=re.compile(self.non_whitespace_before + '(``)' + + end_string_suffix), + target=re.compile(self.non_whitespace_escape_before + + r'(`)' + end_string_suffix), + substitution_ref=re.compile(self.non_whitespace_escape_before + + r'(\|_{0,2})' + + end_string_suffix), + email=re.compile(self.email_pattern % args + '$', + re.VERBOSE), + uri=re.compile( + (r""" + %(start_string_prefix)s + (?P<whole> + (?P<absolute> # absolute URI + (?P<scheme> # scheme (http, ftp, mailto) + [a-zA-Z][a-zA-Z0-9.+-]* + ) + : + ( + ( # either: + (//?)? # hierarchical URI + %(uric)s* # URI characters + %(uri_end)s # final URI char + ) + ( # optional query + \?%(uric)s* + %(uri_end)s + )? + ( # optional fragment + \#%(uric)s* + %(uri_end)s + )? + ) + ) + | # *OR* + (?P<email> # email address + """ + self.email_pattern + r""" + ) + ) + %(end_string_suffix)s + """) % args, re.VERBOSE), + pep=re.compile( + r""" + %(start_string_prefix)s + ( + (pep-(?P<pepnum1>\d+)(.txt)?) # reference to source file + | + (PEP\s+(?P<pepnum2>\d+)) # reference by name + ) + %(end_string_suffix)s""" % args, re.VERBOSE), + rfc=re.compile( + r""" + %(start_string_prefix)s + (RFC(-|\s+)?(?P<rfcnum>\d+)) + %(end_string_suffix)s""" % args, re.VERBOSE)) + + self.implicit_dispatch.append((self.patterns.uri, + self.standalone_uri)) + if settings.pep_references: + self.implicit_dispatch.append((self.patterns.pep, + self.pep_reference)) + if settings.rfc_references: + self.implicit_dispatch.append((self.patterns.rfc, + self.rfc_reference)) + + def parse(self, text, lineno, memo, parent): + # Needs to be refactored for nested inline markup. + # Add nested_parse() method? + """ + Return 2 lists: nodes (text and inline elements), and system_messages. + + Using `self.patterns.initial`, a pattern which matches start-strings + (emphasis, strong, interpreted, phrase reference, literal, + substitution reference, and inline target) and complete constructs + (simple reference, footnote reference), search for a candidate. When + one is found, check for validity (e.g., not a quoted '*' character). + If valid, search for the corresponding end string if applicable, and + check it for validity. If not found or invalid, generate a warning + and ignore the start-string. Implicit inline markup (e.g. standalone + URIs) is found last. + + :text: source string + :lineno: absolute line number (cf. statemachine.get_source_and_line()) + """ + self.reporter = memo.reporter + self.document = memo.document + self.language = memo.language + self.parent = parent + pattern_search = self.patterns.initial.search + dispatch = self.dispatch + remaining = escape2null(text) + processed = [] + unprocessed = [] + messages = [] + while remaining: + match = pattern_search(remaining) + if match: + groups = match.groupdict() + method = dispatch[groups['start'] or groups['backquote'] + or groups['refend'] or groups['fnend']] + before, inlines, remaining, sysmessages = method(self, match, + lineno) + unprocessed.append(before) + messages += sysmessages + if inlines: + processed += self.implicit_inline(''.join(unprocessed), + lineno) + processed += inlines + unprocessed = [] + else: + break + remaining = ''.join(unprocessed) + remaining + if remaining: + processed += self.implicit_inline(remaining, lineno) + return processed, messages + + # Inline object recognition + # ------------------------- + # See also init_customizations(). + non_whitespace_before = r'(?<!\s)' + non_whitespace_escape_before = r'(?<![\s\x00])' + non_unescaped_whitespace_escape_before = r'(?<!(?<!\x00)[\s\x00])' + non_whitespace_after = r'(?!\s)' + # Alphanumerics with isolated internal [-._+:] chars (i.e. not 2 together): + simplename = r'(?:(?!_)\w)+(?:[-._+:](?:(?!_)\w)+)*' + # Valid URI characters (see RFC 2396 & RFC 2732); + # final \x00 allows backslash escapes in URIs: + uric = r"""[-_.!~*'()[\];/:@&=+$,%a-zA-Z0-9\x00]""" + # Delimiter indicating the end of a URI (not part of the URI): + uri_end_delim = r"""[>]""" + # Last URI character; same as uric but no punctuation: + urilast = r"""[_~*/=+a-zA-Z0-9]""" + # End of a URI (either 'urilast' or 'uric followed by a + # uri_end_delim'): + uri_end = r"""(?:%(urilast)s|%(uric)s(?=%(uri_end_delim)s))""" % locals() + emailc = r"""[-_!~*'{|}/#?^`&=+$%a-zA-Z0-9\x00]""" + email_pattern = r""" + %(emailc)s+(?:\.%(emailc)s+)* # name + (?<!\x00)@ # at + %(emailc)s+(?:\.%(emailc)s*)* # host + %(uri_end)s # final URI char + """ + + def quoted_start(self, match): + """Test if inline markup start-string is 'quoted'. + + 'Quoted' in this context means the start-string is enclosed in a pair + of matching opening/closing delimiters (not necessarily quotes) + or at the end of the match. + """ + string = match.string + start = match.start() + if start == 0: # start-string at beginning of text + return False + prestart = string[start - 1] + try: + poststart = string[match.end()] + except IndexError: # start-string at end of text + return True # not "quoted" but no markup start-string either + return punctuation_chars.match_chars(prestart, poststart) + + def inline_obj(self, match, lineno, end_pattern, nodeclass, + restore_backslashes=False): + string = match.string + matchstart = match.start('start') + matchend = match.end('start') + if self.quoted_start(match): + return string[:matchend], [], string[matchend:], [], '' + endmatch = end_pattern.search(string[matchend:]) + if endmatch and endmatch.start(1): # 1 or more chars + text = endmatch.string[:endmatch.start(1)] + if restore_backslashes: + text = unescape(text, True) + textend = matchend + endmatch.end(1) + rawsource = unescape(string[matchstart:textend], True) + node = nodeclass(rawsource, text) + return (string[:matchstart], [node], + string[textend:], [], endmatch.group(1)) + msg = self.reporter.warning( + 'Inline %s start-string without end-string.' + % nodeclass.__name__, line=lineno) + text = unescape(string[matchstart:matchend], True) + prb = self.problematic(text, text, msg) + return string[:matchstart], [prb], string[matchend:], [msg], '' + + def problematic(self, text, rawsource, message): + msgid = self.document.set_id(message, self.parent) + problematic = nodes.problematic(rawsource, text, refid=msgid) + prbid = self.document.set_id(problematic) + message.add_backref(prbid) + return problematic + + def emphasis(self, match, lineno): + before, inlines, remaining, sysmessages, endstring = self.inline_obj( + match, lineno, self.patterns.emphasis, nodes.emphasis) + return before, inlines, remaining, sysmessages + + def strong(self, match, lineno): + before, inlines, remaining, sysmessages, endstring = self.inline_obj( + match, lineno, self.patterns.strong, nodes.strong) + return before, inlines, remaining, sysmessages + + def interpreted_or_phrase_ref(self, match, lineno): + end_pattern = self.patterns.interpreted_or_phrase_ref + string = match.string + matchstart = match.start('backquote') + matchend = match.end('backquote') + rolestart = match.start('role') + role = match.group('role') + position = '' + if role: + role = role[1:-1] + position = 'prefix' + elif self.quoted_start(match): + return string[:matchend], [], string[matchend:], [] + endmatch = end_pattern.search(string[matchend:]) + if endmatch and endmatch.start(1): # 1 or more chars + textend = matchend + endmatch.end() + if endmatch.group('role'): + if role: + msg = self.reporter.warning( + 'Multiple roles in interpreted text (both ' + 'prefix and suffix present; only one allowed).', + line=lineno) + text = unescape(string[rolestart:textend], True) + prb = self.problematic(text, text, msg) + return string[:rolestart], [prb], string[textend:], [msg] + role = endmatch.group('suffix')[1:-1] + position = 'suffix' + escaped = endmatch.string[:endmatch.start(1)] + rawsource = unescape(string[matchstart:textend], True) + if rawsource[-1:] == '_': + if role: + msg = self.reporter.warning( + 'Mismatch: both interpreted text role %s and ' + 'reference suffix.' % position, line=lineno) + text = unescape(string[rolestart:textend], True) + prb = self.problematic(text, text, msg) + return string[:rolestart], [prb], string[textend:], [msg] + return self.phrase_ref(string[:matchstart], string[textend:], + rawsource, escaped) + else: + rawsource = unescape(string[rolestart:textend], True) + nodelist, messages = self.interpreted(rawsource, escaped, role, + lineno) + return (string[:rolestart], nodelist, + string[textend:], messages) + msg = self.reporter.warning( + 'Inline interpreted text or phrase reference start-string ' + 'without end-string.', line=lineno) + text = unescape(string[matchstart:matchend], True) + prb = self.problematic(text, text, msg) + return string[:matchstart], [prb], string[matchend:], [msg] + + def phrase_ref(self, before, after, rawsource, escaped, text=None): + # `text` is ignored (since 0.16) + match = self.patterns.embedded_link.search(escaped) + if match: # embedded <URI> or <alias_> + text = escaped[:match.start(0)] + unescaped = unescape(text) + rawtext = unescape(text, True) + aliastext = match.group(2) + rawaliastext = unescape(aliastext, True) + underscore_escaped = rawaliastext.endswith(r'\_') + if (aliastext.endswith('_') + and not (underscore_escaped + or self.patterns.uri.match(aliastext))): + aliastype = 'name' + alias = normalize_name(unescape(aliastext[:-1])) + target = nodes.target(match.group(1), refname=alias) + target.indirect_reference_name = whitespace_normalize_name( + unescape(aliastext[:-1])) + else: + aliastype = 'uri' + # remove unescaped whitespace + alias_parts = split_escaped_whitespace(match.group(2)) + alias = ' '.join(''.join(part.split()) + for part in alias_parts) + alias = self.adjust_uri(unescape(alias)) + if alias.endswith(r'\_'): + alias = alias[:-2] + '_' + target = nodes.target(match.group(1), refuri=alias) + target.referenced = 1 + if not aliastext: + raise ApplicationError('problem with embedded link: %r' + % aliastext) + if not text: + text = alias + unescaped = unescape(text) + rawtext = rawaliastext + else: + text = escaped + unescaped = unescape(text) + target = None + rawtext = unescape(escaped, True) + + refname = normalize_name(unescaped) + reference = nodes.reference(rawsource, text, + name=whitespace_normalize_name(unescaped)) + reference[0].rawsource = rawtext + + node_list = [reference] + + if rawsource[-2:] == '__': + if target and (aliastype == 'name'): + reference['refname'] = alias + self.document.note_refname(reference) + # self.document.note_indirect_target(target) # required? + elif target and (aliastype == 'uri'): + reference['refuri'] = alias + else: + reference['anonymous'] = 1 + else: + if target: + target['names'].append(refname) + if aliastype == 'name': + reference['refname'] = alias + self.document.note_indirect_target(target) + self.document.note_refname(reference) + else: + reference['refuri'] = alias + self.document.note_explicit_target(target, self.parent) + # target.note_referenced_by(name=refname) + node_list.append(target) + else: + reference['refname'] = refname + self.document.note_refname(reference) + return before, node_list, after, [] + + def adjust_uri(self, uri): + match = self.patterns.email.match(uri) + if match: + return 'mailto:' + uri + else: + return uri + + def interpreted(self, rawsource, text, role, lineno): + role_fn, messages = roles.role(role, self.language, lineno, + self.reporter) + if role_fn: + nodes, messages2 = role_fn(role, rawsource, text, lineno, self) + return nodes, messages + messages2 + else: + msg = self.reporter.error( + 'Unknown interpreted text role "%s".' % role, + line=lineno) + return ([self.problematic(rawsource, rawsource, msg)], + messages + [msg]) + + def literal(self, match, lineno): + before, inlines, remaining, sysmessages, endstring = self.inline_obj( + match, lineno, self.patterns.literal, nodes.literal, + restore_backslashes=True) + return before, inlines, remaining, sysmessages + + def inline_internal_target(self, match, lineno): + before, inlines, remaining, sysmessages, endstring = self.inline_obj( + match, lineno, self.patterns.target, nodes.target) + if inlines and isinstance(inlines[0], nodes.target): + assert len(inlines) == 1 + target = inlines[0] + name = normalize_name(target.astext()) + target['names'].append(name) + self.document.note_explicit_target(target, self.parent) + return before, inlines, remaining, sysmessages + + def substitution_reference(self, match, lineno): + before, inlines, remaining, sysmessages, endstring = self.inline_obj( + match, lineno, self.patterns.substitution_ref, + nodes.substitution_reference) + if len(inlines) == 1: + subref_node = inlines[0] + if isinstance(subref_node, nodes.substitution_reference): + subref_text = subref_node.astext() + self.document.note_substitution_ref(subref_node, subref_text) + if endstring[-1:] == '_': + reference_node = nodes.reference( + '|%s%s' % (subref_text, endstring), '') + if endstring[-2:] == '__': + reference_node['anonymous'] = 1 + else: + reference_node['refname'] = normalize_name(subref_text) + self.document.note_refname(reference_node) + reference_node += subref_node + inlines = [reference_node] + return before, inlines, remaining, sysmessages + + def footnote_reference(self, match, lineno): + """ + Handles `nodes.footnote_reference` and `nodes.citation_reference` + elements. + """ + label = match.group('footnotelabel') + refname = normalize_name(label) + string = match.string + before = string[:match.start('whole')] + remaining = string[match.end('whole'):] + if match.group('citationlabel'): + refnode = nodes.citation_reference('[%s]_' % label, + refname=refname) + refnode += nodes.Text(label) + self.document.note_citation_ref(refnode) + else: + refnode = nodes.footnote_reference('[%s]_' % label) + if refname[0] == '#': + refname = refname[1:] + refnode['auto'] = 1 + self.document.note_autofootnote_ref(refnode) + elif refname == '*': + refname = '' + refnode['auto'] = '*' + self.document.note_symbol_footnote_ref( + refnode) + else: + refnode += nodes.Text(label) + if refname: + refnode['refname'] = refname + self.document.note_footnote_ref(refnode) + if utils.get_trim_footnote_ref_space(self.document.settings): + before = before.rstrip() + return before, [refnode], remaining, [] + + def reference(self, match, lineno, anonymous=False): + referencename = match.group('refname') + refname = normalize_name(referencename) + referencenode = nodes.reference( + referencename + match.group('refend'), referencename, + name=whitespace_normalize_name(referencename)) + referencenode[0].rawsource = referencename + if anonymous: + referencenode['anonymous'] = 1 + else: + referencenode['refname'] = refname + self.document.note_refname(referencenode) + string = match.string + matchstart = match.start('whole') + matchend = match.end('whole') + return string[:matchstart], [referencenode], string[matchend:], [] + + def anonymous_reference(self, match, lineno): + return self.reference(match, lineno, anonymous=True) + + def standalone_uri(self, match, lineno): + if (not match.group('scheme') + or match.group('scheme').lower() in urischemes.schemes): + if match.group('email'): + addscheme = 'mailto:' + else: + addscheme = '' + text = match.group('whole') + refuri = addscheme + unescape(text) + reference = nodes.reference(unescape(text, True), text, + refuri=refuri) + return [reference] + else: # not a valid scheme + raise MarkupMismatch + + def pep_reference(self, match, lineno): + text = match.group(0) + if text.startswith('pep-'): + pepnum = int(unescape(match.group('pepnum1'))) + elif text.startswith('PEP'): + pepnum = int(unescape(match.group('pepnum2'))) + else: + raise MarkupMismatch + ref = (self.document.settings.pep_base_url + + self.document.settings.pep_file_url_template % pepnum) + return [nodes.reference(unescape(text, True), text, refuri=ref)] + + rfc_url = 'rfc%d.html' + + def rfc_reference(self, match, lineno): + text = match.group(0) + if text.startswith('RFC'): + rfcnum = int(unescape(match.group('rfcnum'))) + ref = self.document.settings.rfc_base_url + self.rfc_url % rfcnum + else: + raise MarkupMismatch + return [nodes.reference(unescape(text, True), text, refuri=ref)] + + def implicit_inline(self, text, lineno): + """ + Check each of the patterns in `self.implicit_dispatch` for a match, + and dispatch to the stored method for the pattern. Recursively check + the text before and after the match. Return a list of `nodes.Text` + and inline element nodes. + """ + if not text: + return [] + for pattern, method in self.implicit_dispatch: + match = pattern.search(text) + if match: + try: + # Must recurse on strings before *and* after the match; + # there may be multiple patterns. + return (self.implicit_inline(text[:match.start()], lineno) + + method(match, lineno) + + self.implicit_inline(text[match.end():], lineno)) + except MarkupMismatch: + pass + return [nodes.Text(text)] + + dispatch = {'*': emphasis, + '**': strong, + '`': interpreted_or_phrase_ref, + '``': literal, + '_`': inline_internal_target, + ']_': footnote_reference, + '|': substitution_reference, + '_': reference, + '__': anonymous_reference} + + +def _loweralpha_to_int(s, _zero=(ord('a')-1)): + return ord(s) - _zero + + +def _upperalpha_to_int(s, _zero=(ord('A')-1)): + return ord(s) - _zero + + +def _lowerroman_to_int(s): + return roman.fromRoman(s.upper()) + + +class Body(RSTState): + + """ + Generic classifier of the first line of a block. + """ + + double_width_pad_char = tableparser.TableParser.double_width_pad_char + """Padding character for East Asian double-width text.""" + + enum = Struct() + """Enumerated list parsing information.""" + + enum.formatinfo = { + 'parens': Struct(prefix='(', suffix=')', start=1, end=-1), + 'rparen': Struct(prefix='', suffix=')', start=0, end=-1), + 'period': Struct(prefix='', suffix='.', start=0, end=-1)} + enum.formats = enum.formatinfo.keys() + enum.sequences = ['arabic', 'loweralpha', 'upperalpha', + 'lowerroman', 'upperroman'] # ORDERED! + enum.sequencepats = {'arabic': '[0-9]+', + 'loweralpha': '[a-z]', + 'upperalpha': '[A-Z]', + 'lowerroman': '[ivxlcdm]+', + 'upperroman': '[IVXLCDM]+'} + enum.converters = {'arabic': int, + 'loweralpha': _loweralpha_to_int, + 'upperalpha': _upperalpha_to_int, + 'lowerroman': _lowerroman_to_int, + 'upperroman': roman.fromRoman} + + enum.sequenceregexps = {} + for sequence in enum.sequences: + enum.sequenceregexps[sequence] = re.compile( + enum.sequencepats[sequence] + '$') + + grid_table_top_pat = re.compile(r'\+-[-+]+-\+ *$') + """Matches the top (& bottom) of a full table).""" + + simple_table_top_pat = re.compile('=+( +=+)+ *$') + """Matches the top of a simple table.""" + + simple_table_border_pat = re.compile('=+[ =]*$') + """Matches the bottom & header bottom of a simple table.""" + + pats = {} + """Fragments of patterns used by transitions.""" + + pats['nonalphanum7bit'] = '[!-/:-@[-`{-~]' + pats['alpha'] = '[a-zA-Z]' + pats['alphanum'] = '[a-zA-Z0-9]' + pats['alphanumplus'] = '[a-zA-Z0-9_-]' + pats['enum'] = ('(%(arabic)s|%(loweralpha)s|%(upperalpha)s|%(lowerroman)s' + '|%(upperroman)s|#)' % enum.sequencepats) + pats['optname'] = '%(alphanum)s%(alphanumplus)s*' % pats + # @@@ Loosen up the pattern? Allow Unicode? + pats['optarg'] = '(%(alpha)s%(alphanumplus)s*|<[^<>]+>)' % pats + pats['shortopt'] = r'(-|\+)%(alphanum)s( ?%(optarg)s)?' % pats + pats['longopt'] = r'(--|/)%(optname)s([ =]%(optarg)s)?' % pats + pats['option'] = r'(%(shortopt)s|%(longopt)s)' % pats + + for format in enum.formats: + pats[format] = '(?P<%s>%s%s%s)' % ( + format, re.escape(enum.formatinfo[format].prefix), + pats['enum'], re.escape(enum.formatinfo[format].suffix)) + + patterns = { + 'bullet': '[-+*\u2022\u2023\u2043]( +|$)', + 'enumerator': r'(%(parens)s|%(rparen)s|%(period)s)( +|$)' % pats, + 'field_marker': r':(?![: ])([^:\\]|\\.|:(?!([ `]|$)))*(?<! ):( +|$)', + 'option_marker': r'%(option)s(, %(option)s)*( +| ?$)' % pats, + 'doctest': r'>>>( +|$)', + 'line_block': r'\|( +|$)', + 'grid_table_top': grid_table_top_pat, + 'simple_table_top': simple_table_top_pat, + 'explicit_markup': r'\.\.( +|$)', + 'anonymous': r'__( +|$)', + 'line': r'(%(nonalphanum7bit)s)\1* *$' % pats, + 'text': r''} + initial_transitions = ( + 'bullet', + 'enumerator', + 'field_marker', + 'option_marker', + 'doctest', + 'line_block', + 'grid_table_top', + 'simple_table_top', + 'explicit_markup', + 'anonymous', + 'line', + 'text') + + def indent(self, match, context, next_state): + """Block quote.""" + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_indented() + elements = self.block_quote(indented, line_offset) + self.parent += elements + if not blank_finish: + self.parent += self.unindent_warning('Block quote') + return context, next_state, [] + + def block_quote(self, indented, line_offset): + elements = [] + while indented: + blockquote = nodes.block_quote(rawsource='\n'.join(indented)) + (blockquote.source, blockquote.line + ) = self.state_machine.get_source_and_line(line_offset+1) + (blockquote_lines, + attribution_lines, + attribution_offset, + indented, + new_line_offset) = self.split_attribution(indented, line_offset) + self.nested_parse(blockquote_lines, line_offset, blockquote) + elements.append(blockquote) + if attribution_lines: + attribution, messages = self.parse_attribution( + attribution_lines, line_offset+attribution_offset) + blockquote += attribution + elements += messages + line_offset = new_line_offset + while indented and not indented[0]: + indented = indented[1:] + line_offset += 1 + return elements + + # U+2014 is an em-dash: + attribution_pattern = re.compile('(---?(?!-)|\u2014) *(?=[^ \\n])') + + def split_attribution(self, indented, line_offset): + """ + Check for a block quote attribution and split it off: + + * First line after a blank line must begin with a dash ("--", "---", + em-dash; matches `self.attribution_pattern`). + * Every line after that must have consistent indentation. + * Attributions must be preceded by block quote content. + + Return a tuple of: (block quote content lines, attribution lines, + attribution offset, remaining indented lines, remaining lines offset). + """ + blank = None + nonblank_seen = False + for i in range(len(indented)): + line = indented[i].rstrip() + if line: + if nonblank_seen and blank == i - 1: # last line blank + match = self.attribution_pattern.match(line) + if match: + attribution_end, indent = self.check_attribution( + indented, i) + if attribution_end: + a_lines = indented[i:attribution_end] + a_lines.trim_left(match.end(), end=1) + a_lines.trim_left(indent, start=1) + return (indented[:i], a_lines, + i, indented[attribution_end:], + line_offset + attribution_end) + nonblank_seen = True + else: + blank = i + else: + return indented, None, None, None, None + + def check_attribution(self, indented, attribution_start): + """ + Check attribution shape. + Return the index past the end of the attribution, and the indent. + """ + indent = None + i = attribution_start + 1 + for i in range(attribution_start + 1, len(indented)): + line = indented[i].rstrip() + if not line: + break + if indent is None: + indent = len(line) - len(line.lstrip()) + elif len(line) - len(line.lstrip()) != indent: + return None, None # bad shape; not an attribution + else: + # return index of line after last attribution line: + i += 1 + return i, (indent or 0) + + def parse_attribution(self, indented, line_offset): + text = '\n'.join(indented).rstrip() + lineno = 1 + line_offset # line_offset is zero-based + textnodes, messages = self.inline_text(text, lineno) + node = nodes.attribution(text, '', *textnodes) + node.source, node.line = self.state_machine.get_source_and_line(lineno) + return node, messages + + def bullet(self, match, context, next_state): + """Bullet list item.""" + ul = nodes.bullet_list() + ul.source, ul.line = self.state_machine.get_source_and_line() + self.parent += ul + ul['bullet'] = match.string[0] + i, blank_finish = self.list_item(match.end()) + ul += i + offset = self.state_machine.line_offset + 1 # next line + new_line_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=ul, initial_state='BulletList', + blank_finish=blank_finish) + self.goto_line(new_line_offset) + if not blank_finish: + self.parent += self.unindent_warning('Bullet list') + return [], next_state, [] + + def list_item(self, indent): + src, srcline = self.state_machine.get_source_and_line() + if self.state_machine.line[indent:]: + indented, line_offset, blank_finish = ( + self.state_machine.get_known_indented(indent)) + else: + indented, indent, line_offset, blank_finish = ( + self.state_machine.get_first_known_indented(indent)) + listitem = nodes.list_item('\n'.join(indented)) + listitem.source, listitem.line = src, srcline + if indented: + self.nested_parse(indented, input_offset=line_offset, + node=listitem) + return listitem, blank_finish + + def enumerator(self, match, context, next_state): + """Enumerated List Item""" + format, sequence, text, ordinal = self.parse_enumerator(match) + if not self.is_enumerated_list_item(ordinal, sequence, format): + raise statemachine.TransitionCorrection('text') + enumlist = nodes.enumerated_list() + self.parent += enumlist + if sequence == '#': + enumlist['enumtype'] = 'arabic' + else: + enumlist['enumtype'] = sequence + enumlist['prefix'] = self.enum.formatinfo[format].prefix + enumlist['suffix'] = self.enum.formatinfo[format].suffix + if ordinal != 1: + enumlist['start'] = ordinal + msg = self.reporter.info( + 'Enumerated list start value not ordinal-1: "%s" (ordinal %s)' + % (text, ordinal)) + self.parent += msg + listitem, blank_finish = self.list_item(match.end()) + enumlist += listitem + offset = self.state_machine.line_offset + 1 # next line + newline_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=enumlist, initial_state='EnumeratedList', + blank_finish=blank_finish, + extra_settings={'lastordinal': ordinal, + 'format': format, + 'auto': sequence == '#'}) + self.goto_line(newline_offset) + if not blank_finish: + self.parent += self.unindent_warning('Enumerated list') + return [], next_state, [] + + def parse_enumerator(self, match, expected_sequence=None): + """ + Analyze an enumerator and return the results. + + :Return: + - the enumerator format ('period', 'parens', or 'rparen'), + - the sequence used ('arabic', 'loweralpha', 'upperroman', etc.), + - the text of the enumerator, stripped of formatting, and + - the ordinal value of the enumerator ('a' -> 1, 'ii' -> 2, etc.; + ``None`` is returned for invalid enumerator text). + + The enumerator format has already been determined by the regular + expression match. If `expected_sequence` is given, that sequence is + tried first. If not, we check for Roman numeral 1. This way, + single-character Roman numerals (which are also alphabetical) can be + matched. If no sequence has been matched, all sequences are checked in + order. + """ + groupdict = match.groupdict() + sequence = '' + for format in self.enum.formats: + if groupdict[format]: # was this the format matched? + break # yes; keep `format` + else: # shouldn't happen + raise ParserError('enumerator format not matched') + text = groupdict[format][self.enum.formatinfo[format].start # noqa: E203,E501 + : self.enum.formatinfo[format].end] + if text == '#': + sequence = '#' + elif expected_sequence: + try: + if self.enum.sequenceregexps[expected_sequence].match(text): + sequence = expected_sequence + except KeyError: # shouldn't happen + raise ParserError('unknown enumerator sequence: %s' + % sequence) + elif text == 'i': + sequence = 'lowerroman' + elif text == 'I': + sequence = 'upperroman' + if not sequence: + for sequence in self.enum.sequences: + if self.enum.sequenceregexps[sequence].match(text): + break + else: # shouldn't happen + raise ParserError('enumerator sequence not matched') + if sequence == '#': + ordinal = 1 + else: + try: + ordinal = self.enum.converters[sequence](text) + except roman.InvalidRomanNumeralError: + ordinal = None + return format, sequence, text, ordinal + + def is_enumerated_list_item(self, ordinal, sequence, format): + """ + Check validity based on the ordinal value and the second line. + + Return true if the ordinal is valid and the second line is blank, + indented, or starts with the next enumerator or an auto-enumerator. + """ + if ordinal is None: + return None + try: + next_line = self.state_machine.next_line() + except EOFError: # end of input lines + self.state_machine.previous_line() + return 1 + else: + self.state_machine.previous_line() + if not next_line[:1].strip(): # blank or indented + return 1 + result = self.make_enumerator(ordinal + 1, sequence, format) + if result: + next_enumerator, auto_enumerator = result + try: + if (next_line.startswith(next_enumerator) + or next_line.startswith(auto_enumerator)): + return 1 + except TypeError: + pass + return None + + def make_enumerator(self, ordinal, sequence, format): + """ + Construct and return the next enumerated list item marker, and an + auto-enumerator ("#" instead of the regular enumerator). + + Return ``None`` for invalid (out of range) ordinals. + """ + if sequence == '#': + enumerator = '#' + elif sequence == 'arabic': + enumerator = str(ordinal) + else: + if sequence.endswith('alpha'): + if ordinal > 26: + return None + enumerator = chr(ordinal + ord('a') - 1) + elif sequence.endswith('roman'): + try: + enumerator = roman.toRoman(ordinal) + except roman.RomanError: + return None + else: # shouldn't happen + raise ParserError('unknown enumerator sequence: "%s"' + % sequence) + if sequence.startswith('lower'): + enumerator = enumerator.lower() + elif sequence.startswith('upper'): + enumerator = enumerator.upper() + else: # shouldn't happen + raise ParserError('unknown enumerator sequence: "%s"' + % sequence) + formatinfo = self.enum.formatinfo[format] + next_enumerator = (formatinfo.prefix + enumerator + formatinfo.suffix + + ' ') + auto_enumerator = formatinfo.prefix + '#' + formatinfo.suffix + ' ' + return next_enumerator, auto_enumerator + + def field_marker(self, match, context, next_state): + """Field list item.""" + field_list = nodes.field_list() + self.parent += field_list + field, blank_finish = self.field(match) + field_list += field + offset = self.state_machine.line_offset + 1 # next line + newline_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=field_list, initial_state='FieldList', + blank_finish=blank_finish) + self.goto_line(newline_offset) + if not blank_finish: + self.parent += self.unindent_warning('Field list') + return [], next_state, [] + + def field(self, match): + name = self.parse_field_marker(match) + src, srcline = self.state_machine.get_source_and_line() + lineno = self.state_machine.abs_line_number() + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + field_node = nodes.field() + field_node.source = src + field_node.line = srcline + name_nodes, name_messages = self.inline_text(name, lineno) + field_node += nodes.field_name(name, '', *name_nodes) + field_body = nodes.field_body('\n'.join(indented), *name_messages) + field_node += field_body + if indented: + self.parse_field_body(indented, line_offset, field_body) + return field_node, blank_finish + + def parse_field_marker(self, match): + """Extract & return field name from a field marker match.""" + field = match.group()[1:] # strip off leading ':' + field = field[:field.rfind(':')] # strip off trailing ':' etc. + return field + + def parse_field_body(self, indented, offset, node): + self.nested_parse(indented, input_offset=offset, node=node) + + def option_marker(self, match, context, next_state): + """Option list item.""" + optionlist = nodes.option_list() + (optionlist.source, optionlist.line + ) = self.state_machine.get_source_and_line() + try: + listitem, blank_finish = self.option_list_item(match) + except MarkupError as error: + # This shouldn't happen; pattern won't match. + msg = self.reporter.error('Invalid option list marker: %s' + % error) + self.parent += msg + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + elements = self.block_quote(indented, line_offset) + self.parent += elements + if not blank_finish: + self.parent += self.unindent_warning('Option list') + return [], next_state, [] + self.parent += optionlist + optionlist += listitem + offset = self.state_machine.line_offset + 1 # next line + newline_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=optionlist, initial_state='OptionList', + blank_finish=blank_finish) + self.goto_line(newline_offset) + if not blank_finish: + self.parent += self.unindent_warning('Option list') + return [], next_state, [] + + def option_list_item(self, match): + offset = self.state_machine.abs_line_offset() + options = self.parse_option_marker(match) + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + if not indented: # not an option list item + self.goto_line(offset) + raise statemachine.TransitionCorrection('text') + option_group = nodes.option_group('', *options) + description = nodes.description('\n'.join(indented)) + option_list_item = nodes.option_list_item('', option_group, + description) + if indented: + self.nested_parse(indented, input_offset=line_offset, + node=description) + return option_list_item, blank_finish + + def parse_option_marker(self, match): + """ + Return a list of `node.option` and `node.option_argument` objects, + parsed from an option marker match. + + :Exception: `MarkupError` for invalid option markers. + """ + optlist = [] + # split at ", ", except inside < > (complex arguments) + optionstrings = re.split(r', (?![^<]*>)', match.group().rstrip()) + for optionstring in optionstrings: + tokens = optionstring.split() + delimiter = ' ' + firstopt = tokens[0].split('=', 1) + if len(firstopt) > 1: + # "--opt=value" form + tokens[:1] = firstopt + delimiter = '=' + elif (len(tokens[0]) > 2 + and ((tokens[0].startswith('-') + and not tokens[0].startswith('--')) + or tokens[0].startswith('+'))): + # "-ovalue" form + tokens[:1] = [tokens[0][:2], tokens[0][2:]] + delimiter = '' + if len(tokens) > 1 and (tokens[1].startswith('<') + and tokens[-1].endswith('>')): + # "-o <value1 value2>" form; join all values into one token + tokens[1:] = [' '.join(tokens[1:])] + if 0 < len(tokens) <= 2: + option = nodes.option(optionstring) + option += nodes.option_string(tokens[0], tokens[0]) + if len(tokens) > 1: + option += nodes.option_argument(tokens[1], tokens[1], + delimiter=delimiter) + optlist.append(option) + else: + raise MarkupError( + 'wrong number of option tokens (=%s), should be 1 or 2: ' + '"%s"' % (len(tokens), optionstring)) + return optlist + + def doctest(self, match, context, next_state): + data = '\n'.join(self.state_machine.get_text_block()) + # TODO: prepend class value ['pycon'] (Python Console) + # parse with `directives.body.CodeBlock` (returns literal-block + # with class "code" and syntax highlight markup). + self.parent += nodes.doctest_block(data, data) + return [], next_state, [] + + def line_block(self, match, context, next_state): + """First line of a line block.""" + block = nodes.line_block() + self.parent += block + lineno = self.state_machine.abs_line_number() + line, messages, blank_finish = self.line_block_line(match, lineno) + block += line + self.parent += messages + if not blank_finish: + offset = self.state_machine.line_offset + 1 # next line + new_line_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=block, initial_state='LineBlock', + blank_finish=0) + self.goto_line(new_line_offset) + if not blank_finish: + self.parent += self.reporter.warning( + 'Line block ends without a blank line.', + line=lineno+1) + if len(block): + if block[0].indent is None: + block[0].indent = 0 + self.nest_line_block_lines(block) + return [], next_state, [] + + def line_block_line(self, match, lineno): + """Return one line element of a line_block.""" + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end(), + until_blank=True) + text = '\n'.join(indented) + text_nodes, messages = self.inline_text(text, lineno) + line = nodes.line(text, '', *text_nodes) + if match.string.rstrip() != '|': # not empty + line.indent = len(match.group(1)) - 1 + return line, messages, blank_finish + + def nest_line_block_lines(self, block): + for index in range(1, len(block)): + if getattr(block[index], 'indent', None) is None: + block[index].indent = block[index - 1].indent + self.nest_line_block_segment(block) + + def nest_line_block_segment(self, block): + indents = [item.indent for item in block] + least = min(indents) + new_items = [] + new_block = nodes.line_block() + for item in block: + if item.indent > least: + new_block.append(item) + else: + if len(new_block): + self.nest_line_block_segment(new_block) + new_items.append(new_block) + new_block = nodes.line_block() + new_items.append(item) + if len(new_block): + self.nest_line_block_segment(new_block) + new_items.append(new_block) + block[:] = new_items + + def grid_table_top(self, match, context, next_state): + """Top border of a full table.""" + return self.table_top(match, context, next_state, + self.isolate_grid_table, + tableparser.GridTableParser) + + def simple_table_top(self, match, context, next_state): + """Top border of a simple table.""" + return self.table_top(match, context, next_state, + self.isolate_simple_table, + tableparser.SimpleTableParser) + + def table_top(self, match, context, next_state, + isolate_function, parser_class): + """Top border of a generic table.""" + nodelist, blank_finish = self.table(isolate_function, parser_class) + self.parent += nodelist + if not blank_finish: + msg = self.reporter.warning( + 'Blank line required after table.', + line=self.state_machine.abs_line_number()+1) + self.parent += msg + return [], next_state, [] + + def table(self, isolate_function, parser_class): + """Parse a table.""" + block, messages, blank_finish = isolate_function() + if block: + try: + parser = parser_class() + tabledata = parser.parse(block) + tableline = (self.state_machine.abs_line_number() - len(block) + + 1) + table = self.build_table(tabledata, tableline) + nodelist = [table] + messages + except tableparser.TableMarkupError as err: + nodelist = self.malformed_table(block, ' '.join(err.args), + offset=err.offset) + messages + else: + nodelist = messages + return nodelist, blank_finish + + def isolate_grid_table(self): + messages = [] + blank_finish = 1 + try: + block = self.state_machine.get_text_block(flush_left=True) + except statemachine.UnexpectedIndentationError as err: + block, src, srcline = err.args + messages.append(self.reporter.error('Unexpected indentation.', + source=src, line=srcline)) + blank_finish = 0 + block.disconnect() + # for East Asian chars: + block.pad_double_width(self.double_width_pad_char) + width = len(block[0].strip()) + for i in range(len(block)): + block[i] = block[i].strip() + if block[i][0] not in '+|': # check left edge + blank_finish = 0 + self.state_machine.previous_line(len(block) - i) + del block[i:] + break + if not self.grid_table_top_pat.match(block[-1]): # find bottom + blank_finish = 0 + # from second-last to third line of table: + for i in range(len(block) - 2, 1, -1): + if self.grid_table_top_pat.match(block[i]): + self.state_machine.previous_line(len(block) - i + 1) + del block[i+1:] + break + else: + messages.extend(self.malformed_table(block)) + return [], messages, blank_finish + for i in range(len(block)): # check right edge + if len(block[i]) != width or block[i][-1] not in '+|': + messages.extend(self.malformed_table(block)) + return [], messages, blank_finish + return block, messages, blank_finish + + def isolate_simple_table(self): + start = self.state_machine.line_offset + lines = self.state_machine.input_lines + limit = len(lines) - 1 + toplen = len(lines[start].strip()) + pattern_match = self.simple_table_border_pat.match + found = 0 + found_at = None + i = start + 1 + while i <= limit: + line = lines[i] + match = pattern_match(line) + if match: + if len(line.strip()) != toplen: + self.state_machine.next_line(i - start) + messages = self.malformed_table( + lines[start:i+1], 'Bottom/header table border does ' + 'not match top border.') + return [], messages, i == limit or not lines[i+1].strip() + found += 1 + found_at = i + if found == 2 or i == limit or not lines[i+1].strip(): + end = i + break + i += 1 + else: # reached end of input_lines + if found: + extra = ' or no blank line after table bottom' + self.state_machine.next_line(found_at - start) + block = lines[start:found_at+1] + else: + extra = '' + self.state_machine.next_line(i - start - 1) + block = lines[start:] + messages = self.malformed_table( + block, 'No bottom table border found%s.' % extra) + return [], messages, not extra + self.state_machine.next_line(end - start) + block = lines[start:end+1] + # for East Asian chars: + block.pad_double_width(self.double_width_pad_char) + return block, [], end == limit or not lines[end+1].strip() + + def malformed_table(self, block, detail='', offset=0): + block.replace(self.double_width_pad_char, '') + data = '\n'.join(block) + message = 'Malformed table.' + startline = self.state_machine.abs_line_number() - len(block) + 1 + if detail: + message += '\n' + detail + error = self.reporter.error(message, nodes.literal_block(data, data), + line=startline+offset) + return [error] + + def build_table(self, tabledata, tableline, stub_columns=0, widths=None): + colwidths, headrows, bodyrows = tabledata + table = nodes.table() + if widths == 'auto': + table['classes'] += ['colwidths-auto'] + elif widths: # "grid" or list of integers + table['classes'] += ['colwidths-given'] + tgroup = nodes.tgroup(cols=len(colwidths)) + table += tgroup + for colwidth in colwidths: + colspec = nodes.colspec(colwidth=colwidth) + if stub_columns: + colspec.attributes['stub'] = 1 + stub_columns -= 1 + tgroup += colspec + if headrows: + thead = nodes.thead() + tgroup += thead + for row in headrows: + thead += self.build_table_row(row, tableline) + tbody = nodes.tbody() + tgroup += tbody + for row in bodyrows: + tbody += self.build_table_row(row, tableline) + return table + + def build_table_row(self, rowdata, tableline): + row = nodes.row() + for cell in rowdata: + if cell is None: + continue + morerows, morecols, offset, cellblock = cell + attributes = {} + if morerows: + attributes['morerows'] = morerows + if morecols: + attributes['morecols'] = morecols + entry = nodes.entry(**attributes) + row += entry + if ''.join(cellblock): + self.nested_parse(cellblock, input_offset=tableline+offset, + node=entry) + return row + + explicit = Struct() + """Patterns and constants used for explicit markup recognition.""" + + explicit.patterns = Struct( + target=re.compile(r""" + ( + _ # anonymous target + | # *OR* + (?!_) # no underscore at the beginning + (?P<quote>`?) # optional open quote + (?![ `]) # first char. not space or + # backquote + (?P<name> # reference name + .+? + ) + %(non_whitespace_escape_before)s + (?P=quote) # close quote if open quote used + ) + (?<!(?<!\x00):) # no unescaped colon at end + %(non_whitespace_escape_before)s + [ ]? # optional space + : # end of reference name + ([ ]+|$) # followed by whitespace + """ % vars(Inliner), re.VERBOSE), + reference=re.compile(r""" + ( + (?P<simple>%(simplename)s)_ + | # *OR* + ` # open backquote + (?![ ]) # not space + (?P<phrase>.+?) # hyperlink phrase + %(non_whitespace_escape_before)s + `_ # close backquote, + # reference mark + ) + $ # end of string + """ % vars(Inliner), re.VERBOSE), + substitution=re.compile(r""" + ( + (?![ ]) # first char. not space + (?P<name>.+?) # substitution text + %(non_whitespace_escape_before)s + \| # close delimiter + ) + ([ ]+|$) # followed by whitespace + """ % vars(Inliner), + re.VERBOSE),) + + def footnote(self, match): + src, srcline = self.state_machine.get_source_and_line() + (indented, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + label = match.group(1) + name = normalize_name(label) + footnote = nodes.footnote('\n'.join(indented)) + footnote.source = src + footnote.line = srcline + if name[0] == '#': # auto-numbered + name = name[1:] # autonumber label + footnote['auto'] = 1 + if name: + footnote['names'].append(name) + self.document.note_autofootnote(footnote) + elif name == '*': # auto-symbol + name = '' + footnote['auto'] = '*' + self.document.note_symbol_footnote(footnote) + else: # manually numbered + footnote += nodes.label('', label) + footnote['names'].append(name) + self.document.note_footnote(footnote) + if name: + self.document.note_explicit_target(footnote, footnote) + else: + self.document.set_id(footnote, footnote) + if indented: + self.nested_parse(indented, input_offset=offset, node=footnote) + return [footnote], blank_finish + + def citation(self, match): + src, srcline = self.state_machine.get_source_and_line() + (indented, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + label = match.group(1) + name = normalize_name(label) + citation = nodes.citation('\n'.join(indented)) + citation.source = src + citation.line = srcline + citation += nodes.label('', label) + citation['names'].append(name) + self.document.note_citation(citation) + self.document.note_explicit_target(citation, citation) + if indented: + self.nested_parse(indented, input_offset=offset, node=citation) + return [citation], blank_finish + + def hyperlink_target(self, match): + pattern = self.explicit.patterns.target + lineno = self.state_machine.abs_line_number() + (block, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented( + match.end(), until_blank=True, strip_indent=False) + blocktext = match.string[:match.end()] + '\n'.join(block) + block = [escape2null(line) for line in block] + escaped = block[0] + blockindex = 0 + while True: + targetmatch = pattern.match(escaped) + if targetmatch: + break + blockindex += 1 + try: + escaped += block[blockindex] + except IndexError: + raise MarkupError('malformed hyperlink target.') + del block[:blockindex] + block[0] = (block[0] + ' ')[targetmatch.end()-len(escaped)-1:].strip() + target = self.make_target(block, blocktext, lineno, + targetmatch.group('name')) + return [target], blank_finish + + def make_target(self, block, block_text, lineno, target_name): + target_type, data = self.parse_target(block, block_text, lineno) + if target_type == 'refname': + target = nodes.target(block_text, '', refname=normalize_name(data)) + target.indirect_reference_name = data + self.add_target(target_name, '', target, lineno) + self.document.note_indirect_target(target) + return target + elif target_type == 'refuri': + target = nodes.target(block_text, '') + self.add_target(target_name, data, target, lineno) + return target + else: + return data + + def parse_target(self, block, block_text, lineno): + """ + Determine the type of reference of a target. + + :Return: A 2-tuple, one of: + + - 'refname' and the indirect reference name + - 'refuri' and the URI + - 'malformed' and a system_message node + """ + if block and block[-1].strip()[-1:] == '_': # possible indirect target + reference = ' '.join(line.strip() for line in block) + refname = self.is_reference(reference) + if refname: + return 'refname', refname + ref_parts = split_escaped_whitespace(' '.join(block)) + reference = ' '.join(''.join(unescape(part).split()) + for part in ref_parts) + return 'refuri', reference + + def is_reference(self, reference): + match = self.explicit.patterns.reference.match( + whitespace_normalize_name(reference)) + if not match: + return None + return unescape(match.group('simple') or match.group('phrase')) + + def add_target(self, targetname, refuri, target, lineno): + target.line = lineno + if targetname: + name = normalize_name(unescape(targetname)) + target['names'].append(name) + if refuri: + uri = self.inliner.adjust_uri(refuri) + if uri: + target['refuri'] = uri + else: + raise ApplicationError('problem with URI: %r' % refuri) + self.document.note_explicit_target(target, self.parent) + else: # anonymous target + if refuri: + target['refuri'] = refuri + target['anonymous'] = 1 + self.document.note_anonymous_target(target) + + def substitution_def(self, match): + pattern = self.explicit.patterns.substitution + src, srcline = self.state_machine.get_source_and_line() + (block, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end(), + strip_indent=False) + blocktext = (match.string[:match.end()] + '\n'.join(block)) + block.disconnect() + escaped = escape2null(block[0].rstrip()) + blockindex = 0 + while True: + subdefmatch = pattern.match(escaped) + if subdefmatch: + break + blockindex += 1 + try: + escaped = escaped + ' ' + escape2null( + block[blockindex].strip()) + except IndexError: + raise MarkupError('malformed substitution definition.') + del block[:blockindex] # strip out the substitution marker + start = subdefmatch.end()-len(escaped)-1 + block[0] = (block[0].strip() + ' ')[start:-1] + if not block[0]: + del block[0] + offset += 1 + while block and not block[-1].strip(): + block.pop() + subname = subdefmatch.group('name') + substitution_node = nodes.substitution_definition(blocktext) + substitution_node.source = src + substitution_node.line = srcline + if not block: + msg = self.reporter.warning( + 'Substitution definition "%s" missing contents.' % subname, + nodes.literal_block(blocktext, blocktext), + source=src, line=srcline) + return [msg], blank_finish + block[0] = block[0].strip() + substitution_node['names'].append( + nodes.whitespace_normalize_name(subname)) + new_abs_offset, blank_finish = self.nested_list_parse( + block, input_offset=offset, node=substitution_node, + initial_state='SubstitutionDef', blank_finish=blank_finish) + i = 0 + for node in substitution_node[:]: + if not (isinstance(node, nodes.Inline) + or isinstance(node, nodes.Text)): + self.parent += substitution_node[i] + del substitution_node[i] + else: + i += 1 + for node in substitution_node.findall(nodes.Element): + if self.disallowed_inside_substitution_definitions(node): + pformat = nodes.literal_block('', node.pformat().rstrip()) + msg = self.reporter.error( + 'Substitution definition contains illegal element <%s>:' + % node.tagname, + pformat, nodes.literal_block(blocktext, blocktext), + source=src, line=srcline) + return [msg], blank_finish + if len(substitution_node) == 0: + msg = self.reporter.warning( + 'Substitution definition "%s" empty or invalid.' % subname, + nodes.literal_block(blocktext, blocktext), + source=src, line=srcline) + return [msg], blank_finish + self.document.note_substitution_def( + substitution_node, subname, self.parent) + return [substitution_node], blank_finish + + def disallowed_inside_substitution_definitions(self, node): + if (node['ids'] + or isinstance(node, nodes.reference) and node.get('anonymous') + or isinstance(node, nodes.footnote_reference) and node.get('auto')): # noqa: E501 + return True + else: + return False + + def directive(self, match, **option_presets): + """Returns a 2-tuple: list of nodes, and a "blank finish" boolean.""" + type_name = match.group(1) + directive_class, messages = directives.directive( + type_name, self.memo.language, self.document) + self.parent += messages + if directive_class: + return self.run_directive( + directive_class, match, type_name, option_presets) + else: + return self.unknown_directive(type_name) + + def run_directive(self, directive, match, type_name, option_presets): + """ + Parse a directive then run its directive function. + + Parameters: + + - `directive`: The class implementing the directive. Must be + a subclass of `rst.Directive`. + + - `match`: A regular expression match object which matched the first + line of the directive. + + - `type_name`: The directive name, as used in the source text. + + - `option_presets`: A dictionary of preset options, defaults for the + directive options. Currently, only an "alt" option is passed by + substitution definitions (value: the substitution name), which may + be used by an embedded image directive. + + Returns a 2-tuple: list of nodes, and a "blank finish" boolean. + """ + if isinstance(directive, (FunctionType, MethodType)): + from docutils.parsers.rst import convert_directive_function + directive = convert_directive_function(directive) + lineno = self.state_machine.abs_line_number() + initial_line_offset = self.state_machine.line_offset + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end(), + strip_top=0) + block_text = '\n'.join(self.state_machine.input_lines[ + initial_line_offset : self.state_machine.line_offset + 1]) # noqa: E203,E501 + try: + arguments, options, content, content_offset = ( + self.parse_directive_block(indented, line_offset, + directive, option_presets)) + except MarkupError as detail: + error = self.reporter.error( + 'Error in "%s" directive:\n%s.' % (type_name, + ' '.join(detail.args)), + nodes.literal_block(block_text, block_text), line=lineno) + return [error], blank_finish + directive_instance = directive( + type_name, arguments, options, content, lineno, + content_offset, block_text, self, self.state_machine) + try: + result = directive_instance.run() + except docutils.parsers.rst.DirectiveError as error: + msg_node = self.reporter.system_message(error.level, error.msg, + line=lineno) + msg_node += nodes.literal_block(block_text, block_text) + result = [msg_node] + assert isinstance(result, list), \ + 'Directive "%s" must return a list of nodes.' % type_name + for i in range(len(result)): + assert isinstance(result[i], nodes.Node), \ + ('Directive "%s" returned non-Node object (index %s): %r' + % (type_name, i, result[i])) + return (result, + blank_finish or self.state_machine.is_next_line_blank()) + + def parse_directive_block(self, indented, line_offset, directive, + option_presets): + option_spec = directive.option_spec + has_content = directive.has_content + if indented and not indented[0].strip(): + indented.trim_start() + line_offset += 1 + while indented and not indented[-1].strip(): + indented.trim_end() + if indented and (directive.required_arguments + or directive.optional_arguments + or option_spec): + for i, line in enumerate(indented): + if not line.strip(): + break + else: + i += 1 + arg_block = indented[:i] + content = indented[i+1:] + content_offset = line_offset + i + 1 + else: + content = indented + content_offset = line_offset + arg_block = [] + if option_spec: + options, arg_block = self.parse_directive_options( + option_presets, option_spec, arg_block) + else: + options = {} + if arg_block and not (directive.required_arguments + or directive.optional_arguments): + content = arg_block + indented[i:] + content_offset = line_offset + arg_block = [] + while content and not content[0].strip(): + content.trim_start() + content_offset += 1 + if directive.required_arguments or directive.optional_arguments: + arguments = self.parse_directive_arguments( + directive, arg_block) + else: + arguments = [] + if content and not has_content: + raise MarkupError('no content permitted') + return arguments, options, content, content_offset + + def parse_directive_options(self, option_presets, option_spec, arg_block): + options = option_presets.copy() + for i, line in enumerate(arg_block): + if re.match(Body.patterns['field_marker'], line): + opt_block = arg_block[i:] + arg_block = arg_block[:i] + break + else: + opt_block = [] + if opt_block: + success, data = self.parse_extension_options(option_spec, + opt_block) + if success: # data is a dict of options + options.update(data) + else: # data is an error string + raise MarkupError(data) + return options, arg_block + + def parse_directive_arguments(self, directive, arg_block): + required = directive.required_arguments + optional = directive.optional_arguments + arg_text = '\n'.join(arg_block) + arguments = arg_text.split() + if len(arguments) < required: + raise MarkupError('%s argument(s) required, %s supplied' + % (required, len(arguments))) + elif len(arguments) > required + optional: + if directive.final_argument_whitespace: + arguments = arg_text.split(None, required + optional - 1) + else: + raise MarkupError( + 'maximum %s argument(s) allowed, %s supplied' + % (required + optional, len(arguments))) + return arguments + + def parse_extension_options(self, option_spec, datalines): + """ + Parse `datalines` for a field list containing extension options + matching `option_spec`. + + :Parameters: + - `option_spec`: a mapping of option name to conversion + function, which should raise an exception on bad input. + - `datalines`: a list of input strings. + + :Return: + - Success value, 1 or 0. + - An option dictionary on success, an error string on failure. + """ + node = nodes.field_list() + newline_offset, blank_finish = self.nested_list_parse( + datalines, 0, node, initial_state='ExtensionOptions', + blank_finish=True) + if newline_offset != len(datalines): # incomplete parse of block + return 0, 'invalid option block' + try: + options = utils.extract_extension_options(node, option_spec) + except KeyError as detail: + return 0, 'unknown option: "%s"' % detail.args[0] + except (ValueError, TypeError) as detail: + return 0, 'invalid option value: %s' % ' '.join(detail.args) + except utils.ExtensionOptionError as detail: + return 0, 'invalid option data: %s' % ' '.join(detail.args) + if blank_finish: + return 1, options + else: + return 0, 'option data incompletely parsed' + + def unknown_directive(self, type_name): + lineno = self.state_machine.abs_line_number() + (indented, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented(0, strip_indent=False) + text = '\n'.join(indented) + error = self.reporter.error('Unknown directive type "%s".' % type_name, + nodes.literal_block(text, text), + line=lineno) + return [error], blank_finish + + def comment(self, match): + if self.state_machine.is_next_line_blank(): + first_comment_line = match.string[match.end():] + if not first_comment_line.strip(): # empty comment + return [nodes.comment()], True # "A tiny but practical wart." + if first_comment_line.startswith('end of inclusion from "'): + # cf. parsers.rst.directives.misc.Include + self.document.include_log.pop() + return [], True + (indented, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + while indented and not indented[-1].strip(): + indented.trim_end() + text = '\n'.join(indented) + return [nodes.comment(text, text)], blank_finish + + explicit.constructs = [ + (footnote, + re.compile(r""" + \.\.[ ]+ # explicit markup start + \[ + ( # footnote label: + [0-9]+ # manually numbered footnote + | # *OR* + \# # anonymous auto-numbered footnote + | # *OR* + \#%s # auto-number ed?) footnote label + | # *OR* + \* # auto-symbol footnote + ) + \] + ([ ]+|$) # whitespace or end of line + """ % Inliner.simplename, re.VERBOSE)), + (citation, + re.compile(r""" + \.\.[ ]+ # explicit markup start + \[(%s)\] # citation label + ([ ]+|$) # whitespace or end of line + """ % Inliner.simplename, re.VERBOSE)), + (hyperlink_target, + re.compile(r""" + \.\.[ ]+ # explicit markup start + _ # target indicator + (?![ ]|$) # first char. not space or EOL + """, re.VERBOSE)), + (substitution_def, + re.compile(r""" + \.\.[ ]+ # explicit markup start + \| # substitution indicator + (?![ ]|$) # first char. not space or EOL + """, re.VERBOSE)), + (directive, + re.compile(r""" + \.\.[ ]+ # explicit markup start + (%s) # directive name + [ ]? # optional space + :: # directive delimiter + ([ ]+|$) # whitespace or end of line + """ % Inliner.simplename, re.VERBOSE))] + + def explicit_markup(self, match, context, next_state): + """Footnotes, hyperlink targets, directives, comments.""" + nodelist, blank_finish = self.explicit_construct(match) + self.parent += nodelist + self.explicit_list(blank_finish) + return [], next_state, [] + + def explicit_construct(self, match): + """Determine which explicit construct this is, parse & return it.""" + errors = [] + for method, pattern in self.explicit.constructs: + expmatch = pattern.match(match.string) + if expmatch: + try: + return method(self, expmatch) + except MarkupError as error: + lineno = self.state_machine.abs_line_number() + message = ' '.join(error.args) + errors.append(self.reporter.warning(message, line=lineno)) + break + nodelist, blank_finish = self.comment(match) + return nodelist + errors, blank_finish + + def explicit_list(self, blank_finish): + """ + Create a nested state machine for a series of explicit markup + constructs (including anonymous hyperlink targets). + """ + offset = self.state_machine.line_offset + 1 # next line + newline_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=self.parent, initial_state='Explicit', + blank_finish=blank_finish, + match_titles=self.state_machine.match_titles) + self.goto_line(newline_offset) + if not blank_finish: + self.parent += self.unindent_warning('Explicit markup') + + def anonymous(self, match, context, next_state): + """Anonymous hyperlink targets.""" + nodelist, blank_finish = self.anonymous_target(match) + self.parent += nodelist + self.explicit_list(blank_finish) + return [], next_state, [] + + def anonymous_target(self, match): + lineno = self.state_machine.abs_line_number() + (block, indent, offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end(), + until_blank=True) + blocktext = match.string[:match.end()] + '\n'.join(block) + block = [escape2null(line) for line in block] + target = self.make_target(block, blocktext, lineno, '') + return [target], blank_finish + + def line(self, match, context, next_state): + """Section title overline or transition marker.""" + if self.state_machine.match_titles: + return [match.string], 'Line', [] + elif match.string.strip() == '::': + raise statemachine.TransitionCorrection('text') + elif len(match.string.strip()) < 4: + msg = self.reporter.info( + 'Unexpected possible title overline or transition.\n' + "Treating it as ordinary text because it's so short.", + line=self.state_machine.abs_line_number()) + self.parent += msg + raise statemachine.TransitionCorrection('text') + else: + blocktext = self.state_machine.line + msg = self.reporter.severe( + 'Unexpected section title or transition.', + nodes.literal_block(blocktext, blocktext), + line=self.state_machine.abs_line_number()) + self.parent += msg + return [], next_state, [] + + def text(self, match, context, next_state): + """Titles, definition lists, paragraphs.""" + return [match.string], 'Text', [] + + +class RFC2822Body(Body): + + """ + RFC2822 headers are only valid as the first constructs in documents. As + soon as anything else appears, the `Body` state should take over. + """ + + patterns = Body.patterns.copy() # can't modify the original + patterns['rfc2822'] = r'[!-9;-~]+:( +|$)' + initial_transitions = [(name, 'Body') + for name in Body.initial_transitions] + initial_transitions.insert(-1, ('rfc2822', 'Body')) # just before 'text' + + def rfc2822(self, match, context, next_state): + """RFC2822-style field list item.""" + fieldlist = nodes.field_list(classes=['rfc2822']) + self.parent += fieldlist + field, blank_finish = self.rfc2822_field(match) + fieldlist += field + offset = self.state_machine.line_offset + 1 # next line + newline_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=fieldlist, initial_state='RFC2822List', + blank_finish=blank_finish) + self.goto_line(newline_offset) + if not blank_finish: + self.parent += self.unindent_warning( + 'RFC2822-style field list') + return [], next_state, [] + + def rfc2822_field(self, match): + name = match.string[:match.string.find(':')] + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end(), + until_blank=True) + fieldnode = nodes.field() + fieldnode += nodes.field_name(name, name) + fieldbody = nodes.field_body('\n'.join(indented)) + fieldnode += fieldbody + if indented: + self.nested_parse(indented, input_offset=line_offset, + node=fieldbody) + return fieldnode, blank_finish + + +class SpecializedBody(Body): + + """ + Superclass for second and subsequent compound element members. Compound + elements are lists and list-like constructs. + + All transition methods are disabled (redefined as `invalid_input`). + Override individual methods in subclasses to re-enable. + + For example, once an initial bullet list item, say, is recognized, the + `BulletList` subclass takes over, with a "bullet_list" node as its + container. Upon encountering the initial bullet list item, `Body.bullet` + calls its ``self.nested_list_parse`` (`RSTState.nested_list_parse`), which + starts up a nested parsing session with `BulletList` as the initial state. + Only the ``bullet`` transition method is enabled in `BulletList`; as long + as only bullet list items are encountered, they are parsed and inserted + into the container. The first construct which is *not* a bullet list item + triggers the `invalid_input` method, which ends the nested parse and + closes the container. `BulletList` needs to recognize input that is + invalid in the context of a bullet list, which means everything *other + than* bullet list items, so it inherits the transition list created in + `Body`. + """ + + def invalid_input(self, match=None, context=None, next_state=None): + """Not a compound element member. Abort this state machine.""" + self.state_machine.previous_line() # back up so parent SM can reassess + raise EOFError + + indent = invalid_input + bullet = invalid_input + enumerator = invalid_input + field_marker = invalid_input + option_marker = invalid_input + doctest = invalid_input + line_block = invalid_input + grid_table_top = invalid_input + simple_table_top = invalid_input + explicit_markup = invalid_input + anonymous = invalid_input + line = invalid_input + text = invalid_input + + +class BulletList(SpecializedBody): + + """Second and subsequent bullet_list list_items.""" + + def bullet(self, match, context, next_state): + """Bullet list item.""" + if match.string[0] != self.parent['bullet']: + # different bullet: new list + self.invalid_input() + listitem, blank_finish = self.list_item(match.end()) + self.parent += listitem + self.blank_finish = blank_finish + return [], next_state, [] + + +class DefinitionList(SpecializedBody): + + """Second and subsequent definition_list_items.""" + + def text(self, match, context, next_state): + """Definition lists.""" + return [match.string], 'Definition', [] + + +class EnumeratedList(SpecializedBody): + + """Second and subsequent enumerated_list list_items.""" + + def enumerator(self, match, context, next_state): + """Enumerated list item.""" + format, sequence, text, ordinal = self.parse_enumerator( + match, self.parent['enumtype']) + if (format != self.format + or (sequence != '#' and (sequence != self.parent['enumtype'] + or self.auto + or ordinal != (self.lastordinal + 1))) + or not self.is_enumerated_list_item(ordinal, sequence, format)): + # different enumeration: new list + self.invalid_input() + if sequence == '#': + self.auto = 1 + listitem, blank_finish = self.list_item(match.end()) + self.parent += listitem + self.blank_finish = blank_finish + self.lastordinal = ordinal + return [], next_state, [] + + +class FieldList(SpecializedBody): + + """Second and subsequent field_list fields.""" + + def field_marker(self, match, context, next_state): + """Field list field.""" + field, blank_finish = self.field(match) + self.parent += field + self.blank_finish = blank_finish + return [], next_state, [] + + +class OptionList(SpecializedBody): + + """Second and subsequent option_list option_list_items.""" + + def option_marker(self, match, context, next_state): + """Option list item.""" + try: + option_list_item, blank_finish = self.option_list_item(match) + except MarkupError: + self.invalid_input() + self.parent += option_list_item + self.blank_finish = blank_finish + return [], next_state, [] + + +class RFC2822List(SpecializedBody, RFC2822Body): + + """Second and subsequent RFC2822-style field_list fields.""" + + patterns = RFC2822Body.patterns + initial_transitions = RFC2822Body.initial_transitions + + def rfc2822(self, match, context, next_state): + """RFC2822-style field list item.""" + field, blank_finish = self.rfc2822_field(match) + self.parent += field + self.blank_finish = blank_finish + return [], 'RFC2822List', [] + + blank = SpecializedBody.invalid_input + + +class ExtensionOptions(FieldList): + + """ + Parse field_list fields for extension options. + + No nested parsing is done (including inline markup parsing). + """ + + def parse_field_body(self, indented, offset, node): + """Override `Body.parse_field_body` for simpler parsing.""" + lines = [] + for line in list(indented) + ['']: + if line.strip(): + lines.append(line) + elif lines: + text = '\n'.join(lines) + node += nodes.paragraph(text, text) + lines = [] + + +class LineBlock(SpecializedBody): + + """Second and subsequent lines of a line_block.""" + + blank = SpecializedBody.invalid_input + + def line_block(self, match, context, next_state): + """New line of line block.""" + lineno = self.state_machine.abs_line_number() + line, messages, blank_finish = self.line_block_line(match, lineno) + self.parent += line + self.parent.parent += messages + self.blank_finish = blank_finish + return [], next_state, [] + + +class Explicit(SpecializedBody): + + """Second and subsequent explicit markup construct.""" + + def explicit_markup(self, match, context, next_state): + """Footnotes, hyperlink targets, directives, comments.""" + nodelist, blank_finish = self.explicit_construct(match) + self.parent += nodelist + self.blank_finish = blank_finish + return [], next_state, [] + + def anonymous(self, match, context, next_state): + """Anonymous hyperlink targets.""" + nodelist, blank_finish = self.anonymous_target(match) + self.parent += nodelist + self.blank_finish = blank_finish + return [], next_state, [] + + blank = SpecializedBody.invalid_input + + +class SubstitutionDef(Body): + + """ + Parser for the contents of a substitution_definition element. + """ + + patterns = { + 'embedded_directive': re.compile(r'(%s)::( +|$)' + % Inliner.simplename), + 'text': r''} + initial_transitions = ['embedded_directive', 'text'] + + def embedded_directive(self, match, context, next_state): + nodelist, blank_finish = self.directive(match, + alt=self.parent['names'][0]) + self.parent += nodelist + if not self.state_machine.at_eof(): + self.blank_finish = blank_finish + raise EOFError + + def text(self, match, context, next_state): + if not self.state_machine.at_eof(): + self.blank_finish = self.state_machine.is_next_line_blank() + raise EOFError + + +class Text(RSTState): + + """ + Classifier of second line of a text block. + + Could be a paragraph, a definition list item, or a title. + """ + + patterns = {'underline': Body.patterns['line'], + 'text': r''} + initial_transitions = [('underline', 'Body'), ('text', 'Body')] + + def blank(self, match, context, next_state): + """End of paragraph.""" + # NOTE: self.paragraph returns [node, system_message(s)], literalnext + paragraph, literalnext = self.paragraph( + context, self.state_machine.abs_line_number() - 1) + self.parent += paragraph + if literalnext: + self.parent += self.literal_block() + return [], 'Body', [] + + def eof(self, context): + if context: + self.blank(None, context, None) + return [] + + def indent(self, match, context, next_state): + """Definition list item.""" + dl = nodes.definition_list() + # the definition list starts on the line before the indent: + lineno = self.state_machine.abs_line_number() - 1 + dl.source, dl.line = self.state_machine.get_source_and_line(lineno) + dl_item, blank_finish = self.definition_list_item(context) + dl += dl_item + self.parent += dl + offset = self.state_machine.line_offset + 1 # next line + newline_offset, blank_finish = self.nested_list_parse( + self.state_machine.input_lines[offset:], + input_offset=self.state_machine.abs_line_offset() + 1, + node=dl, initial_state='DefinitionList', + blank_finish=blank_finish, blank_finish_state='Definition') + self.goto_line(newline_offset) + if not blank_finish: + self.parent += self.unindent_warning('Definition list') + return [], 'Body', [] + + def underline(self, match, context, next_state): + """Section title.""" + lineno = self.state_machine.abs_line_number() + title = context[0].rstrip() + underline = match.string.rstrip() + source = title + '\n' + underline + messages = [] + if column_width(title) > len(underline): + if len(underline) < 4: + if self.state_machine.match_titles: + msg = self.reporter.info( + 'Possible title underline, too short for the title.\n' + "Treating it as ordinary text because it's so short.", + line=lineno) + self.parent += msg + raise statemachine.TransitionCorrection('text') + else: + blocktext = context[0] + '\n' + self.state_machine.line + msg = self.reporter.warning( + 'Title underline too short.', + nodes.literal_block(blocktext, blocktext), + line=lineno) + messages.append(msg) + if not self.state_machine.match_titles: + blocktext = context[0] + '\n' + self.state_machine.line + # We need get_source_and_line() here to report correctly + src, srcline = self.state_machine.get_source_and_line() + # TODO: why is abs_line_number() == srcline+1 + # if the error is in a table (try with test_tables.py)? + # print("get_source_and_line", srcline) + # print("abs_line_number", self.state_machine.abs_line_number()) + msg = self.reporter.severe( + 'Unexpected section title.', + nodes.literal_block(blocktext, blocktext), + source=src, line=srcline) + self.parent += messages + self.parent += msg + return [], next_state, [] + style = underline[0] + context[:] = [] + self.section(title, source, style, lineno - 1, messages) + return [], next_state, [] + + def text(self, match, context, next_state): + """Paragraph.""" + startline = self.state_machine.abs_line_number() - 1 + msg = None + try: + block = self.state_machine.get_text_block(flush_left=True) + except statemachine.UnexpectedIndentationError as err: + block, src, srcline = err.args + msg = self.reporter.error('Unexpected indentation.', + source=src, line=srcline) + lines = context + list(block) + paragraph, literalnext = self.paragraph(lines, startline) + self.parent += paragraph + self.parent += msg + if literalnext: + try: + self.state_machine.next_line() + except EOFError: + pass + self.parent += self.literal_block() + return [], next_state, [] + + def literal_block(self): + """Return a list of nodes.""" + (indented, indent, offset, blank_finish + ) = self.state_machine.get_indented() + while indented and not indented[-1].strip(): + indented.trim_end() + if not indented: + return self.quoted_literal_block() + data = '\n'.join(indented) + literal_block = nodes.literal_block(data, data) + (literal_block.source, + literal_block.line) = self.state_machine.get_source_and_line(offset+1) + nodelist = [literal_block] + if not blank_finish: + nodelist.append(self.unindent_warning('Literal block')) + return nodelist + + def quoted_literal_block(self): + abs_line_offset = self.state_machine.abs_line_offset() + offset = self.state_machine.line_offset + parent_node = nodes.Element() + new_abs_offset = self.nested_parse( + self.state_machine.input_lines[offset:], + input_offset=abs_line_offset, node=parent_node, match_titles=False, + state_machine_kwargs={'state_classes': (QuotedLiteralBlock,), + 'initial_state': 'QuotedLiteralBlock'}) + self.goto_line(new_abs_offset) + return parent_node.children + + def definition_list_item(self, termline): + # the parser is already on the second (indented) line: + dd_lineno = self.state_machine.abs_line_number() + dt_lineno = dd_lineno - 1 + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_indented() + dl_item = nodes.definition_list_item( + '\n'.join(termline + list(indented))) + (dl_item.source, + dl_item.line) = self.state_machine.get_source_and_line(dt_lineno) + dt_nodes, messages = self.term(termline, dt_lineno) + dl_item += dt_nodes + dd = nodes.definition('', *messages) + dd.source, dd.line = self.state_machine.get_source_and_line(dd_lineno) + dl_item += dd + if termline[0][-2:] == '::': + dd += self.reporter.info( + 'Blank line missing before literal block (after the "::")? ' + 'Interpreted as a definition list item.', + line=dd_lineno) + # TODO: drop a definition if it is an empty comment to allow + # definition list items with several terms? + # https://sourceforge.net/p/docutils/feature-requests/60/ + self.nested_parse(indented, input_offset=line_offset, node=dd) + return dl_item, blank_finish + + classifier_delimiter = re.compile(' +: +') + + def term(self, lines, lineno): + """Return a definition_list's term and optional classifiers.""" + assert len(lines) == 1 + text_nodes, messages = self.inline_text(lines[0], lineno) + dt = nodes.term(lines[0]) + dt.source, dt.line = self.state_machine.get_source_and_line(lineno) + node_list = [dt] + for i in range(len(text_nodes)): + node = text_nodes[i] + if isinstance(node, nodes.Text): + parts = self.classifier_delimiter.split(node) + if len(parts) == 1: + node_list[-1] += node + else: + text = parts[0].rstrip() + textnode = nodes.Text(text) + node_list[-1] += textnode + for part in parts[1:]: + node_list.append( + nodes.classifier(unescape(part, True), part)) + else: + node_list[-1] += node + return node_list, messages + + +class SpecializedText(Text): + + """ + Superclass for second and subsequent lines of Text-variants. + + All transition methods are disabled. Override individual methods in + subclasses to re-enable. + """ + + def eof(self, context): + """Incomplete construct.""" + return [] + + def invalid_input(self, match=None, context=None, next_state=None): + """Not a compound element member. Abort this state machine.""" + raise EOFError + + blank = invalid_input + indent = invalid_input + underline = invalid_input + text = invalid_input + + +class Definition(SpecializedText): + + """Second line of potential definition_list_item.""" + + def eof(self, context): + """Not a definition.""" + self.state_machine.previous_line(2) # so parent SM can reassess + return [] + + def indent(self, match, context, next_state): + """Definition list item.""" + dl_item, blank_finish = self.definition_list_item(context) + self.parent += dl_item + self.blank_finish = blank_finish + return [], 'DefinitionList', [] + + +class Line(SpecializedText): + + """ + Second line of over- & underlined section title or transition marker. + """ + + eofcheck = 1 # @@@ ??? + """Set to 0 while parsing sections, so that we don't catch the EOF.""" + + def eof(self, context): + """Transition marker at end of section or document.""" + marker = context[0].strip() + if self.memo.section_bubble_up_kludge: + self.memo.section_bubble_up_kludge = False + elif len(marker) < 4: + self.state_correction(context) + if self.eofcheck: # ignore EOFError with sections + src, srcline = self.state_machine.get_source_and_line() + # lineno = self.state_machine.abs_line_number() - 1 + transition = nodes.transition(rawsource=context[0]) + transition.source = src + transition.line = srcline - 1 + # transition.line = lineno + self.parent += transition + self.eofcheck = 1 + return [] + + def blank(self, match, context, next_state): + """Transition marker.""" + src, srcline = self.state_machine.get_source_and_line() + marker = context[0].strip() + if len(marker) < 4: + self.state_correction(context) + transition = nodes.transition(rawsource=marker) + transition.source = src + transition.line = srcline - 1 + self.parent += transition + return [], 'Body', [] + + def text(self, match, context, next_state): + """Potential over- & underlined title.""" + lineno = self.state_machine.abs_line_number() - 1 + overline = context[0] + title = match.string + underline = '' + try: + underline = self.state_machine.next_line() + except EOFError: + blocktext = overline + '\n' + title + if len(overline.rstrip()) < 4: + self.short_overline(context, blocktext, lineno, 2) + else: + msg = self.reporter.severe( + 'Incomplete section title.', + nodes.literal_block(blocktext, blocktext), + line=lineno) + self.parent += msg + return [], 'Body', [] + source = '%s\n%s\n%s' % (overline, title, underline) + overline = overline.rstrip() + underline = underline.rstrip() + if not self.transitions['underline'][0].match(underline): + blocktext = overline + '\n' + title + '\n' + underline + if len(overline.rstrip()) < 4: + self.short_overline(context, blocktext, lineno, 2) + else: + msg = self.reporter.severe( + 'Missing matching underline for section title overline.', + nodes.literal_block(source, source), + line=lineno) + self.parent += msg + return [], 'Body', [] + elif overline != underline: + blocktext = overline + '\n' + title + '\n' + underline + if len(overline.rstrip()) < 4: + self.short_overline(context, blocktext, lineno, 2) + else: + msg = self.reporter.severe( + 'Title overline & underline mismatch.', + nodes.literal_block(source, source), + line=lineno) + self.parent += msg + return [], 'Body', [] + title = title.rstrip() + messages = [] + if column_width(title) > len(overline): + blocktext = overline + '\n' + title + '\n' + underline + if len(overline.rstrip()) < 4: + self.short_overline(context, blocktext, lineno, 2) + else: + msg = self.reporter.warning( + 'Title overline too short.', + nodes.literal_block(source, source), + line=lineno) + messages.append(msg) + style = (overline[0], underline[0]) + self.eofcheck = 0 # @@@ not sure this is correct + self.section(title.lstrip(), source, style, lineno + 1, messages) + self.eofcheck = 1 + return [], 'Body', [] + + indent = text # indented title + + def underline(self, match, context, next_state): + overline = context[0] + blocktext = overline + '\n' + self.state_machine.line + lineno = self.state_machine.abs_line_number() - 1 + if len(overline.rstrip()) < 4: + self.short_overline(context, blocktext, lineno, 1) + msg = self.reporter.error( + 'Invalid section title or transition marker.', + nodes.literal_block(blocktext, blocktext), + line=lineno) + self.parent += msg + return [], 'Body', [] + + def short_overline(self, context, blocktext, lineno, lines=1): + msg = self.reporter.info( + 'Possible incomplete section title.\nTreating the overline as ' + "ordinary text because it's so short.", + line=lineno) + self.parent += msg + self.state_correction(context, lines) + + def state_correction(self, context, lines=1): + self.state_machine.previous_line(lines) + context[:] = [] + raise statemachine.StateCorrection('Body', 'text') + + +class QuotedLiteralBlock(RSTState): + + """ + Nested parse handler for quoted (unindented) literal blocks. + + Special-purpose. Not for inclusion in `state_classes`. + """ + + patterns = {'initial_quoted': r'(%(nonalphanum7bit)s)' % Body.pats, + 'text': r''} + initial_transitions = ('initial_quoted', 'text') + + def __init__(self, state_machine, debug=False): + RSTState.__init__(self, state_machine, debug) + self.messages = [] + self.initial_lineno = None + + def blank(self, match, context, next_state): + if context: + raise EOFError + else: + return context, next_state, [] + + def eof(self, context): + if context: + src, srcline = self.state_machine.get_source_and_line( + self.initial_lineno) + text = '\n'.join(context) + literal_block = nodes.literal_block(text, text) + literal_block.source = src + literal_block.line = srcline + self.parent += literal_block + else: + self.parent += self.reporter.warning( + 'Literal block expected; none found.', + line=self.state_machine.abs_line_number() + ) # src not available, statemachine.input_lines is empty + self.state_machine.previous_line() + self.parent += self.messages + return [] + + def indent(self, match, context, next_state): + assert context, ('QuotedLiteralBlock.indent: context should not ' + 'be empty!') + self.messages.append( + self.reporter.error('Unexpected indentation.', + line=self.state_machine.abs_line_number())) + self.state_machine.previous_line() + raise EOFError + + def initial_quoted(self, match, context, next_state): + """Match arbitrary quote character on the first line only.""" + self.remove_transition('initial_quoted') + quote = match.string[0] + pattern = re.compile(re.escape(quote)) + # New transition matches consistent quotes only: + self.add_transition('quoted', + (pattern, self.quoted, self.__class__.__name__)) + self.initial_lineno = self.state_machine.abs_line_number() + return [match.string], next_state, [] + + def quoted(self, match, context, next_state): + """Match consistent quotes on subsequent lines.""" + context.append(match.string) + return context, next_state, [] + + def text(self, match, context, next_state): + if context: + self.messages.append( + self.reporter.error('Inconsistent literal block quoting.', + line=self.state_machine.abs_line_number())) + self.state_machine.previous_line() + raise EOFError + + +state_classes = (Body, BulletList, DefinitionList, EnumeratedList, FieldList, + OptionList, LineBlock, ExtensionOptions, Explicit, Text, + Definition, Line, SubstitutionDef, RFC2822Body, RFC2822List) +"""Standard set of State classes used to start `RSTStateMachine`.""" diff --git a/.venv/lib/python3.12/site-packages/docutils/parsers/rst/tableparser.py b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/tableparser.py new file mode 100644 index 00000000..1e0e2be2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/parsers/rst/tableparser.py @@ -0,0 +1,539 @@ +# $Id: tableparser.py 9038 2022-03-05 23:31:46Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +This module defines table parser classes,which parse plaintext-graphic tables +and produce a well-formed data structure suitable for building a CALS table. + +:Classes: + - `GridTableParser`: Parse fully-formed tables represented with a grid. + - `SimpleTableParser`: Parse simple tables, delimited by top & bottom + borders. + +:Exception class: `TableMarkupError` + +:Function: + `update_dict_of_lists()`: Merge two dictionaries containing list values. +""" + +__docformat__ = 'reStructuredText' + + +import re +import sys +from docutils import DataError +from docutils.utils import strip_combining_chars + + +class TableMarkupError(DataError): + + """ + Raise if there is any problem with table markup. + + The keyword argument `offset` denotes the offset of the problem + from the table's start line. + """ + + def __init__(self, *args, **kwargs): + self.offset = kwargs.pop('offset', 0) + DataError.__init__(self, *args) + + +class TableParser: + + """ + Abstract superclass for the common parts of the syntax-specific parsers. + """ + + head_body_separator_pat = None + """Matches the row separator between head rows and body rows.""" + + double_width_pad_char = '\x00' + """Padding character for East Asian double-width text.""" + + def parse(self, block): + """ + Analyze the text `block` and return a table data structure. + + Given a plaintext-graphic table in `block` (list of lines of text; no + whitespace padding), parse the table, construct and return the data + necessary to construct a CALS table or equivalent. + + Raise `TableMarkupError` if there is any problem with the markup. + """ + self.setup(block) + self.find_head_body_sep() + self.parse_table() + return self.structure_from_cells() + + def find_head_body_sep(self): + """Look for a head/body row separator line; store the line index.""" + for i in range(len(self.block)): + line = self.block[i] + if self.head_body_separator_pat.match(line): + if self.head_body_sep: + raise TableMarkupError( + 'Multiple head/body row separators ' + '(table lines %s and %s); only one allowed.' + % (self.head_body_sep+1, i+1), offset=i) + else: + self.head_body_sep = i + self.block[i] = line.replace('=', '-') + if self.head_body_sep == 0 or self.head_body_sep == (len(self.block) + - 1): + raise TableMarkupError('The head/body row separator may not be ' + 'the first or last line of the table.', + offset=i) + + +class GridTableParser(TableParser): + + """ + Parse a grid table using `parse()`. + + Here's an example of a grid table:: + + +------------------------+------------+----------+----------+ + | Header row, column 1 | Header 2 | Header 3 | Header 4 | + +========================+============+==========+==========+ + | body row 1, column 1 | column 2 | column 3 | column 4 | + +------------------------+------------+----------+----------+ + | body row 2 | Cells may span columns. | + +------------------------+------------+---------------------+ + | body row 3 | Cells may | - Table cells | + +------------------------+ span rows. | - contain | + | body row 4 | | - body elements. | + +------------------------+------------+---------------------+ + + Intersections use '+', row separators use '-' (except for one optional + head/body row separator, which uses '='), and column separators use '|'. + + Passing the above table to the `parse()` method will result in the + following data structure:: + + ([24, 12, 10, 10], + [[(0, 0, 1, ['Header row, column 1']), + (0, 0, 1, ['Header 2']), + (0, 0, 1, ['Header 3']), + (0, 0, 1, ['Header 4'])]], + [[(0, 0, 3, ['body row 1, column 1']), + (0, 0, 3, ['column 2']), + (0, 0, 3, ['column 3']), + (0, 0, 3, ['column 4'])], + [(0, 0, 5, ['body row 2']), + (0, 2, 5, ['Cells may span columns.']), + None, + None], + [(0, 0, 7, ['body row 3']), + (1, 0, 7, ['Cells may', 'span rows.', '']), + (1, 1, 7, ['- Table cells', '- contain', '- body elements.']), + None], + [(0, 0, 9, ['body row 4']), None, None, None]]) + + The first item is a list containing column widths (colspecs). The second + item is a list of head rows, and the third is a list of body rows. Each + row contains a list of cells. Each cell is either None (for a cell unused + because of another cell's span), or a tuple. A cell tuple contains four + items: the number of extra rows used by the cell in a vertical span + (morerows); the number of extra columns used by the cell in a horizontal + span (morecols); the line offset of the first line of the cell contents; + and the cell contents, a list of lines of text. + """ + + head_body_separator_pat = re.compile(r'\+=[=+]+=\+ *$') + + def setup(self, block): + self.block = block[:] # make a copy; it may be modified + self.block.disconnect() # don't propagate changes to parent + self.bottom = len(block) - 1 + self.right = len(block[0]) - 1 + self.head_body_sep = None + self.done = [-1] * len(block[0]) + self.cells = [] + self.rowseps = {0: [0]} + self.colseps = {0: [0]} + + def parse_table(self): + """ + Start with a queue of upper-left corners, containing the upper-left + corner of the table itself. Trace out one rectangular cell, remember + it, and add its upper-right and lower-left corners to the queue of + potential upper-left corners of further cells. Process the queue in + top-to-bottom order, keeping track of how much of each text column has + been seen. + + We'll end up knowing all the row and column boundaries, cell positions + and their dimensions. + """ + corners = [(0, 0)] + while corners: + top, left = corners.pop(0) + if (top == self.bottom + or left == self.right + or top <= self.done[left]): + continue + result = self.scan_cell(top, left) + if not result: + continue + bottom, right, rowseps, colseps = result + update_dict_of_lists(self.rowseps, rowseps) + update_dict_of_lists(self.colseps, colseps) + self.mark_done(top, left, bottom, right) + cellblock = self.block.get_2D_block(top + 1, left + 1, + bottom, right) + cellblock.disconnect() # lines in cell can't sync with parent + cellblock.replace(self.double_width_pad_char, '') + self.cells.append((top, left, bottom, right, cellblock)) + corners.extend([(top, right), (bottom, left)]) + corners.sort() + if not self.check_parse_complete(): + raise TableMarkupError('Malformed table; parse incomplete.') + + def mark_done(self, top, left, bottom, right): + """For keeping track of how much of each text column has been seen.""" + before = top - 1 + after = bottom - 1 + for col in range(left, right): + assert self.done[col] == before + self.done[col] = after + + def check_parse_complete(self): + """Each text column should have been completely seen.""" + last = self.bottom - 1 + for col in range(self.right): + if self.done[col] != last: + return False + return True + + def scan_cell(self, top, left): + """Starting at the top-left corner, start tracing out a cell.""" + assert self.block[top][left] == '+' + return self.scan_right(top, left) + + def scan_right(self, top, left): + """ + Look for the top-right corner of the cell, and make note of all column + boundaries ('+'). + """ + colseps = {} + line = self.block[top] + for i in range(left + 1, self.right + 1): + if line[i] == '+': + colseps[i] = [top] + result = self.scan_down(top, left, i) + if result: + bottom, rowseps, newcolseps = result + update_dict_of_lists(colseps, newcolseps) + return bottom, i, rowseps, colseps + elif line[i] != '-': + return None + return None + + def scan_down(self, top, left, right): + """ + Look for the bottom-right corner of the cell, making note of all row + boundaries. + """ + rowseps = {} + for i in range(top + 1, self.bottom + 1): + if self.block[i][right] == '+': + rowseps[i] = [right] + result = self.scan_left(top, left, i, right) + if result: + newrowseps, colseps = result + update_dict_of_lists(rowseps, newrowseps) + return i, rowseps, colseps + elif self.block[i][right] != '|': + return None + return None + + def scan_left(self, top, left, bottom, right): + """ + Noting column boundaries, look for the bottom-left corner of the cell. + It must line up with the starting point. + """ + colseps = {} + line = self.block[bottom] + for i in range(right - 1, left, -1): + if line[i] == '+': + colseps[i] = [bottom] + elif line[i] != '-': + return None + if line[left] != '+': + return None + result = self.scan_up(top, left, bottom, right) + if result is not None: + rowseps = result + return rowseps, colseps + return None + + def scan_up(self, top, left, bottom, right): + """ + Noting row boundaries, see if we can return to the starting point. + """ + rowseps = {} + for i in range(bottom - 1, top, -1): + if self.block[i][left] == '+': + rowseps[i] = [left] + elif self.block[i][left] != '|': + return None + return rowseps + + def structure_from_cells(self): + """ + From the data collected by `scan_cell()`, convert to the final data + structure. + """ + rowseps = sorted(self.rowseps.keys()) # list of row boundaries + rowindex = {} + for i in range(len(rowseps)): + rowindex[rowseps[i]] = i # row boundary -> row number mapping + colseps = sorted(self.colseps.keys()) # list of column boundaries + colindex = {} + for i in range(len(colseps)): + colindex[colseps[i]] = i # column boundary -> col number map + colspecs = [(colseps[i] - colseps[i - 1] - 1) + for i in range(1, len(colseps))] # list of column widths + # prepare an empty table with the correct number of rows & columns + onerow = [None for i in range(len(colseps) - 1)] + rows = [onerow[:] for i in range(len(rowseps) - 1)] + # keep track of # of cells remaining; should reduce to zero + remaining = (len(rowseps) - 1) * (len(colseps) - 1) + for top, left, bottom, right, block in self.cells: + rownum = rowindex[top] + colnum = colindex[left] + assert rows[rownum][colnum] is None, ( + 'Cell (row %s, column %s) already used.' + % (rownum + 1, colnum + 1)) + morerows = rowindex[bottom] - rownum - 1 + morecols = colindex[right] - colnum - 1 + remaining -= (morerows + 1) * (morecols + 1) + # write the cell into the table + rows[rownum][colnum] = (morerows, morecols, top + 1, block) + assert remaining == 0, 'Unused cells remaining.' + if self.head_body_sep: # separate head rows from body rows + numheadrows = rowindex[self.head_body_sep] + headrows = rows[:numheadrows] + bodyrows = rows[numheadrows:] + else: + headrows = [] + bodyrows = rows + return colspecs, headrows, bodyrows + + +class SimpleTableParser(TableParser): + + """ + Parse a simple table using `parse()`. + + Here's an example of a simple table:: + + ===== ===== + col 1 col 2 + ===== ===== + 1 Second column of row 1. + 2 Second column of row 2. + Second line of paragraph. + 3 - Second column of row 3. + + - Second item in bullet + list (row 3, column 2). + 4 is a span + ------------ + 5 + ===== ===== + + Top and bottom borders use '=', column span underlines use '-', column + separation is indicated with spaces. + + Passing the above table to the `parse()` method will result in the + following data structure, whose interpretation is the same as for + `GridTableParser`:: + + ([5, 25], + [[(0, 0, 1, ['col 1']), + (0, 0, 1, ['col 2'])]], + [[(0, 0, 3, ['1']), + (0, 0, 3, ['Second column of row 1.'])], + [(0, 0, 4, ['2']), + (0, 0, 4, ['Second column of row 2.', + 'Second line of paragraph.'])], + [(0, 0, 6, ['3']), + (0, 0, 6, ['- Second column of row 3.', + '', + '- Second item in bullet', + ' list (row 3, column 2).'])], + [(0, 1, 10, ['4 is a span'])], + [(0, 0, 12, ['5']), + (0, 0, 12, [''])]]) + """ + + head_body_separator_pat = re.compile('=[ =]*$') + span_pat = re.compile('-[ -]*$') + + def setup(self, block): + self.block = block[:] # make a copy; it will be modified + self.block.disconnect() # don't propagate changes to parent + # Convert top & bottom borders to column span underlines: + self.block[0] = self.block[0].replace('=', '-') + self.block[-1] = self.block[-1].replace('=', '-') + self.head_body_sep = None + self.columns = [] + self.border_end = None + self.table = [] + self.done = [-1] * len(block[0]) + self.rowseps = {0: [0]} + self.colseps = {0: [0]} + + def parse_table(self): + """ + First determine the column boundaries from the top border, then + process rows. Each row may consist of multiple lines; accumulate + lines until a row is complete. Call `self.parse_row` to finish the + job. + """ + # Top border must fully describe all table columns. + self.columns = self.parse_columns(self.block[0], 0) + self.border_end = self.columns[-1][1] + firststart, firstend = self.columns[0] + offset = 1 # skip top border + start = 1 + text_found = None + while offset < len(self.block): + line = self.block[offset] + if self.span_pat.match(line): + # Column span underline or border; row is complete. + self.parse_row(self.block[start:offset], start, + (line.rstrip(), offset)) + start = offset + 1 + text_found = None + elif line[firststart:firstend].strip(): + # First column not blank, therefore it's a new row. + if text_found and offset != start: + self.parse_row(self.block[start:offset], start) + start = offset + text_found = 1 + elif not text_found: + start = offset + 1 + offset += 1 + + def parse_columns(self, line, offset): + """ + Given a column span underline, return a list of (begin, end) pairs. + """ + cols = [] + end = 0 + while True: + begin = line.find('-', end) + end = line.find(' ', begin) + if begin < 0: + break + if end < 0: + end = len(line) + cols.append((begin, end)) + if self.columns: + if cols[-1][1] != self.border_end: + raise TableMarkupError('Column span incomplete in table ' + 'line %s.' % (offset+1), + offset=offset) + # Allow for an unbounded rightmost column: + cols[-1] = (cols[-1][0], self.columns[-1][1]) + return cols + + def init_row(self, colspec, offset): + i = 0 + cells = [] + for start, end in colspec: + morecols = 0 + try: + assert start == self.columns[i][0] + while end != self.columns[i][1]: + i += 1 + morecols += 1 + except (AssertionError, IndexError): + raise TableMarkupError('Column span alignment problem ' + 'in table line %s.' % (offset+2), + offset=offset+1) + cells.append([0, morecols, offset, []]) + i += 1 + return cells + + def parse_row(self, lines, start, spanline=None): + """ + Given the text `lines` of a row, parse it and append to `self.table`. + + The row is parsed according to the current column spec (either + `spanline` if provided or `self.columns`). For each column, extract + text from each line, and check for text in column margins. Finally, + adjust for insignificant whitespace. + """ + if not (lines or spanline): + # No new row, just blank lines. + return + if spanline: + columns = self.parse_columns(*spanline) + else: + columns = self.columns[:] + self.check_columns(lines, start, columns) + row = self.init_row(columns, start) + for i in range(len(columns)): + start, end = columns[i] + cellblock = lines.get_2D_block(0, start, len(lines), end) + cellblock.disconnect() # lines in cell can't sync with parent + cellblock.replace(self.double_width_pad_char, '') + row[i][3] = cellblock + self.table.append(row) + + def check_columns(self, lines, first_line, columns): + """ + Check for text in column margins and text overflow in the last column. + Raise TableMarkupError if anything but whitespace is in column margins. + Adjust the end value for the last column if there is text overflow. + """ + # "Infinite" value for a dummy last column's beginning, used to + # check for text overflow: + columns.append((sys.maxsize, None)) + lastcol = len(columns) - 2 + # combining characters do not contribute to the column width + lines = [strip_combining_chars(line) for line in lines] + + for i in range(len(columns) - 1): + start, end = columns[i] + nextstart = columns[i+1][0] + offset = 0 + for line in lines: + if i == lastcol and line[end:].strip(): + text = line[start:].rstrip() + new_end = start + len(text) + main_start, main_end = self.columns[-1] + columns[i] = (start, max(main_end, new_end)) + if new_end > main_end: + self.columns[-1] = (main_start, new_end) + elif line[end:nextstart].strip(): + raise TableMarkupError('Text in column margin in table ' + 'line %s.' % (first_line+offset+1), + offset=first_line+offset) + offset += 1 + columns.pop() + + def structure_from_cells(self): + colspecs = [end - start for start, end in self.columns] + first_body_row = 0 + if self.head_body_sep: + for i in range(len(self.table)): + if self.table[i][0][2] > self.head_body_sep: + first_body_row = i + break + return (colspecs, self.table[:first_body_row], + self.table[first_body_row:]) + + +def update_dict_of_lists(master, newdata): + """ + Extend the list values of `master` with those from `newdata`. + + Both parameters must be dictionaries containing list values. + """ + for key, values in newdata.items(): + master.setdefault(key, []).extend(values) diff --git a/.venv/lib/python3.12/site-packages/docutils/readers/__init__.py b/.venv/lib/python3.12/site-packages/docutils/readers/__init__.py new file mode 100644 index 00000000..9ab47840 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/readers/__init__.py @@ -0,0 +1,113 @@ +# $Id: __init__.py 9258 2022-11-21 14:51:43Z milde $ +# Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer +# Copyright: This module has been placed in the public domain. + +""" +This package contains Docutils Reader modules. +""" + +__docformat__ = 'reStructuredText' + +from importlib import import_module + +from docutils import utils, parsers, Component +from docutils.transforms import universal + + +class Reader(Component): + + """ + Abstract base class for docutils Readers. + + Each reader module or package must export a subclass also called 'Reader'. + + The two steps of a Reader's responsibility are to read data from the + source Input object and parse the data with the Parser object. + Call `read()` to process a document. + """ + + component_type = 'reader' + config_section = 'readers' + + def get_transforms(self): + return super().get_transforms() + [universal.Decorations, + universal.ExposeInternals, + universal.StripComments] + + def __init__(self, parser=None, parser_name=None): + """ + Initialize the Reader instance. + + Several instance attributes are defined with dummy initial values. + Subclasses may use these attributes as they wish. + """ + + self.parser = parser + """A `parsers.Parser` instance shared by all doctrees. May be left + unspecified if the document source determines the parser.""" + + if parser is None and parser_name: + self.set_parser(parser_name) + + self.source = None + """`docutils.io` IO object, source of input data.""" + + self.input = None + """Raw text input; either a single string or, for more complex cases, + a collection of strings.""" + + def set_parser(self, parser_name): + """Set `self.parser` by name.""" + parser_class = parsers.get_parser_class(parser_name) + self.parser = parser_class() + + def read(self, source, parser, settings): + self.source = source + if not self.parser: + self.parser = parser + self.settings = settings + self.input = self.source.read() + self.parse() + return self.document + + def parse(self): + """Parse `self.input` into a document tree.""" + self.document = document = self.new_document() + self.parser.parse(self.input, document) + document.current_source = document.current_line = None + + def new_document(self): + """Create and return a new empty document tree (root node).""" + return utils.new_document(self.source.source_path, self.settings) + + +class ReReader(Reader): + + """ + A reader which rereads an existing document tree (e.g. a + deserializer). + + Often used in conjunction with `writers.UnfilteredWriter`. + """ + + def get_transforms(self): + # Do not add any transforms. They have already been applied + # by the reader which originally created the document. + return Component.get_transforms(self) + + +_reader_aliases = {} + + +def get_reader_class(reader_name): + """Return the Reader class from the `reader_name` module.""" + name = reader_name.lower() + name = _reader_aliases.get(name, name) + try: + module = import_module('docutils.readers.'+name) + except ImportError: + try: + module = import_module(name) + except ImportError as err: + raise ImportError(f'Reader "{reader_name}" not found. {err}') + return module.Reader diff --git a/.venv/lib/python3.12/site-packages/docutils/readers/doctree.py b/.venv/lib/python3.12/site-packages/docutils/readers/doctree.py new file mode 100644 index 00000000..f8d37269 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/readers/doctree.py @@ -0,0 +1,46 @@ +# $Id: doctree.py 4564 2006-05-21 20:44:42Z wiemann $ +# Author: Martin Blais <blais@furius.ca> +# Copyright: This module has been placed in the public domain. + +"""Reader for existing document trees.""" + +from docutils import readers, utils, transforms + + +class Reader(readers.ReReader): + + """ + Adapt the Reader API for an existing document tree. + + The existing document tree must be passed as the ``source`` parameter to + the `docutils.core.Publisher` initializer, wrapped in a + `docutils.io.DocTreeInput` object:: + + pub = docutils.core.Publisher( + ..., source=docutils.io.DocTreeInput(document), ...) + + The original document settings are overridden; if you want to use the + settings of the original document, pass ``settings=document.settings`` to + the Publisher call above. + """ + + supported = ('doctree',) + + config_section = 'doctree reader' + config_section_dependencies = ('readers',) + + def parse(self): + """ + No parsing to do; refurbish the document tree instead. + Overrides the inherited method. + """ + self.document = self.input + # Create fresh Transformer object, to be populated from Writer + # component. + self.document.transformer = transforms.Transformer(self.document) + # Replace existing settings object with new one. + self.document.settings = self.settings + # Create fresh Reporter object because it is dependent on + # (new) settings. + self.document.reporter = utils.new_reporter( + self.document.get('source', ''), self.document.settings) diff --git a/.venv/lib/python3.12/site-packages/docutils/readers/pep.py b/.venv/lib/python3.12/site-packages/docutils/readers/pep.py new file mode 100644 index 00000000..3540f83a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/readers/pep.py @@ -0,0 +1,48 @@ +# $Id: pep.py 9258 2022-11-21 14:51:43Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Python Enhancement Proposal (PEP) Reader. +""" + +__docformat__ = 'reStructuredText' + + +from docutils.readers import standalone +from docutils.transforms import peps, frontmatter +from docutils.parsers import rst + + +class Reader(standalone.Reader): + + supported = ('pep',) + """Contexts this reader supports.""" + + settings_spec = ( + 'PEP Reader Option Defaults', + 'The --pep-references and --rfc-references options (for the ' + 'reStructuredText parser) are on by default.', + ()) + + config_section = 'pep reader' + config_section_dependencies = ('readers', 'standalone reader') + + def get_transforms(self): + transforms = super().get_transforms() + # We have PEP-specific frontmatter handling. + transforms.remove(frontmatter.DocTitle) + transforms.remove(frontmatter.SectionSubTitle) + transforms.remove(frontmatter.DocInfo) + transforms.extend([peps.Headers, peps.Contents, peps.TargetNotes]) + return transforms + + settings_default_overrides = {'pep_references': 1, 'rfc_references': 1} + + inliner_class = rst.states.Inliner + + def __init__(self, parser=None, parser_name=None): + """`parser` should be ``None``.""" + if parser is None: + parser = rst.Parser(rfc2822=True, inliner=self.inliner_class()) + standalone.Reader.__init__(self, parser, '') diff --git a/.venv/lib/python3.12/site-packages/docutils/readers/standalone.py b/.venv/lib/python3.12/site-packages/docutils/readers/standalone.py new file mode 100644 index 00000000..a6dfd6d7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/readers/standalone.py @@ -0,0 +1,65 @@ +# $Id: standalone.py 9539 2024-02-17 10:36:51Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Standalone file Reader for the reStructuredText markup syntax. +""" + +__docformat__ = 'reStructuredText' + + +from docutils import frontend, readers +from docutils.transforms import frontmatter, references, misc + + +class Reader(readers.Reader): + + supported = ('standalone',) + """Contexts this reader supports.""" + + document = None + """A single document tree.""" + + settings_spec = ( + 'Standalone Reader Options', + None, + (('Disable the promotion of a lone top-level section title to ' + 'document title (and subsequent section title to document ' + 'subtitle promotion; enabled by default).', + ['--no-doc-title'], + {'dest': 'doctitle_xform', 'action': 'store_false', + 'default': True, 'validator': frontend.validate_boolean}), + ('Disable the bibliographic field list transform (enabled by ' + 'default).', + ['--no-doc-info'], + {'dest': 'docinfo_xform', 'action': 'store_false', + 'default': True, 'validator': frontend.validate_boolean}), + ('Activate the promotion of lone subsection titles to ' + 'section subtitles (disabled by default).', + ['--section-subtitles'], + {'dest': 'sectsubtitle_xform', 'action': 'store_true', + 'default': False, 'validator': frontend.validate_boolean}), + ('Deactivate the promotion of lone subsection titles.', + ['--no-section-subtitles'], + {'dest': 'sectsubtitle_xform', 'action': 'store_false'}), + )) + + config_section = 'standalone reader' + config_section_dependencies = ('readers',) + + def get_transforms(self): + return super().get_transforms() + [ + references.Substitutions, + references.PropagateTargets, + frontmatter.DocTitle, + frontmatter.SectionSubTitle, + frontmatter.DocInfo, + references.AnonymousHyperlinks, + references.IndirectHyperlinks, + references.Footnotes, + references.ExternalTargets, + references.InternalTargets, + references.DanglingReferences, + misc.Transitions, + ] diff --git a/.venv/lib/python3.12/site-packages/docutils/statemachine.py b/.venv/lib/python3.12/site-packages/docutils/statemachine.py new file mode 100644 index 00000000..abdecfa7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/statemachine.py @@ -0,0 +1,1525 @@ +# $Id: statemachine.py 9072 2022-06-15 11:31:09Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +A finite state machine specialized for regular-expression-based text filters, +this module defines the following classes: + +- `StateMachine`, a state machine +- `State`, a state superclass +- `StateMachineWS`, a whitespace-sensitive version of `StateMachine` +- `StateWS`, a state superclass for use with `StateMachineWS` +- `SearchStateMachine`, uses `re.search()` instead of `re.match()` +- `SearchStateMachineWS`, uses `re.search()` instead of `re.match()` +- `ViewList`, extends standard Python lists. +- `StringList`, string-specific ViewList. + +Exception classes: + +- `StateMachineError` +- `UnknownStateError` +- `DuplicateStateError` +- `UnknownTransitionError` +- `DuplicateTransitionError` +- `TransitionPatternNotFound` +- `TransitionMethodNotFound` +- `UnexpectedIndentationError` +- `TransitionCorrection`: Raised to switch to another transition. +- `StateCorrection`: Raised to switch to another state & transition. + +Functions: + +- `string2lines()`: split a multi-line string into a list of one-line strings + + +How To Use This Module +====================== +(See the individual classes, methods, and attributes for details.) + +1. Import it: ``import statemachine`` or ``from statemachine import ...``. + You will also need to ``import re``. + +2. Derive a subclass of `State` (or `StateWS`) for each state in your state + machine:: + + class MyState(statemachine.State): + + Within the state's class definition: + + a) Include a pattern for each transition, in `State.patterns`:: + + patterns = {'atransition': r'pattern', ...} + + b) Include a list of initial transitions to be set up automatically, in + `State.initial_transitions`:: + + initial_transitions = ['atransition', ...] + + c) Define a method for each transition, with the same name as the + transition pattern:: + + def atransition(self, match, context, next_state): + # do something + result = [...] # a list + return context, next_state, result + # context, next_state may be altered + + Transition methods may raise an `EOFError` to cut processing short. + + d) You may wish to override the `State.bof()` and/or `State.eof()` implicit + transition methods, which handle the beginning- and end-of-file. + + e) In order to handle nested processing, you may wish to override the + attributes `State.nested_sm` and/or `State.nested_sm_kwargs`. + + If you are using `StateWS` as a base class, in order to handle nested + indented blocks, you may wish to: + + - override the attributes `StateWS.indent_sm`, + `StateWS.indent_sm_kwargs`, `StateWS.known_indent_sm`, and/or + `StateWS.known_indent_sm_kwargs`; + - override the `StateWS.blank()` method; and/or + - override or extend the `StateWS.indent()`, `StateWS.known_indent()`, + and/or `StateWS.firstknown_indent()` methods. + +3. Create a state machine object:: + + sm = StateMachine(state_classes=[MyState, ...], + initial_state='MyState') + +4. Obtain the input text, which needs to be converted into a tab-free list of + one-line strings. For example, to read text from a file called + 'inputfile':: + + with open('inputfile', encoding='utf-8') as fp: + input_string = fp.read() + input_lines = statemachine.string2lines(input_string) + +5. Run the state machine on the input text and collect the results, a list:: + + results = sm.run(input_lines) + +6. Remove any lingering circular references:: + + sm.unlink() +""" + +__docformat__ = 'restructuredtext' + +import sys +import re +from unicodedata import east_asian_width + +from docutils import utils + + +class StateMachine: + + """ + A finite state machine for text filters using regular expressions. + + The input is provided in the form of a list of one-line strings (no + newlines). States are subclasses of the `State` class. Transitions consist + of regular expression patterns and transition methods, and are defined in + each state. + + The state machine is started with the `run()` method, which returns the + results of processing in a list. + """ + + def __init__(self, state_classes, initial_state, debug=False): + """ + Initialize a `StateMachine` object; add state objects. + + Parameters: + + - `state_classes`: a list of `State` (sub)classes. + - `initial_state`: a string, the class name of the initial state. + - `debug`: a boolean; produce verbose output if true (nonzero). + """ + + self.input_lines = None + """`StringList` of input lines (without newlines). + Filled by `self.run()`.""" + + self.input_offset = 0 + """Offset of `self.input_lines` from the beginning of the file.""" + + self.line = None + """Current input line.""" + + self.line_offset = -1 + """Current input line offset from beginning of `self.input_lines`.""" + + self.debug = debug + """Debugging mode on/off.""" + + self.initial_state = initial_state + """The name of the initial state (key to `self.states`).""" + + self.current_state = initial_state + """The name of the current state (key to `self.states`).""" + + self.states = {} + """Mapping of {state_name: State_object}.""" + + self.add_states(state_classes) + + self.observers = [] + """List of bound methods or functions to call whenever the current + line changes. Observers are called with one argument, ``self``. + Cleared at the end of `run()`.""" + + def unlink(self): + """Remove circular references to objects no longer required.""" + for state in self.states.values(): + state.unlink() + self.states = None + + def run(self, input_lines, input_offset=0, context=None, + input_source=None, initial_state=None): + """ + Run the state machine on `input_lines`. Return results (a list). + + Reset `self.line_offset` and `self.current_state`. Run the + beginning-of-file transition. Input one line at a time and check for a + matching transition. If a match is found, call the transition method + and possibly change the state. Store the context returned by the + transition method to be passed on to the next transition matched. + Accumulate the results returned by the transition methods in a list. + Run the end-of-file transition. Finally, return the accumulated + results. + + Parameters: + + - `input_lines`: a list of strings without newlines, or `StringList`. + - `input_offset`: the line offset of `input_lines` from the beginning + of the file. + - `context`: application-specific storage. + - `input_source`: name or path of source of `input_lines`. + - `initial_state`: name of initial state. + """ + self.runtime_init() + if isinstance(input_lines, StringList): + self.input_lines = input_lines + else: + self.input_lines = StringList(input_lines, source=input_source) + self.input_offset = input_offset + self.line_offset = -1 + self.current_state = initial_state or self.initial_state + if self.debug: + print('\nStateMachine.run: input_lines (line_offset=%s):\n| %s' + % (self.line_offset, '\n| '.join(self.input_lines)), + file=sys.stderr) + transitions = None + results = [] + state = self.get_state() + try: + if self.debug: + print('\nStateMachine.run: bof transition', file=sys.stderr) + context, result = state.bof(context) + results.extend(result) + while True: + try: + try: + self.next_line() + if self.debug: + source, offset = self.input_lines.info( + self.line_offset) + print(f'\nStateMachine.run: line ' + f'(source={source!r}, offset={offset!r}):\n' + f'| {self.line}', file=sys.stderr) + context, next_state, result = self.check_line( + context, state, transitions) + except EOFError: + if self.debug: + print('\nStateMachine.run: %s.eof transition' + % state.__class__.__name__, file=sys.stderr) + result = state.eof(context) + results.extend(result) + break + else: + results.extend(result) + except TransitionCorrection as exception: + self.previous_line() # back up for another try + transitions = (exception.args[0],) + if self.debug: + print('\nStateMachine.run: TransitionCorrection to ' + f'state "{state.__class__.__name__}", ' + f'transition {transitions[0]}.', + file=sys.stderr) + continue + except StateCorrection as exception: + self.previous_line() # back up for another try + next_state = exception.args[0] + if len(exception.args) == 1: + transitions = None + else: + transitions = (exception.args[1],) + if self.debug: + print('\nStateMachine.run: StateCorrection to state ' + f'"{next_state}", transition {transitions[0]}.', + file=sys.stderr) + else: + transitions = None + state = self.get_state(next_state) + except: # noqa catchall + if self.debug: + self.error() + raise + self.observers = [] + return results + + def get_state(self, next_state=None): + """ + Return current state object; set it first if `next_state` given. + + Parameter `next_state`: a string, the name of the next state. + + Exception: `UnknownStateError` raised if `next_state` unknown. + """ + if next_state: + if self.debug and next_state != self.current_state: + print('\nStateMachine.get_state: Changing state from ' + '"%s" to "%s" (input line %s).' + % (self.current_state, next_state, + self.abs_line_number()), file=sys.stderr) + self.current_state = next_state + try: + return self.states[self.current_state] + except KeyError: + raise UnknownStateError(self.current_state) + + def next_line(self, n=1): + """Load `self.line` with the `n`'th next line and return it.""" + try: + try: + self.line_offset += n + self.line = self.input_lines[self.line_offset] + except IndexError: + self.line = None + raise EOFError + return self.line + finally: + self.notify_observers() + + def is_next_line_blank(self): + """Return True if the next line is blank or non-existent.""" + try: + return not self.input_lines[self.line_offset + 1].strip() + except IndexError: + return 1 + + def at_eof(self): + """Return 1 if the input is at or past end-of-file.""" + return self.line_offset >= len(self.input_lines) - 1 + + def at_bof(self): + """Return 1 if the input is at or before beginning-of-file.""" + return self.line_offset <= 0 + + def previous_line(self, n=1): + """Load `self.line` with the `n`'th previous line and return it.""" + self.line_offset -= n + if self.line_offset < 0: + self.line = None + else: + self.line = self.input_lines[self.line_offset] + self.notify_observers() + return self.line + + def goto_line(self, line_offset): + """Jump to absolute line offset `line_offset`, load and return it.""" + try: + try: + self.line_offset = line_offset - self.input_offset + self.line = self.input_lines[self.line_offset] + except IndexError: + self.line = None + raise EOFError + return self.line + finally: + self.notify_observers() + + def get_source(self, line_offset): + """Return source of line at absolute line offset `line_offset`.""" + return self.input_lines.source(line_offset - self.input_offset) + + def abs_line_offset(self): + """Return line offset of current line, from beginning of file.""" + return self.line_offset + self.input_offset + + def abs_line_number(self): + """Return line number of current line (counting from 1).""" + return self.line_offset + self.input_offset + 1 + + def get_source_and_line(self, lineno=None): + """Return (source, line) tuple for current or given line number. + + Looks up the source and line number in the `self.input_lines` + StringList instance to count for included source files. + + If the optional argument `lineno` is given, convert it from an + absolute line number to the corresponding (source, line) pair. + """ + if lineno is None: + offset = self.line_offset + else: + offset = lineno - self.input_offset - 1 + try: + src, srcoffset = self.input_lines.info(offset) + srcline = srcoffset + 1 + except TypeError: + # line is None if index is "Just past the end" + src, srcline = self.get_source_and_line(offset + self.input_offset) + return src, srcline + 1 + except IndexError: # `offset` is off the list + src, srcline = None, None + # raise AssertionError('cannot find line %d in %s lines' % + # (offset, len(self.input_lines))) + # # list(self.input_lines.lines()))) + return src, srcline + + def insert_input(self, input_lines, source): + self.input_lines.insert(self.line_offset + 1, '', + source='internal padding after '+source, + offset=len(input_lines)) + self.input_lines.insert(self.line_offset + 1, '', + source='internal padding before '+source, + offset=-1) + self.input_lines.insert(self.line_offset + 2, + StringList(input_lines, source)) + + def get_text_block(self, flush_left=False): + """ + Return a contiguous block of text. + + If `flush_left` is true, raise `UnexpectedIndentationError` if an + indented line is encountered before the text block ends (with a blank + line). + """ + try: + block = self.input_lines.get_text_block(self.line_offset, + flush_left) + self.next_line(len(block) - 1) + return block + except UnexpectedIndentationError as err: + block = err.args[0] + self.next_line(len(block) - 1) # advance to last line of block + raise + + def check_line(self, context, state, transitions=None): + """ + Examine one line of input for a transition match & execute its method. + + Parameters: + + - `context`: application-dependent storage. + - `state`: a `State` object, the current state. + - `transitions`: an optional ordered list of transition names to try, + instead of ``state.transition_order``. + + Return the values returned by the transition method: + + - context: possibly modified from the parameter `context`; + - next state name (`State` subclass name); + - the result output of the transition, a list. + + When there is no match, ``state.no_match()`` is called and its return + value is returned. + """ + if transitions is None: + transitions = state.transition_order + if self.debug: + print('\nStateMachine.check_line: state="%s", transitions=%r.' + % (state.__class__.__name__, transitions), file=sys.stderr) + for name in transitions: + pattern, method, next_state = state.transitions[name] + match = pattern.match(self.line) + if match: + if self.debug: + print('\nStateMachine.check_line: Matched transition ' + f'"{name}" in state "{state.__class__.__name__}".', + file=sys.stderr) + return method(match, context, next_state) + else: + if self.debug: + print('\nStateMachine.check_line: No match in state "%s".' + % state.__class__.__name__, file=sys.stderr) + return state.no_match(context, transitions) + + def add_state(self, state_class): + """ + Initialize & add a `state_class` (`State` subclass) object. + + Exception: `DuplicateStateError` raised if `state_class` was already + added. + """ + statename = state_class.__name__ + if statename in self.states: + raise DuplicateStateError(statename) + self.states[statename] = state_class(self, self.debug) + + def add_states(self, state_classes): + """ + Add `state_classes` (a list of `State` subclasses). + """ + for state_class in state_classes: + self.add_state(state_class) + + def runtime_init(self): + """ + Initialize `self.states`. + """ + for state in self.states.values(): + state.runtime_init() + + def error(self): + """Report error details.""" + type, value, module, line, function = _exception_data() + print('%s: %s' % (type, value), file=sys.stderr) + print('input line %s' % (self.abs_line_number()), file=sys.stderr) + print('module %s, line %s, function %s' % (module, line, function), + file=sys.stderr) + + def attach_observer(self, observer): + """ + The `observer` parameter is a function or bound method which takes two + arguments, the source and offset of the current line. + """ + self.observers.append(observer) + + def detach_observer(self, observer): + self.observers.remove(observer) + + def notify_observers(self): + for observer in self.observers: + try: + info = self.input_lines.info(self.line_offset) + except IndexError: + info = (None, None) + observer(*info) + + +class State: + + """ + State superclass. Contains a list of transitions, and transition methods. + + Transition methods all have the same signature. They take 3 parameters: + + - An `re` match object. ``match.string`` contains the matched input line, + ``match.start()`` gives the start index of the match, and + ``match.end()`` gives the end index. + - A context object, whose meaning is application-defined (initial value + ``None``). It can be used to store any information required by the state + machine, and the returned context is passed on to the next transition + method unchanged. + - The name of the next state, a string, taken from the transitions list; + normally it is returned unchanged, but it may be altered by the + transition method if necessary. + + Transition methods all return a 3-tuple: + + - A context object, as (potentially) modified by the transition method. + - The next state name (a return value of ``None`` means no state change). + - The processing result, a list, which is accumulated by the state + machine. + + Transition methods may raise an `EOFError` to cut processing short. + + There are two implicit transitions, and corresponding transition methods + are defined: `bof()` handles the beginning-of-file, and `eof()` handles + the end-of-file. These methods have non-standard signatures and return + values. `bof()` returns the initial context and results, and may be used + to return a header string, or do any other processing needed. `eof()` + should handle any remaining context and wrap things up; it returns the + final processing result. + + Typical applications need only subclass `State` (or a subclass), set the + `patterns` and `initial_transitions` class attributes, and provide + corresponding transition methods. The default object initialization will + take care of constructing the list of transitions. + """ + + patterns = None + """ + {Name: pattern} mapping, used by `make_transition()`. Each pattern may + be a string or a compiled `re` pattern. Override in subclasses. + """ + + initial_transitions = None + """ + A list of transitions to initialize when a `State` is instantiated. + Each entry is either a transition name string, or a (transition name, next + state name) pair. See `make_transitions()`. Override in subclasses. + """ + + nested_sm = None + """ + The `StateMachine` class for handling nested processing. + + If left as ``None``, `nested_sm` defaults to the class of the state's + controlling state machine. Override it in subclasses to avoid the default. + """ + + nested_sm_kwargs = None + """ + Keyword arguments dictionary, passed to the `nested_sm` constructor. + + Two keys must have entries in the dictionary: + + - Key 'state_classes' must be set to a list of `State` classes. + - Key 'initial_state' must be set to the name of the initial state class. + + If `nested_sm_kwargs` is left as ``None``, 'state_classes' defaults to the + class of the current state, and 'initial_state' defaults to the name of + the class of the current state. Override in subclasses to avoid the + defaults. + """ + + def __init__(self, state_machine, debug=False): + """ + Initialize a `State` object; make & add initial transitions. + + Parameters: + + - `statemachine`: the controlling `StateMachine` object. + - `debug`: a boolean; produce verbose output if true. + """ + + self.transition_order = [] + """A list of transition names in search order.""" + + self.transitions = {} + """ + A mapping of transition names to 3-tuples containing + (compiled_pattern, transition_method, next_state_name). Initialized as + an instance attribute dynamically (instead of as a class attribute) + because it may make forward references to patterns and methods in this + or other classes. + """ + + self.add_initial_transitions() + + self.state_machine = state_machine + """A reference to the controlling `StateMachine` object.""" + + self.debug = debug + """Debugging mode on/off.""" + + if self.nested_sm is None: + self.nested_sm = self.state_machine.__class__ + if self.nested_sm_kwargs is None: + self.nested_sm_kwargs = {'state_classes': [self.__class__], + 'initial_state': self.__class__.__name__} + + def runtime_init(self): + """ + Initialize this `State` before running the state machine; called from + `self.state_machine.run()`. + """ + pass + + def unlink(self): + """Remove circular references to objects no longer required.""" + self.state_machine = None + + def add_initial_transitions(self): + """Make and add transitions listed in `self.initial_transitions`.""" + if self.initial_transitions: + names, transitions = self.make_transitions( + self.initial_transitions) + self.add_transitions(names, transitions) + + def add_transitions(self, names, transitions): + """ + Add a list of transitions to the start of the transition list. + + Parameters: + + - `names`: a list of transition names. + - `transitions`: a mapping of names to transition tuples. + + Exceptions: `DuplicateTransitionError`, `UnknownTransitionError`. + """ + for name in names: + if name in self.transitions: + raise DuplicateTransitionError(name) + if name not in transitions: + raise UnknownTransitionError(name) + self.transition_order[:0] = names + self.transitions.update(transitions) + + def add_transition(self, name, transition): + """ + Add a transition to the start of the transition list. + + Parameter `transition`: a ready-made transition 3-tuple. + + Exception: `DuplicateTransitionError`. + """ + if name in self.transitions: + raise DuplicateTransitionError(name) + self.transition_order[:0] = [name] + self.transitions[name] = transition + + def remove_transition(self, name): + """ + Remove a transition by `name`. + + Exception: `UnknownTransitionError`. + """ + try: + del self.transitions[name] + self.transition_order.remove(name) + except: # noqa catchall + raise UnknownTransitionError(name) + + def make_transition(self, name, next_state=None): + """ + Make & return a transition tuple based on `name`. + + This is a convenience function to simplify transition creation. + + Parameters: + + - `name`: a string, the name of the transition pattern & method. This + `State` object must have a method called '`name`', and a dictionary + `self.patterns` containing a key '`name`'. + - `next_state`: a string, the name of the next `State` object for this + transition. A value of ``None`` (or absent) implies no state change + (i.e., continue with the same state). + + Exceptions: `TransitionPatternNotFound`, `TransitionMethodNotFound`. + """ + if next_state is None: + next_state = self.__class__.__name__ + try: + pattern = self.patterns[name] + if not hasattr(pattern, 'match'): + pattern = self.patterns[name] = re.compile(pattern) + except KeyError: + raise TransitionPatternNotFound( + '%s.patterns[%r]' % (self.__class__.__name__, name)) + try: + method = getattr(self, name) + except AttributeError: + raise TransitionMethodNotFound( + '%s.%s' % (self.__class__.__name__, name)) + return pattern, method, next_state + + def make_transitions(self, name_list): + """ + Return a list of transition names and a transition mapping. + + Parameter `name_list`: a list, where each entry is either a transition + name string, or a 1- or 2-tuple (transition name, optional next state + name). + """ + names = [] + transitions = {} + for namestate in name_list: + if isinstance(namestate, str): + transitions[namestate] = self.make_transition(namestate) + names.append(namestate) + else: + transitions[namestate[0]] = self.make_transition(*namestate) + names.append(namestate[0]) + return names, transitions + + def no_match(self, context, transitions): + """ + Called when there is no match from `StateMachine.check_line()`. + + Return the same values returned by transition methods: + + - context: unchanged; + - next state name: ``None``; + - empty result list. + + Override in subclasses to catch this event. + """ + return context, None, [] + + def bof(self, context): + """ + Handle beginning-of-file. Return unchanged `context`, empty result. + + Override in subclasses. + + Parameter `context`: application-defined storage. + """ + return context, [] + + def eof(self, context): + """ + Handle end-of-file. Return empty result. + + Override in subclasses. + + Parameter `context`: application-defined storage. + """ + return [] + + def nop(self, match, context, next_state): + """ + A "do nothing" transition method. + + Return unchanged `context` & `next_state`, empty result. Useful for + simple state changes (actionless transitions). + """ + return context, next_state, [] + + +class StateMachineWS(StateMachine): + + """ + `StateMachine` subclass specialized for whitespace recognition. + + There are three methods provided for extracting indented text blocks: + + - `get_indented()`: use when the indent is unknown. + - `get_known_indented()`: use when the indent is known for all lines. + - `get_first_known_indented()`: use when only the first line's indent is + known. + """ + + def get_indented(self, until_blank=False, strip_indent=True): + """ + Return a block of indented lines of text, and info. + + Extract an indented block where the indent is unknown for all lines. + + :Parameters: + - `until_blank`: Stop collecting at the first blank line if true. + - `strip_indent`: Strip common leading indent if true (default). + + :Return: + - the indented block (a list of lines of text), + - its indent, + - its first line offset from BOF, and + - whether or not it finished with a blank line. + """ + offset = self.abs_line_offset() + indented, indent, blank_finish = self.input_lines.get_indented( + self.line_offset, until_blank, strip_indent) + if indented: + self.next_line(len(indented) - 1) # advance to last indented line + while indented and not indented[0].strip(): + indented.trim_start() + offset += 1 + return indented, indent, offset, blank_finish + + def get_known_indented(self, indent, until_blank=False, strip_indent=True): + """ + Return an indented block and info. + + Extract an indented block where the indent is known for all lines. + Starting with the current line, extract the entire text block with at + least `indent` indentation (which must be whitespace, except for the + first line). + + :Parameters: + - `indent`: The number of indent columns/characters. + - `until_blank`: Stop collecting at the first blank line if true. + - `strip_indent`: Strip `indent` characters of indentation if true + (default). + + :Return: + - the indented block, + - its first line offset from BOF, and + - whether or not it finished with a blank line. + """ + offset = self.abs_line_offset() + indented, indent, blank_finish = self.input_lines.get_indented( + self.line_offset, until_blank, strip_indent, + block_indent=indent) + self.next_line(len(indented) - 1) # advance to last indented line + while indented and not indented[0].strip(): + indented.trim_start() + offset += 1 + return indented, offset, blank_finish + + def get_first_known_indented(self, indent, until_blank=False, + strip_indent=True, strip_top=True): + """ + Return an indented block and info. + + Extract an indented block where the indent is known for the first line + and unknown for all other lines. + + :Parameters: + - `indent`: The first line's indent (# of columns/characters). + - `until_blank`: Stop collecting at the first blank line if true + (1). + - `strip_indent`: Strip `indent` characters of indentation if true + (1, default). + - `strip_top`: Strip blank lines from the beginning of the block. + + :Return: + - the indented block, + - its indent, + - its first line offset from BOF, and + - whether or not it finished with a blank line. + """ + offset = self.abs_line_offset() + indented, indent, blank_finish = self.input_lines.get_indented( + self.line_offset, until_blank, strip_indent, + first_indent=indent) + self.next_line(len(indented) - 1) # advance to last indented line + if strip_top: + while indented and not indented[0].strip(): + indented.trim_start() + offset += 1 + return indented, indent, offset, blank_finish + + +class StateWS(State): + + """ + State superclass specialized for whitespace (blank lines & indents). + + Use this class with `StateMachineWS`. The transitions 'blank' (for blank + lines) and 'indent' (for indented text blocks) are added automatically, + before any other transitions. The transition method `blank()` handles + blank lines and `indent()` handles nested indented blocks. Indented + blocks trigger a new state machine to be created by `indent()` and run. + The class of the state machine to be created is in `indent_sm`, and the + constructor keyword arguments are in the dictionary `indent_sm_kwargs`. + + The methods `known_indent()` and `firstknown_indent()` are provided for + indented blocks where the indent (all lines' and first line's only, + respectively) is known to the transition method, along with the attributes + `known_indent_sm` and `known_indent_sm_kwargs`. Neither transition method + is triggered automatically. + """ + + indent_sm = None + """ + The `StateMachine` class handling indented text blocks. + + If left as ``None``, `indent_sm` defaults to the value of + `State.nested_sm`. Override it in subclasses to avoid the default. + """ + + indent_sm_kwargs = None + """ + Keyword arguments dictionary, passed to the `indent_sm` constructor. + + If left as ``None``, `indent_sm_kwargs` defaults to the value of + `State.nested_sm_kwargs`. Override it in subclasses to avoid the default. + """ + + known_indent_sm = None + """ + The `StateMachine` class handling known-indented text blocks. + + If left as ``None``, `known_indent_sm` defaults to the value of + `indent_sm`. Override it in subclasses to avoid the default. + """ + + known_indent_sm_kwargs = None + """ + Keyword arguments dictionary, passed to the `known_indent_sm` constructor. + + If left as ``None``, `known_indent_sm_kwargs` defaults to the value of + `indent_sm_kwargs`. Override it in subclasses to avoid the default. + """ + + ws_patterns = {'blank': re.compile(' *$'), + 'indent': re.compile(' +')} + """Patterns for default whitespace transitions. May be overridden in + subclasses.""" + + ws_initial_transitions = ('blank', 'indent') + """Default initial whitespace transitions, added before those listed in + `State.initial_transitions`. May be overridden in subclasses.""" + + def __init__(self, state_machine, debug=False): + """ + Initialize a `StateSM` object; extends `State.__init__()`. + + Check for indent state machine attributes, set defaults if not set. + """ + State.__init__(self, state_machine, debug) + if self.indent_sm is None: + self.indent_sm = self.nested_sm + if self.indent_sm_kwargs is None: + self.indent_sm_kwargs = self.nested_sm_kwargs + if self.known_indent_sm is None: + self.known_indent_sm = self.indent_sm + if self.known_indent_sm_kwargs is None: + self.known_indent_sm_kwargs = self.indent_sm_kwargs + + def add_initial_transitions(self): + """ + Add whitespace-specific transitions before those defined in subclass. + + Extends `State.add_initial_transitions()`. + """ + State.add_initial_transitions(self) + if self.patterns is None: + self.patterns = {} + self.patterns.update(self.ws_patterns) + names, transitions = self.make_transitions( + self.ws_initial_transitions) + self.add_transitions(names, transitions) + + def blank(self, match, context, next_state): + """Handle blank lines. Does nothing. Override in subclasses.""" + return self.nop(match, context, next_state) + + def indent(self, match, context, next_state): + """ + Handle an indented text block. Extend or override in subclasses. + + Recursively run the registered state machine for indented blocks + (`self.indent_sm`). + """ + (indented, indent, line_offset, blank_finish + ) = self.state_machine.get_indented() + sm = self.indent_sm(debug=self.debug, **self.indent_sm_kwargs) + results = sm.run(indented, input_offset=line_offset) + return context, next_state, results + + def known_indent(self, match, context, next_state): + """ + Handle a known-indent text block. Extend or override in subclasses. + + Recursively run the registered state machine for known-indent indented + blocks (`self.known_indent_sm`). The indent is the length of the + match, ``match.end()``. + """ + (indented, line_offset, blank_finish + ) = self.state_machine.get_known_indented(match.end()) + sm = self.known_indent_sm(debug=self.debug, + **self.known_indent_sm_kwargs) + results = sm.run(indented, input_offset=line_offset) + return context, next_state, results + + def first_known_indent(self, match, context, next_state): + """ + Handle an indented text block (first line's indent known). + + Extend or override in subclasses. + + Recursively run the registered state machine for known-indent indented + blocks (`self.known_indent_sm`). The indent is the length of the + match, ``match.end()``. + """ + (indented, line_offset, blank_finish + ) = self.state_machine.get_first_known_indented(match.end()) + sm = self.known_indent_sm(debug=self.debug, + **self.known_indent_sm_kwargs) + results = sm.run(indented, input_offset=line_offset) + return context, next_state, results + + +class _SearchOverride: + + """ + Mix-in class to override `StateMachine` regular expression behavior. + + Changes regular expression matching, from the default `re.match()` + (succeeds only if the pattern matches at the start of `self.line`) to + `re.search()` (succeeds if the pattern matches anywhere in `self.line`). + When subclassing a `StateMachine`, list this class **first** in the + inheritance list of the class definition. + """ + + def match(self, pattern): + """ + Return the result of a regular expression search. + + Overrides `StateMachine.match()`. + + Parameter `pattern`: `re` compiled regular expression. + """ + return pattern.search(self.line) + + +class SearchStateMachine(_SearchOverride, StateMachine): + """`StateMachine` which uses `re.search()` instead of `re.match()`.""" + pass + + +class SearchStateMachineWS(_SearchOverride, StateMachineWS): + """`StateMachineWS` which uses `re.search()` instead of `re.match()`.""" + pass + + +class ViewList: + + """ + List with extended functionality: slices of ViewList objects are child + lists, linked to their parents. Changes made to a child list also affect + the parent list. A child list is effectively a "view" (in the SQL sense) + of the parent list. Changes to parent lists, however, do *not* affect + active child lists. If a parent list is changed, any active child lists + should be recreated. + + The start and end of the slice can be trimmed using the `trim_start()` and + `trim_end()` methods, without affecting the parent list. The link between + child and parent lists can be broken by calling `disconnect()` on the + child list. + + Also, ViewList objects keep track of the source & offset of each item. + This information is accessible via the `source()`, `offset()`, and + `info()` methods. + """ + + def __init__(self, initlist=None, source=None, items=None, + parent=None, parent_offset=None): + self.data = [] + """The actual list of data, flattened from various sources.""" + + self.items = [] + """A list of (source, offset) pairs, same length as `self.data`: the + source of each line and the offset of each line from the beginning of + its source.""" + + self.parent = parent + """The parent list.""" + + self.parent_offset = parent_offset + """Offset of this list from the beginning of the parent list.""" + + if isinstance(initlist, ViewList): + self.data = initlist.data[:] + self.items = initlist.items[:] + elif initlist is not None: + self.data = list(initlist) + if items: + self.items = items + else: + self.items = [(source, i) for i in range(len(initlist))] + assert len(self.data) == len(self.items), 'data mismatch' + + def __str__(self): + return str(self.data) + + def __repr__(self): + return f'{self.__class__.__name__}({self.data}, items={self.items})' + + def __lt__(self, other): return self.data < self.__cast(other) # noqa + def __le__(self, other): return self.data <= self.__cast(other) # noqa + def __eq__(self, other): return self.data == self.__cast(other) # noqa + def __ne__(self, other): return self.data != self.__cast(other) # noqa + def __gt__(self, other): return self.data > self.__cast(other) # noqa + def __ge__(self, other): return self.data >= self.__cast(other) # noqa + + def __cast(self, other): + if isinstance(other, ViewList): + return other.data + else: + return other + + def __contains__(self, item): + return item in self.data + + def __len__(self): + return len(self.data) + + # The __getitem__()/__setitem__() methods check whether the index + # is a slice first, since indexing a native list with a slice object + # just works. + + def __getitem__(self, i): + if isinstance(i, slice): + assert i.step in (None, 1), 'cannot handle slice with stride' + return self.__class__(self.data[i.start:i.stop], + items=self.items[i.start:i.stop], + parent=self, parent_offset=i.start or 0) + else: + return self.data[i] + + def __setitem__(self, i, item): + if isinstance(i, slice): + assert i.step in (None, 1), 'cannot handle slice with stride' + if not isinstance(item, ViewList): + raise TypeError('assigning non-ViewList to ViewList slice') + self.data[i.start:i.stop] = item.data + self.items[i.start:i.stop] = item.items + assert len(self.data) == len(self.items), 'data mismatch' + if self.parent: + k = (i.start or 0) + self.parent_offset + n = (i.stop or len(self)) + self.parent_offset + self.parent[k:n] = item + else: + self.data[i] = item + if self.parent: + self.parent[i + self.parent_offset] = item + + def __delitem__(self, i): + try: + del self.data[i] + del self.items[i] + if self.parent: + del self.parent[i + self.parent_offset] + except TypeError: + assert i.step is None, 'cannot handle slice with stride' + del self.data[i.start:i.stop] + del self.items[i.start:i.stop] + if self.parent: + k = (i.start or 0) + self.parent_offset + n = (i.stop or len(self)) + self.parent_offset + del self.parent[k:n] + + def __add__(self, other): + if isinstance(other, ViewList): + return self.__class__(self.data + other.data, + items=(self.items + other.items)) + else: + raise TypeError('adding non-ViewList to a ViewList') + + def __radd__(self, other): + if isinstance(other, ViewList): + return self.__class__(other.data + self.data, + items=(other.items + self.items)) + else: + raise TypeError('adding ViewList to a non-ViewList') + + def __iadd__(self, other): + if isinstance(other, ViewList): + self.data += other.data + else: + raise TypeError('argument to += must be a ViewList') + return self + + def __mul__(self, n): + return self.__class__(self.data * n, items=(self.items * n)) + + __rmul__ = __mul__ + + def __imul__(self, n): + self.data *= n + self.items *= n + return self + + def extend(self, other): + if not isinstance(other, ViewList): + raise TypeError('extending a ViewList with a non-ViewList') + if self.parent: + self.parent.insert(len(self.data) + self.parent_offset, other) + self.data.extend(other.data) + self.items.extend(other.items) + + def append(self, item, source=None, offset=0): + if source is None: + self.extend(item) + else: + if self.parent: + self.parent.insert(len(self.data) + self.parent_offset, item, + source, offset) + self.data.append(item) + self.items.append((source, offset)) + + def insert(self, i, item, source=None, offset=0): + if source is None: + if not isinstance(item, ViewList): + raise TypeError('inserting non-ViewList with no source given') + self.data[i:i] = item.data + self.items[i:i] = item.items + if self.parent: + index = (len(self.data) + i) % len(self.data) + self.parent.insert(index + self.parent_offset, item) + else: + self.data.insert(i, item) + self.items.insert(i, (source, offset)) + if self.parent: + index = (len(self.data) + i) % len(self.data) + self.parent.insert(index + self.parent_offset, item, + source, offset) + + def pop(self, i=-1): + if self.parent: + index = (len(self.data) + i) % len(self.data) + self.parent.pop(index + self.parent_offset) + self.items.pop(i) + return self.data.pop(i) + + def trim_start(self, n=1): + """ + Remove items from the start of the list, without touching the parent. + """ + if n > len(self.data): + raise IndexError("Size of trim too large; can't trim %s items " + "from a list of size %s." % (n, len(self.data))) + elif n < 0: + raise IndexError('Trim size must be >= 0.') + del self.data[:n] + del self.items[:n] + if self.parent: + self.parent_offset += n + + def trim_end(self, n=1): + """ + Remove items from the end of the list, without touching the parent. + """ + if n > len(self.data): + raise IndexError("Size of trim too large; can't trim %s items " + "from a list of size %s." % (n, len(self.data))) + elif n < 0: + raise IndexError('Trim size must be >= 0.') + del self.data[-n:] + del self.items[-n:] + + def remove(self, item): + index = self.index(item) + del self[index] + + def count(self, item): + return self.data.count(item) + + def index(self, item): + return self.data.index(item) + + def reverse(self): + self.data.reverse() + self.items.reverse() + self.parent = None + + def sort(self, *args): + tmp = sorted(zip(self.data, self.items), *args) + self.data = [entry[0] for entry in tmp] + self.items = [entry[1] for entry in tmp] + self.parent = None + + def info(self, i): + """Return source & offset for index `i`.""" + try: + return self.items[i] + except IndexError: + if i == len(self.data): # Just past the end + return self.items[i - 1][0], None + else: + raise + + def source(self, i): + """Return source for index `i`.""" + return self.info(i)[0] + + def offset(self, i): + """Return offset for index `i`.""" + return self.info(i)[1] + + def disconnect(self): + """Break link between this list and parent list.""" + self.parent = None + + def xitems(self): + """Return iterator yielding (source, offset, value) tuples.""" + for (value, (source, offset)) in zip(self.data, self.items): + yield source, offset, value + + def pprint(self): + """Print the list in `grep` format (`source:offset:value` lines)""" + for line in self.xitems(): + print("%s:%d:%s" % line) + + +class StringList(ViewList): + + """A `ViewList` with string-specific methods.""" + + def trim_left(self, length, start=0, end=sys.maxsize): + """ + Trim `length` characters off the beginning of each item, in-place, + from index `start` to `end`. No whitespace-checking is done on the + trimmed text. Does not affect slice parent. + """ + self.data[start:end] = [line[length:] + for line in self.data[start:end]] + + def get_text_block(self, start, flush_left=False): + """ + Return a contiguous block of text. + + If `flush_left` is true, raise `UnexpectedIndentationError` if an + indented line is encountered before the text block ends (with a blank + line). + """ + end = start + last = len(self.data) + while end < last: + line = self.data[end] + if not line.strip(): + break + if flush_left and (line[0] == ' '): + source, offset = self.info(end) + raise UnexpectedIndentationError(self[start:end], source, + offset + 1) + end += 1 + return self[start:end] + + def get_indented(self, start=0, until_blank=False, strip_indent=True, + block_indent=None, first_indent=None): + """ + Extract and return a StringList of indented lines of text. + + Collect all lines with indentation, determine the minimum indentation, + remove the minimum indentation from all indented lines (unless + `strip_indent` is false), and return them. All lines up to but not + including the first unindented line will be returned. + + :Parameters: + - `start`: The index of the first line to examine. + - `until_blank`: Stop collecting at the first blank line if true. + - `strip_indent`: Strip common leading indent if true (default). + - `block_indent`: The indent of the entire block, if known. + - `first_indent`: The indent of the first line, if known. + + :Return: + - a StringList of indented lines with minimum indent removed; + - the amount of the indent; + - a boolean: did the indented block finish with a blank line or EOF? + """ + indent = block_indent # start with None if unknown + end = start + if block_indent is not None and first_indent is None: + first_indent = block_indent + if first_indent is not None: + end += 1 + last = len(self.data) + while end < last: + line = self.data[end] + if line and (line[0] != ' ' + or (block_indent is not None + and line[:block_indent].strip())): + # Line not indented or insufficiently indented. + # Block finished properly iff the last indented line blank: + blank_finish = ((end > start) + and not self.data[end - 1].strip()) + break + stripped = line.lstrip() + if not stripped: # blank line + if until_blank: + blank_finish = 1 + break + elif block_indent is None: + line_indent = len(line) - len(stripped) + if indent is None: + indent = line_indent + else: + indent = min(indent, line_indent) + end += 1 + else: + blank_finish = 1 # block ends at end of lines + block = self[start:end] + if first_indent is not None and block: + block.data[0] = block.data[0][first_indent:] + if indent and strip_indent: + block.trim_left(indent, start=(first_indent is not None)) + return block, indent or 0, blank_finish + + def get_2D_block(self, top, left, bottom, right, strip_indent=True): + block = self[top:bottom] + indent = right + for i in range(len(block.data)): + # get slice from line, care for combining characters + ci = utils.column_indices(block.data[i]) + try: + left = ci[left] + except IndexError: + left += len(block.data[i]) - len(ci) + try: + right = ci[right] + except IndexError: + right += len(block.data[i]) - len(ci) + block.data[i] = line = block.data[i][left:right].rstrip() + if line: + indent = min(indent, len(line) - len(line.lstrip())) + if strip_indent and 0 < indent < right: + block.data = [line[indent:] for line in block.data] + return block + + def pad_double_width(self, pad_char): + """Pad all double-width characters in `self` appending `pad_char`. + + For East Asian language support. + """ + for i in range(len(self.data)): + line = self.data[i] + if isinstance(line, str): + new = [] + for char in line: + new.append(char) + if east_asian_width(char) in 'WF': # Wide & Full-width + new.append(pad_char) + self.data[i] = ''.join(new) + + def replace(self, old, new): + """Replace all occurrences of substring `old` with `new`.""" + for i in range(len(self.data)): + self.data[i] = self.data[i].replace(old, new) + + +class StateMachineError(Exception): pass +class UnknownStateError(StateMachineError): pass +class DuplicateStateError(StateMachineError): pass +class UnknownTransitionError(StateMachineError): pass +class DuplicateTransitionError(StateMachineError): pass +class TransitionPatternNotFound(StateMachineError): pass +class TransitionMethodNotFound(StateMachineError): pass +class UnexpectedIndentationError(StateMachineError): pass + + +class TransitionCorrection(Exception): + + """ + Raise from within a transition method to switch to another transition. + + Raise with one argument, the new transition name. + """ + + +class StateCorrection(Exception): + + """ + Raise from within a transition method to switch to another state. + + Raise with one or two arguments: new state name, and an optional new + transition name. + """ + + +def string2lines(astring, tab_width=8, convert_whitespace=False, + whitespace=re.compile('[\v\f]')): + """ + Return a list of one-line strings with tabs expanded, no newlines, and + trailing whitespace stripped. + + Each tab is expanded with between 1 and `tab_width` spaces, so that the + next character's index becomes a multiple of `tab_width` (8 by default). + + Parameters: + + - `astring`: a multi-line string. + - `tab_width`: the number of columns between tab stops. + - `convert_whitespace`: convert form feeds and vertical tabs to spaces? + - `whitespace`: pattern object with the to-be-converted + whitespace characters (default [\\v\\f]). + """ + if convert_whitespace: + astring = whitespace.sub(' ', astring) + return [s.expandtabs(tab_width).rstrip() for s in astring.splitlines()] + + +def _exception_data(): + """ + Return exception information: + + - the exception's class name; + - the exception object; + - the name of the file containing the offending code; + - the line number of the offending code; + - the function name of the offending code. + """ + type, value, traceback = sys.exc_info() + while traceback.tb_next: + traceback = traceback.tb_next + code = traceback.tb_frame.f_code + return (type.__name__, value, code.co_filename, traceback.tb_lineno, + code.co_name) diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/__init__.py b/.venv/lib/python3.12/site-packages/docutils/transforms/__init__.py new file mode 100644 index 00000000..20536b3d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/__init__.py @@ -0,0 +1,185 @@ +# $Id: __init__.py 9502 2023-12-14 22:39:08Z milde $ +# Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer +# Copyright: This module has been placed in the public domain. + +""" +This package contains modules for standard tree transforms available +to Docutils components. Tree transforms serve a variety of purposes: + +- To tie up certain syntax-specific "loose ends" that remain after the + initial parsing of the input plaintext. These transforms are used to + supplement a limited syntax. + +- To automate the internal linking of the document tree (hyperlink + references, footnote references, etc.). + +- To extract useful information from the document tree. These + transforms may be used to construct (for example) indexes and tables + of contents. + +Each transform is an optional step that a Docutils component may +choose to perform on the parsed document. +""" + +__docformat__ = 'reStructuredText' + + +from docutils import languages, ApplicationError, TransformSpec + + +class TransformError(ApplicationError): + pass + + +class Transform: + """Docutils transform component abstract base class.""" + + default_priority = None + """Numerical priority of this transform, 0 through 999 (override).""" + + def __init__(self, document, startnode=None): + """ + Initial setup for in-place document transforms. + """ + + self.document = document + """The document tree to transform.""" + + self.startnode = startnode + """Node from which to begin the transform. For many transforms which + apply to the document as a whole, `startnode` is not set (i.e. its + value is `None`).""" + + self.language = languages.get_language( + document.settings.language_code, document.reporter) + """Language module local to this document.""" + + def apply(self, **kwargs): + """Override to apply the transform to the document tree.""" + raise NotImplementedError('subclass must override this method') + + +class Transformer(TransformSpec): + """ + Store "transforms" and apply them to the document tree. + + Collect lists of `Transform` instances and "unknown_reference_resolvers" + from Docutils components (`TransformSpec` instances). + Apply collected "transforms" to the document tree. + + Also keeps track of components by component type name. + + https://docutils.sourceforge.io/docs/peps/pep-0258.html#transformer + """ + + def __init__(self, document): + self.transforms = [] + """List of transforms to apply. Each item is a 4-tuple: + ``(priority string, transform class, pending node or None, kwargs)``. + """ + + self.unknown_reference_resolvers = [] + """List of hook functions which assist in resolving references.""" + + self.document = document + """The `nodes.document` object this Transformer is attached to.""" + + self.applied = [] + """Transforms already applied, in order.""" + + self.sorted = False + """Boolean: is `self.tranforms` sorted?""" + + self.components = {} + """Mapping of component type name to component object. + + Set by `self.populate_from_components()`. + """ + + self.serialno = 0 + """Internal serial number to keep track of the add order of + transforms.""" + + def add_transform(self, transform_class, priority=None, **kwargs): + """ + Store a single transform. Use `priority` to override the default. + `kwargs` is a dictionary whose contents are passed as keyword + arguments to the `apply` method of the transform. This can be used to + pass application-specific data to the transform instance. + """ + if priority is None: + priority = transform_class.default_priority + priority_string = self.get_priority_string(priority) + self.transforms.append( + (priority_string, transform_class, None, kwargs)) + self.sorted = False + + def add_transforms(self, transform_list): + """Store multiple transforms, with default priorities.""" + for transform_class in transform_list: + priority_string = self.get_priority_string( + transform_class.default_priority) + self.transforms.append( + (priority_string, transform_class, None, {})) + self.sorted = False + + def add_pending(self, pending, priority=None): + """Store a transform with an associated `pending` node.""" + transform_class = pending.transform + if priority is None: + priority = transform_class.default_priority + priority_string = self.get_priority_string(priority) + self.transforms.append( + (priority_string, transform_class, pending, {})) + self.sorted = False + + def get_priority_string(self, priority): + """ + Return a string, `priority` combined with `self.serialno`. + + This ensures FIFO order on transforms with identical priority. + """ + self.serialno += 1 + return '%03d-%03d' % (priority, self.serialno) + + def populate_from_components(self, components): + """ + Store each component's default transforms and reference resolvers + + Transforms are stored with default priorities for later sorting. + "Unknown reference resolvers" are sorted and stored. + Components that don't inherit from `TransformSpec` are ignored. + + Also, store components by type name in a mapping for later lookup. + """ + resolvers = [] + for component in components: + if not isinstance(component, TransformSpec): + continue + self.add_transforms(component.get_transforms()) + self.components[component.component_type] = component + resolvers.extend(component.unknown_reference_resolvers) + self.sorted = False # sort transform list in self.apply_transforms() + + # Sort and add helper functions to help resolve unknown references. + def keyfun(f): + return f.priority + resolvers.sort(key=keyfun) + self.unknown_reference_resolvers += resolvers + + def apply_transforms(self): + """Apply all of the stored transforms, in priority order.""" + self.document.reporter.attach_observer( + self.document.note_transform_message) + while self.transforms: + if not self.sorted: + # Unsorted initially, and whenever a transform is added + # (transforms may add other transforms). + self.transforms.sort(reverse=True) + self.sorted = True + priority, transform_class, pending, kwargs = self.transforms.pop() + transform = transform_class(self.document, startnode=pending) + transform.apply(**kwargs) + self.applied.append((priority, transform_class, pending, kwargs)) + self.document.reporter.detach_observer( + self.document.note_transform_message) diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/components.py b/.venv/lib/python3.12/site-packages/docutils/transforms/components.py new file mode 100644 index 00000000..9cbbb503 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/components.py @@ -0,0 +1,54 @@ +# $Id: components.py 9037 2022-03-05 23:31:10Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Docutils component-related transforms. +""" + +from docutils.transforms import Transform + +__docformat__ = 'reStructuredText' + + +class Filter(Transform): + + """ + Include or exclude elements which depend on a specific Docutils component. + + For use with `nodes.pending` elements. A "pending" element's dictionary + attribute ``details`` must contain the keys "component" and "format". The + value of ``details['component']`` must match the type name of the + component the elements depend on (e.g. "writer"). The value of + ``details['format']`` is the name of a specific format or context of that + component (e.g. "html"). If the matching Docutils component supports that + format or context, the "pending" element is replaced by the contents of + ``details['nodes']`` (a list of nodes); otherwise, the "pending" element + is removed. + + For example, up to version 0.17, the reStructuredText "meta" + directive created a "pending" element containing a "meta" element + (in ``pending.details['nodes']``). + Only writers (``pending.details['component'] == 'writer'``) + supporting the "html", "latex", or "odf" formats + (``pending.details['format'] == 'html,latex,odf'``) included the + "meta" element; it was deleted from the output of all other writers. + + This transform is no longer used by Docutils, it may be removed in future. + """ + # TODO: clean up or keep this for 3rd party (or possible future) use? + # (GM 2021-05-18) + + default_priority = 780 + + def apply(self): + pending = self.startnode + component_type = pending.details['component'] # 'reader' or 'writer' + formats = (pending.details['format']).split(',') + component = self.document.transformer.components[component_type] + for format in formats: + if component.supports(format): + pending.replace_self(pending.details['nodes']) + break + else: + pending.parent.remove(pending) diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/frontmatter.py b/.venv/lib/python3.12/site-packages/docutils/transforms/frontmatter.py new file mode 100644 index 00000000..9f534cce --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/frontmatter.py @@ -0,0 +1,540 @@ +# $Id: frontmatter.py 9552 2024-03-08 23:41:31Z milde $ +# Author: David Goodger, Ueli Schlaepfer <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Transforms_ related to the front matter of a document or a section +(information found before the main text): + +- `DocTitle`: Used to transform a lone top level section's title to + the document title, promote a remaining lone top-level section's + title to the document subtitle, and determine the document's title + metadata (document['title']) based on the document title and/or the + "title" setting. + +- `SectionSubTitle`: Used to transform a lone subsection into a + subtitle. + +- `DocInfo`: Used to transform a bibliographic field list into docinfo + elements. + +.. _transforms: https://docutils.sourceforge.io/docs/api/transforms.html +""" + +__docformat__ = 'reStructuredText' + +import re + +from docutils import nodes, parsers, utils +from docutils.transforms import TransformError, Transform + + +class TitlePromoter(Transform): + + """ + Abstract base class for DocTitle and SectionSubTitle transforms. + """ + + def promote_title(self, node): + """ + Transform the following tree:: + + <node> + <section> + <title> + ... + + into :: + + <node> + <title> + ... + + `node` is normally a document. + """ + # Type check + if not isinstance(node, nodes.Element): + raise TypeError('node must be of Element-derived type.') + + # `node` must not have a title yet. + assert not (len(node) and isinstance(node[0], nodes.title)) + section, index = self.candidate_index(node) + if index is None: + return False + + # Transfer the section's attributes to the node: + # NOTE: Change `replace` to False to NOT replace attributes that + # already exist in node with those in section. + # NOTE: Remove `and_source` to NOT copy the 'source' + # attribute from section + node.update_all_atts_concatenating(section, replace=True, + and_source=True) + + # setup_child is called automatically for all nodes. + node[:] = (section[:1] # section title + + node[:index] # everything that was in the + # node before the section + + section[1:]) # everything that was in the section + assert isinstance(node[0], nodes.title) + return True + + def promote_subtitle(self, node): + """ + Transform the following node tree:: + + <node> + <title> + <section> + <title> + ... + + into :: + + <node> + <title> + <subtitle> + ... + """ + # Type check + if not isinstance(node, nodes.Element): + raise TypeError('node must be of Element-derived type.') + + subsection, index = self.candidate_index(node) + if index is None: + return False + subtitle = nodes.subtitle() + + # Transfer the subsection's attributes to the new subtitle + # NOTE: Change `replace` to False to NOT replace attributes + # that already exist in node with those in section. + # NOTE: Remove `and_source` to NOT copy the 'source' + # attribute from section. + subtitle.update_all_atts_concatenating(subsection, replace=True, + and_source=True) + + # Transfer the contents of the subsection's title to the + # subtitle: + subtitle[:] = subsection[0][:] + node[:] = (node[:1] # title + + [subtitle] + # everything that was before the section: + + node[1:index] + # everything that was in the subsection: + + subsection[1:]) + return True + + def candidate_index(self, node): + """ + Find and return the promotion candidate and its index. + + Return (None, None) if no valid candidate was found. + """ + index = node.first_child_not_matching_class( + nodes.PreBibliographic) + if (index is None or len(node) > (index + 1) + or not isinstance(node[index], nodes.section)): + return None, None + else: + return node[index], index + + +class DocTitle(TitlePromoter): + + """ + In reStructuredText_, there is no way to specify a document title + and subtitle explicitly. Instead, we can supply the document title + (and possibly the subtitle as well) implicitly, and use this + two-step transform to "raise" or "promote" the title(s) (and their + corresponding section contents) to the document level. + + 1. If the document contains a single top-level section as its first + element (instances of `nodes.PreBibliographic` are ignored), + the top-level section's title becomes the document's title, and + the top-level section's contents become the document's immediate + contents. The title is also used for the <document> element's + "title" attribute default value. + + 2. If step 1 successfully determines the document title, we + continue by checking for a subtitle. + + If the lone top-level section itself contains a single second-level + section as its first "non-PreBibliographic" element, that section's + title is promoted to the document's subtitle, and that section's + contents become the document's immediate contents. + + Example: + Given this input text:: + + ================= + Top-Level Title + ================= + + Second-Level Title + ~~~~~~~~~~~~~~~~~~ + + A paragraph. + + After parsing and running the DocTitle transform, the result is:: + + <document names="top-level title"> + <title> + Top-Level Title + <subtitle names="second-level title"> + Second-Level Title + <paragraph> + A paragraph. + + (Note that the implicit hyperlink target generated by the + "Second-Level Title" is preserved on the <subtitle> element + itself.) + + Any `nodes.PreBibliographic` instances occurring before the + document title or subtitle are accumulated and inserted as + the first body elements after the title(s). + + .. _reStructuredText: https://docutils.sourceforge.io/rst.html + """ + + default_priority = 320 + + def set_metadata(self): + """ + Set document['title'] metadata title from the following + sources, listed in order of priority: + + * Existing document['title'] attribute. + * "title" setting. + * Document title node (as promoted by promote_title). + """ + if not self.document.hasattr('title'): + if self.document.settings.title is not None: + self.document['title'] = self.document.settings.title + elif len(self.document) and isinstance(self.document[0], + nodes.title): + self.document['title'] = self.document[0].astext() + + def apply(self): + if self.document.settings.setdefault('doctitle_xform', True): + # promote_(sub)title defined in TitlePromoter base class. + if self.promote_title(self.document): + # If a title has been promoted, also try to promote a + # subtitle. + self.promote_subtitle(self.document) + # Set document['title']. + self.set_metadata() + + +class SectionSubTitle(TitlePromoter): + + """ + This works like document subtitles, but for sections. For example, :: + + <section> + <title> + Title + <section> + <title> + Subtitle + ... + + is transformed into :: + + <section> + <title> + Title + <subtitle> + Subtitle + ... + + For details refer to the docstring of DocTitle. + """ + + default_priority = 350 + + def apply(self): + if not self.document.settings.setdefault('sectsubtitle_xform', True): + return + for section in self.document.findall(nodes.section): + # On our way through the node tree, we are modifying it + # but only the not-yet-visited part, so that the iterator + # returned by findall() is not corrupted. + self.promote_subtitle(section) + + +class DocInfo(Transform): + + """ + This transform is specific to the reStructuredText_ markup syntax; + see "Bibliographic Fields" in the `reStructuredText Markup + Specification`_ for a high-level description. This transform + should be run *after* the `DocTitle` transform. + + If the document contains a field list as the first element (instances + of `nodes.PreBibliographic` are ignored), registered bibliographic + field names are transformed to the corresponding DTD elements, + becoming child elements of the <docinfo> element (except for a + dedication and/or an abstract, which become <topic> elements after + <docinfo>). + + For example, given this document fragment after parsing:: + + <document> + <title> + Document Title + <field_list> + <field> + <field_name> + Author + <field_body> + <paragraph> + A. Name + <field> + <field_name> + Status + <field_body> + <paragraph> + $RCSfile$ + ... + + After running the bibliographic field list transform, the + resulting document tree would look like this:: + + <document> + <title> + Document Title + <docinfo> + <author> + A. Name + <status> + frontmatter.py + ... + + The "Status" field contained an expanded RCS keyword, which is + normally (but optionally) cleaned up by the transform. The sole + contents of the field body must be a paragraph containing an + expanded RCS keyword of the form "$keyword: expansion text $". Any + RCS keyword can be processed in any bibliographic field. The + dollar signs and leading RCS keyword name are removed. Extra + processing is done for the following RCS keywords: + + - "RCSfile" expands to the name of the file in the RCS or CVS + repository, which is the name of the source file with a ",v" + suffix appended. The transform will remove the ",v" suffix. + + - "Date" expands to the format "YYYY/MM/DD hh:mm:ss" (in the UTC + time zone). The RCS Keywords transform will extract just the + date itself and transform it to an ISO 8601 format date, as in + "2000-12-31". + + (Since the source file for this text is itself stored under CVS, + we can't show an example of the "Date" RCS keyword because we + can't prevent any RCS keywords used in this explanation from + being expanded. Only the "RCSfile" keyword is stable; its + expansion text changes only if the file name changes.) + + .. _reStructuredText: https://docutils.sourceforge.io/rst.html + .. _reStructuredText Markup Specification: + https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html + """ + + default_priority = 340 + + biblio_nodes = { + 'author': nodes.author, + 'authors': nodes.authors, + 'organization': nodes.organization, + 'address': nodes.address, + 'contact': nodes.contact, + 'version': nodes.version, + 'revision': nodes.revision, + 'status': nodes.status, + 'date': nodes.date, + 'copyright': nodes.copyright, + 'dedication': nodes.topic, + 'abstract': nodes.topic} + """Canonical field name (lowcased) to node class name mapping for + bibliographic fields (field_list).""" + + def apply(self): + if not self.document.settings.setdefault('docinfo_xform', True): + return + document = self.document + index = document.first_child_not_matching_class( + nodes.PreBibliographic) + if index is None: + return + candidate = document[index] + if isinstance(candidate, nodes.field_list): + biblioindex = document.first_child_not_matching_class( + (nodes.Titular, nodes.Decorative, nodes.meta)) + nodelist = self.extract_bibliographic(candidate) + del document[index] # untransformed field list (candidate) + document[biblioindex:biblioindex] = nodelist + + def extract_bibliographic(self, field_list): + docinfo = nodes.docinfo() + bibliofields = self.language.bibliographic_fields + labels = self.language.labels + topics = {'dedication': None, 'abstract': None} + for field in field_list: + try: + name = field[0][0].astext() + normedname = nodes.fully_normalize_name(name) + if not (len(field) == 2 and normedname in bibliofields + and self.check_empty_biblio_field(field, name)): + raise TransformError + canonical = bibliofields[normedname] + biblioclass = self.biblio_nodes[canonical] + if issubclass(biblioclass, nodes.TextElement): + if not self.check_compound_biblio_field(field, name): + raise TransformError + utils.clean_rcs_keywords( + field[1][0], self.rcs_keyword_substitutions) + docinfo.append(biblioclass('', '', *field[1][0])) + elif issubclass(biblioclass, nodes.authors): + self.extract_authors(field, name, docinfo) + elif issubclass(biblioclass, nodes.topic): + if topics[canonical]: + field[-1] += self.document.reporter.warning( + 'There can only be one "%s" field.' % name, + base_node=field) + raise TransformError + title = nodes.title(name, labels[canonical]) + title[0].rawsource = labels[canonical] + topics[canonical] = biblioclass( + '', title, classes=[canonical], *field[1].children) + else: + docinfo.append(biblioclass('', *field[1].children)) + except TransformError: + if len(field[-1]) == 1 \ + and isinstance(field[-1][0], nodes.paragraph): + utils.clean_rcs_keywords( + field[-1][0], self.rcs_keyword_substitutions) + # if normedname not in bibliofields: + classvalue = nodes.make_id(normedname) + if classvalue: + field['classes'].append(classvalue) + docinfo.append(field) + nodelist = [] + if len(docinfo) != 0: + nodelist.append(docinfo) + for name in ('dedication', 'abstract'): + if topics[name]: + nodelist.append(topics[name]) + return nodelist + + def check_empty_biblio_field(self, field, name): + if len(field[-1]) < 1: + field[-1] += self.document.reporter.warning( + f'Cannot extract empty bibliographic field "{name}".', + base_node=field) + return False + return True + + def check_compound_biblio_field(self, field, name): + # Check that the `field` body contains a single paragraph + # (i.e. it must *not* be a compound element). + f_body = field[-1] + if len(f_body) == 1 and isinstance(f_body[0], nodes.paragraph): + return True + # Restore single author name with initial (E. Xampl) parsed as + # enumerated list + # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#enumerated-lists + if (isinstance(f_body[0], nodes.enumerated_list) + and '\n' not in f_body.rawsource.strip()): + # parse into a dummy document and use created nodes + _document = utils.new_document('*DocInfo transform*', + field.document.settings) + parser = parsers.rst.Parser() + parser.parse('\\'+f_body.rawsource, _document) + if (len(_document.children) == 1 + and isinstance(_document.children[0], nodes.paragraph)): + f_body.children = _document.children + return True + # Check failed, add a warning + content = [f'<{e.tagname}>' for e in f_body.children] + if len(content) > 1: + content = '[' + ', '.join(content) + ']' + else: + content = 'a ' + content[0] + f_body += self.document.reporter.warning( + f'Bibliographic field "{name}"\nmust contain ' + f'a single <paragraph>, not {content}.', + base_node=field) + return False + + rcs_keyword_substitutions = [ + (re.compile(r'\$' r'Date: (\d\d\d\d)[-/](\d\d)[-/](\d\d)[ T][\d:]+' + r'[^$]* \$', re.IGNORECASE), r'\1-\2-\3'), + (re.compile(r'\$' r'RCSfile: (.+),v \$', re.IGNORECASE), r'\1'), + (re.compile(r'\$[a-zA-Z]+: (.+) \$'), r'\1')] + + def extract_authors(self, field, name, docinfo): + try: + if len(field[1]) == 1: + if isinstance(field[1][0], nodes.paragraph): + authors = self.authors_from_one_paragraph(field) + elif isinstance(field[1][0], nodes.bullet_list): + authors = self.authors_from_bullet_list(field) + else: + raise TransformError + else: + authors = self.authors_from_paragraphs(field) + authornodes = [nodes.author('', '', *author) + for author in authors if author] + if len(authornodes) >= 1: + docinfo.append(nodes.authors('', *authornodes)) + else: + raise TransformError + except TransformError: + field[-1] += self.document.reporter.warning( + f'Cannot extract "{name}" from bibliographic field:\n' + f'Bibliographic field "{name}" must contain either\n' + ' a single paragraph (with author names separated by one of ' + f'"{"".join(self.language.author_separators)}"),\n' + ' multiple paragraphs (one per author),\n' + ' or a bullet list with one author name per item.\n' + 'Note: Leading initials can cause (mis)recognizing names ' + 'as enumerated list.', + base_node=field) + raise + + def authors_from_one_paragraph(self, field): + """Return list of Text nodes with author names in `field`. + + Author names must be separated by one of the "autor separators" + defined for the document language (default: ";" or ","). + """ + # @@ keep original formatting? (e.g. ``:authors: A. Test, *et-al*``) + text = ''.join(str(node) + for node in field[1].findall(nodes.Text)) + if not text: + raise TransformError + for authorsep in self.language.author_separators: + # don't split at escaped `authorsep`: + pattern = '(?<!\x00)%s' % authorsep + authornames = re.split(pattern, text) + if len(authornames) > 1: + break + authornames = (name.strip() for name in authornames) + return [[nodes.Text(name)] for name in authornames if name] + + def authors_from_bullet_list(self, field): + authors = [] + for item in field[1][0]: + if isinstance(item, nodes.comment): + continue + if len(item) != 1 or not isinstance(item[0], nodes.paragraph): + raise TransformError + authors.append(item[0].children) + if not authors: + raise TransformError + return authors + + def authors_from_paragraphs(self, field): + for item in field[1]: + if not isinstance(item, (nodes.paragraph, nodes.comment)): + raise TransformError + authors = [item.children for item in field[1] + if not isinstance(item, nodes.comment)] + return authors diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/misc.py b/.venv/lib/python3.12/site-packages/docutils/transforms/misc.py new file mode 100644 index 00000000..e2cc796c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/misc.py @@ -0,0 +1,144 @@ +# $Id: misc.py 9037 2022-03-05 23:31:10Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Miscellaneous transforms. +""" + +__docformat__ = 'reStructuredText' + +from docutils import nodes +from docutils.transforms import Transform + + +class CallBack(Transform): + + """ + Inserts a callback into a document. The callback is called when the + transform is applied, which is determined by its priority. + + For use with `nodes.pending` elements. Requires a ``details['callback']`` + entry, a bound method or function which takes one parameter: the pending + node. Other data can be stored in the ``details`` attribute or in the + object hosting the callback method. + """ + + default_priority = 990 + + def apply(self): + pending = self.startnode + pending.details['callback'](pending) + pending.parent.remove(pending) + + +class ClassAttribute(Transform): + + """ + Move the "class" attribute specified in the "pending" node into the + immediately following non-comment element. + """ + + default_priority = 210 + + def apply(self): + pending = self.startnode + parent = pending.parent + child = pending + while parent: + # Check for appropriate following siblings: + for index in range(parent.index(child) + 1, len(parent)): + element = parent[index] + if (isinstance(element, nodes.Invisible) + or isinstance(element, nodes.system_message)): + continue + element['classes'] += pending.details['class'] + pending.parent.remove(pending) + return + else: + # At end of section or container; apply to sibling + child = parent + parent = parent.parent + error = self.document.reporter.error( + 'No suitable element following "%s" directive' + % pending.details['directive'], + nodes.literal_block(pending.rawsource, pending.rawsource), + line=pending.line) + pending.replace_self(error) + + +class Transitions(Transform): + + """ + Move transitions at the end of sections up the tree. Complain + on transitions after a title, at the beginning or end of the + document, and after another transition. + + For example, transform this:: + + <section> + ... + <transition> + <section> + ... + + into this:: + + <section> + ... + <transition> + <section> + ... + """ + + default_priority = 830 + + def apply(self): + for node in self.document.findall(nodes.transition): + self.visit_transition(node) + + def visit_transition(self, node): + index = node.parent.index(node) + error = None + if (index == 0 + or isinstance(node.parent[0], nodes.title) + and (index == 1 + or isinstance(node.parent[1], nodes.subtitle) + and index == 2)): + assert (isinstance(node.parent, nodes.document) + or isinstance(node.parent, nodes.section)) + error = self.document.reporter.error( + 'Document or section may not begin with a transition.', + source=node.source, line=node.line) + elif isinstance(node.parent[index - 1], nodes.transition): + error = self.document.reporter.error( + 'At least one body element must separate transitions; ' + 'adjacent transitions are not allowed.', + source=node.source, line=node.line) + if error: + # Insert before node and update index. + node.parent.insert(index, error) + index += 1 + assert index < len(node.parent) + if index != len(node.parent) - 1: + # No need to move the node. + return + # Node behind which the transition is to be moved. + sibling = node + # While sibling is the last node of its parent. + while index == len(sibling.parent) - 1: + sibling = sibling.parent + # If sibling is the whole document (i.e. it has no parent). + if sibling.parent is None: + # Transition at the end of document. Do not move the + # transition up, and place an error behind. + error = self.document.reporter.error( + 'Document may not end with a transition.', + line=node.line) + node.parent.insert(node.parent.index(node) + 1, error) + return + index = sibling.parent.index(sibling) + # Remove the original transition node. + node.parent.remove(node) + # Insert the transition after the sibling. + sibling.parent.insert(index + 1, node) diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/parts.py b/.venv/lib/python3.12/site-packages/docutils/transforms/parts.py new file mode 100644 index 00000000..fb3898fa --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/parts.py @@ -0,0 +1,176 @@ +# $Id: parts.py 9038 2022-03-05 23:31:46Z milde $ +# Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Dmitry Jemerov +# Copyright: This module has been placed in the public domain. + +""" +Transforms related to document parts. +""" + +__docformat__ = 'reStructuredText' + + +import sys +from docutils import nodes +from docutils.transforms import Transform + + +class SectNum(Transform): + + """ + Automatically assigns numbers to the titles of document sections. + + It is possible to limit the maximum section level for which the numbers + are added. For those sections that are auto-numbered, the "autonum" + attribute is set, informing the contents table generator that a different + form of the TOC should be used. + """ + + default_priority = 710 + """Should be applied before `Contents`.""" + + def apply(self): + self.maxdepth = self.startnode.details.get('depth', None) + self.startvalue = self.startnode.details.get('start', 1) + self.prefix = self.startnode.details.get('prefix', '') + self.suffix = self.startnode.details.get('suffix', '') + self.startnode.parent.remove(self.startnode) + if self.document.settings.sectnum_xform: + if self.maxdepth is None: + self.maxdepth = sys.maxsize + self.update_section_numbers(self.document) + else: # store details for eventual section numbering by the writer + self.document.settings.sectnum_depth = self.maxdepth + self.document.settings.sectnum_start = self.startvalue + self.document.settings.sectnum_prefix = self.prefix + self.document.settings.sectnum_suffix = self.suffix + + def update_section_numbers(self, node, prefix=(), depth=0): + depth += 1 + if prefix: + sectnum = 1 + else: + sectnum = self.startvalue + for child in node: + if isinstance(child, nodes.section): + numbers = prefix + (str(sectnum),) + title = child[0] + # Use for spacing: + generated = nodes.generated( + '', (self.prefix + '.'.join(numbers) + self.suffix + + '\u00a0' * 3), + classes=['sectnum']) + title.insert(0, generated) + title['auto'] = 1 + if depth < self.maxdepth: + self.update_section_numbers(child, numbers, depth) + sectnum += 1 + + +class Contents(Transform): + + """ + This transform generates a table of contents from the entire document tree + or from a single branch. It locates "section" elements and builds them + into a nested bullet list, which is placed within a "topic" created by the + contents directive. A title is either explicitly specified, taken from + the appropriate language module, or omitted (local table of contents). + The depth may be specified. Two-way references between the table of + contents and section titles are generated (requires Writer support). + + This transform requires a startnode, which contains generation + options and provides the location for the generated table of contents (the + startnode is replaced by the table of contents "topic"). + """ + + default_priority = 720 + + def apply(self): + # let the writer (or output software) build the contents list? + toc_by_writer = getattr(self.document.settings, 'use_latex_toc', False) + details = self.startnode.details + if 'local' in details: + startnode = self.startnode.parent.parent + while not (isinstance(startnode, nodes.section) + or isinstance(startnode, nodes.document)): + # find the ToC root: a direct ancestor of startnode + startnode = startnode.parent + else: + startnode = self.document + self.toc_id = self.startnode.parent['ids'][0] + if 'backlinks' in details: + self.backlinks = details['backlinks'] + else: + self.backlinks = self.document.settings.toc_backlinks + if toc_by_writer: + # move customization settings to the parent node + self.startnode.parent.attributes.update(details) + self.startnode.parent.remove(self.startnode) + else: + contents = self.build_contents(startnode) + if len(contents): + self.startnode.replace_self(contents) + else: + self.startnode.parent.parent.remove(self.startnode.parent) + + def build_contents(self, node, level=0): + level += 1 + sections = [sect for sect in node if isinstance(sect, nodes.section)] + entries = [] + depth = self.startnode.details.get('depth', sys.maxsize) + for section in sections: + title = section[0] + auto = title.get('auto') # May be set by SectNum. + entrytext = self.copy_and_filter(title) + reference = nodes.reference('', '', refid=section['ids'][0], + *entrytext) + ref_id = self.document.set_id(reference, + suggested_prefix='toc-entry') + entry = nodes.paragraph('', '', reference) + item = nodes.list_item('', entry) + if (self.backlinks in ('entry', 'top') + and title.next_node(nodes.reference) is None): + if self.backlinks == 'entry': + title['refid'] = ref_id + elif self.backlinks == 'top': + title['refid'] = self.toc_id + if level < depth: + subsects = self.build_contents(section, level) + item += subsects + entries.append(item) + if entries: + contents = nodes.bullet_list('', *entries) + if auto: # auto-numbered sections + contents['classes'].append('auto-toc') + return contents + else: + return [] + + def copy_and_filter(self, node): + """Return a copy of a title, with references, images, etc. removed.""" + visitor = ContentsFilter(self.document) + node.walkabout(visitor) + return visitor.get_entry_text() + + +class ContentsFilter(nodes.TreeCopyVisitor): + + def get_entry_text(self): + return self.get_tree_copy().children + + def visit_citation_reference(self, node): + raise nodes.SkipNode + + def visit_footnote_reference(self, node): + raise nodes.SkipNode + + def visit_image(self, node): + if node.hasattr('alt'): + self.parent.append(nodes.Text(node['alt'])) + raise nodes.SkipNode + + def ignore_node_but_process_children(self, node): + raise nodes.SkipDeparture + + visit_problematic = ignore_node_but_process_children + visit_reference = ignore_node_but_process_children + visit_target = ignore_node_but_process_children diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/peps.py b/.venv/lib/python3.12/site-packages/docutils/transforms/peps.py new file mode 100644 index 00000000..750dbf39 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/peps.py @@ -0,0 +1,308 @@ +# $Id: peps.py 9037 2022-03-05 23:31:10Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Transforms for PEP processing. + +- `Headers`: Used to transform a PEP's initial RFC-2822 header. It remains a + field list, but some entries get processed. +- `Contents`: Auto-inserts a table of contents. +- `PEPZero`: Special processing for PEP 0. +""" + +__docformat__ = 'reStructuredText' + +import os +import re +import time +from docutils import nodes, utils, languages +from docutils import DataError +from docutils.transforms import Transform +from docutils.transforms import parts, references, misc + + +class Headers(Transform): + + """ + Process fields in a PEP's initial RFC-2822 header. + """ + + default_priority = 360 + + pep_url = 'pep-%04d' + pep_cvs_url = ('http://hg.python.org' + '/peps/file/default/pep-%04d.txt') + rcs_keyword_substitutions = ( + (re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'), + (re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),) + + def apply(self): + if not len(self.document): + # @@@ replace these DataErrors with proper system messages + raise DataError('Document tree is empty.') + header = self.document[0] + if (not isinstance(header, nodes.field_list) + or 'rfc2822' not in header['classes']): + raise DataError('Document does not begin with an RFC-2822 ' + 'header; it is not a PEP.') + pep = None + for field in header: + if field[0].astext().lower() == 'pep': # should be the first field + value = field[1].astext() + try: + pep = int(value) + cvs_url = self.pep_cvs_url % pep + except ValueError: + pep = value + cvs_url = None + msg = self.document.reporter.warning( + '"PEP" header must contain an integer; "%s" is an ' + 'invalid value.' % pep, base_node=field) + msgid = self.document.set_id(msg) + prb = nodes.problematic(value, value or '(none)', + refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + if len(field[1]): + field[1][0][:] = [prb] + else: + field[1] += nodes.paragraph('', '', prb) + break + if pep is None: + raise DataError('Document does not contain an RFC-2822 "PEP" ' + 'header.') + if pep == 0: + # Special processing for PEP 0. + pending = nodes.pending(PEPZero) + self.document.insert(1, pending) + self.document.note_pending(pending) + if len(header) < 2 or header[1][0].astext().lower() != 'title': + raise DataError('No title!') + for field in header: + name = field[0].astext().lower() + body = field[1] + if len(body) > 1: + raise DataError('PEP header field body contains multiple ' + 'elements:\n%s' % field.pformat(level=1)) + elif len(body) == 1: + if not isinstance(body[0], nodes.paragraph): + raise DataError('PEP header field body may only contain ' + 'a single paragraph:\n%s' + % field.pformat(level=1)) + elif name == 'last-modified': + try: + date = time.strftime( + '%d-%b-%Y', + time.localtime(os.stat(self.document['source'])[8])) + except OSError: + date = 'unknown' + if cvs_url: + body += nodes.paragraph( + '', '', nodes.reference('', date, refuri=cvs_url)) + else: + # empty + continue + para = body[0] + if name == 'author': + for node in para: + if isinstance(node, nodes.reference): + node.replace_self(mask_email(node)) + elif name == 'discussions-to': + for node in para: + if isinstance(node, nodes.reference): + node.replace_self(mask_email(node, pep)) + elif name in ('replaces', 'replaced-by', 'requires'): + newbody = [] + space = nodes.Text(' ') + for refpep in re.split(r',?\s+', body.astext()): + pepno = int(refpep) + newbody.append(nodes.reference( + refpep, refpep, + refuri=(self.document.settings.pep_base_url + + self.pep_url % pepno))) + newbody.append(space) + para[:] = newbody[:-1] # drop trailing space + elif name == 'last-modified': + utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions) + if cvs_url: + date = para.astext() + para[:] = [nodes.reference('', date, refuri=cvs_url)] + elif name == 'content-type': + pep_type = para.astext() + uri = self.document.settings.pep_base_url + self.pep_url % 12 + para[:] = [nodes.reference('', pep_type, refuri=uri)] + elif name == 'version' and len(body): + utils.clean_rcs_keywords(para, self.rcs_keyword_substitutions) + + +class Contents(Transform): + + """ + Insert an empty table of contents topic and a transform placeholder into + the document after the RFC 2822 header. + """ + + default_priority = 380 + + def apply(self): + language = languages.get_language(self.document.settings.language_code, + self.document.reporter) + name = language.labels['contents'] + title = nodes.title('', name) + topic = nodes.topic('', title, classes=['contents']) + name = nodes.fully_normalize_name(name) + if not self.document.has_name(name): + topic['names'].append(name) + self.document.note_implicit_target(topic) + pending = nodes.pending(parts.Contents) + topic += pending + self.document.insert(1, topic) + self.document.note_pending(pending) + + +class TargetNotes(Transform): + + """ + Locate the "References" section, insert a placeholder for an external + target footnote insertion transform at the end, and schedule the + transform to run immediately. + """ + + default_priority = 520 + + def apply(self): + doc = self.document + i = len(doc) - 1 + refsect = copyright = None + while i >= 0 and isinstance(doc[i], nodes.section): + title_words = doc[i][0].astext().lower().split() + if 'references' in title_words: + refsect = doc[i] + break + elif 'copyright' in title_words: + copyright = i + i -= 1 + if not refsect: + refsect = nodes.section() + refsect += nodes.title('', 'References') + doc.set_id(refsect) + if copyright: + # Put the new "References" section before "Copyright": + doc.insert(copyright, refsect) + else: + # Put the new "References" section at end of doc: + doc.append(refsect) + pending = nodes.pending(references.TargetNotes) + refsect.append(pending) + self.document.note_pending(pending, 0) + pending = nodes.pending(misc.CallBack, + details={'callback': self.cleanup_callback}) + refsect.append(pending) + self.document.note_pending(pending, 1) + + def cleanup_callback(self, pending): + """ + Remove an empty "References" section. + + Called after the `references.TargetNotes` transform is complete. + """ + if len(pending.parent) == 2: # <title> and <pending> + pending.parent.parent.remove(pending.parent) + + +class PEPZero(Transform): + + """ + Special processing for PEP 0. + """ + + default_priority = 760 + + def apply(self): + visitor = PEPZeroSpecial(self.document) + self.document.walk(visitor) + self.startnode.parent.remove(self.startnode) + + +class PEPZeroSpecial(nodes.SparseNodeVisitor): + + """ + Perform the special processing needed by PEP 0: + + - Mask email addresses. + + - Link PEP numbers in the second column of 4-column tables to the PEPs + themselves. + """ + + pep_url = Headers.pep_url + + def unknown_visit(self, node): + pass + + def visit_reference(self, node): + node.replace_self(mask_email(node)) + + def visit_field_list(self, node): + if 'rfc2822' in node['classes']: + raise nodes.SkipNode + + def visit_tgroup(self, node): + self.pep_table = node['cols'] == 4 + self.entry = 0 + + def visit_colspec(self, node): + self.entry += 1 + if self.pep_table and self.entry == 2: + node['classes'].append('num') + + def visit_row(self, node): + self.entry = 0 + + def visit_entry(self, node): + self.entry += 1 + if self.pep_table and self.entry == 2 and len(node) == 1: + node['classes'].append('num') + p = node[0] + if isinstance(p, nodes.paragraph) and len(p) == 1: + text = p.astext() + try: + pep = int(text) + ref = (self.document.settings.pep_base_url + + self.pep_url % pep) + p[0] = nodes.reference(text, text, refuri=ref) + except ValueError: + pass + + +non_masked_addresses = ('peps@python.org', + 'python-list@python.org', + 'python-dev@python.org') + + +def mask_email(ref, pepno=None): + """ + Mask the email address in `ref` and return a replacement node. + + `ref` is returned unchanged if it contains no email address. + + For email addresses such as "user@host", mask the address as "user at + host" (text) to thwart simple email address harvesters (except for those + listed in `non_masked_addresses`). If a PEP number (`pepno`) is given, + return a reference including a default email subject. + """ + if ref.hasattr('refuri') and ref['refuri'].startswith('mailto:'): + if ref['refuri'][8:] in non_masked_addresses: + replacement = ref[0] + else: + replacement_text = ref.astext().replace('@', ' at ') + replacement = nodes.raw('', replacement_text, format='html') + if pepno is None: + return replacement + else: + ref['refuri'] += '?subject=PEP%%20%s' % pepno + ref[:] = [replacement] + return ref + else: + return ref diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/references.py b/.venv/lib/python3.12/site-packages/docutils/transforms/references.py new file mode 100644 index 00000000..06b7697e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/references.py @@ -0,0 +1,924 @@ +# $Id: references.py 9613 2024-04-06 13:27:52Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Transforms for resolving references. +""" + +__docformat__ = 'reStructuredText' + +from docutils import nodes, utils +from docutils.transforms import Transform + + +class PropagateTargets(Transform): + + """ + Propagate empty internal targets to the next element. + + Given the following nodes:: + + <target ids="internal1" names="internal1"> + <target anonymous="1" ids="id1"> + <target ids="internal2" names="internal2"> + <paragraph> + This is a test. + + `PropagateTargets` propagates the ids and names of the internal + targets preceding the paragraph to the paragraph itself:: + + <target refid="internal1"> + <target anonymous="1" refid="id1"> + <target refid="internal2"> + <paragraph ids="internal2 id1 internal1" names="internal2 internal1"> + This is a test. + """ + + default_priority = 260 + + def apply(self): + for target in self.document.findall(nodes.target): + # Only block-level targets without reference (like ".. _target:"): + if (isinstance(target.parent, nodes.TextElement) + or (target.hasattr('refid') or target.hasattr('refuri') + or target.hasattr('refname'))): + continue + assert len(target) == 0, 'error: block-level target has children' + next_node = target.next_node(ascend=True) + # skip system messages (may be removed by universal.FilterMessages) + while isinstance(next_node, nodes.system_message): + next_node = next_node.next_node(ascend=True, descend=False) + # Do not move names and ids into Invisibles (we'd lose the + # attributes) or different Targetables (e.g. footnotes). + if (next_node is None + or isinstance(next_node, (nodes.Invisible, nodes.Targetable)) + and not isinstance(next_node, nodes.target)): + continue + next_node['ids'].extend(target['ids']) + next_node['names'].extend(target['names']) + # Set defaults for next_node.expect_referenced_by_name/id. + if not hasattr(next_node, 'expect_referenced_by_name'): + next_node.expect_referenced_by_name = {} + if not hasattr(next_node, 'expect_referenced_by_id'): + next_node.expect_referenced_by_id = {} + for id in target['ids']: + # Update IDs to node mapping. + self.document.ids[id] = next_node + # If next_node is referenced by id ``id``, this + # target shall be marked as referenced. + next_node.expect_referenced_by_id[id] = target + for name in target['names']: + next_node.expect_referenced_by_name[name] = target + # If there are any expect_referenced_by_... attributes + # in target set, copy them to next_node. + next_node.expect_referenced_by_name.update( + getattr(target, 'expect_referenced_by_name', {})) + next_node.expect_referenced_by_id.update( + getattr(target, 'expect_referenced_by_id', {})) + # Set refid to point to the first former ID of target + # which is now an ID of next_node. + target['refid'] = target['ids'][0] + # Clear ids and names; they have been moved to + # next_node. + target['ids'] = [] + target['names'] = [] + self.document.note_refid(target) + + +class AnonymousHyperlinks(Transform): + + """ + Link anonymous references to targets. Given:: + + <paragraph> + <reference anonymous="1"> + internal + <reference anonymous="1"> + external + <target anonymous="1" ids="id1"> + <target anonymous="1" ids="id2" refuri="http://external"> + + Corresponding references are linked via "refid" or resolved via "refuri":: + + <paragraph> + <reference anonymous="1" refid="id1"> + text + <reference anonymous="1" refuri="http://external"> + external + <target anonymous="1" ids="id1"> + <target anonymous="1" ids="id2" refuri="http://external"> + """ + + default_priority = 440 + + def apply(self): + anonymous_refs = [] + anonymous_targets = [] + for node in self.document.findall(nodes.reference): + if node.get('anonymous'): + anonymous_refs.append(node) + for node in self.document.findall(nodes.target): + if node.get('anonymous'): + anonymous_targets.append(node) + if len(anonymous_refs) != len(anonymous_targets): + msg = self.document.reporter.error( + 'Anonymous hyperlink mismatch: %s references but %s ' + 'targets.\nSee "backrefs" attribute for IDs.' + % (len(anonymous_refs), len(anonymous_targets))) + msgid = self.document.set_id(msg) + for ref in anonymous_refs: + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + return + for ref, target in zip(anonymous_refs, anonymous_targets): + target.referenced = 1 + while True: + if target.hasattr('refuri'): + ref['refuri'] = target['refuri'] + ref.resolved = 1 + break + else: + if not target['ids']: + # Propagated target. + target = self.document.ids[target['refid']] + continue + ref['refid'] = target['ids'][0] + self.document.note_refid(ref) + break + + +class IndirectHyperlinks(Transform): + + """ + a) Indirect external references:: + + <paragraph> + <reference refname="indirect external"> + indirect external + <target id="id1" name="direct external" + refuri="http://indirect"> + <target id="id2" name="indirect external" + refname="direct external"> + + The "refuri" attribute is migrated back to all indirect targets + from the final direct target (i.e. a target not referring to + another indirect target):: + + <paragraph> + <reference refname="indirect external"> + indirect external + <target id="id1" name="direct external" + refuri="http://indirect"> + <target id="id2" name="indirect external" + refuri="http://indirect"> + + Once the attribute is migrated, the preexisting "refname" attribute + is dropped. + + b) Indirect internal references:: + + <target id="id1" name="final target"> + <paragraph> + <reference refname="indirect internal"> + indirect internal + <target id="id2" name="indirect internal 2" + refname="final target"> + <target id="id3" name="indirect internal" + refname="indirect internal 2"> + + Targets which indirectly refer to an internal target become one-hop + indirect (their "refid" attributes are directly set to the internal + target's "id"). References which indirectly refer to an internal + target become direct internal references:: + + <target id="id1" name="final target"> + <paragraph> + <reference refid="id1"> + indirect internal + <target id="id2" name="indirect internal 2" refid="id1"> + <target id="id3" name="indirect internal" refid="id1"> + """ + + default_priority = 460 + + def apply(self): + for target in self.document.indirect_targets: + if not target.resolved: + self.resolve_indirect_target(target) + self.resolve_indirect_references(target) + + def resolve_indirect_target(self, target): + refname = target.get('refname') + if refname is None: + reftarget_id = target['refid'] + else: + reftarget_id = self.document.nameids.get(refname) + if not reftarget_id: + # Check the unknown_reference_resolvers + for resolver_function in \ + self.document.transformer.unknown_reference_resolvers: + if resolver_function(target): + break + else: + self.nonexistent_indirect_target(target) + return + reftarget = self.document.ids[reftarget_id] + reftarget.note_referenced_by(id=reftarget_id) + if (isinstance(reftarget, nodes.target) + and not reftarget.resolved + and reftarget.hasattr('refname')): + if hasattr(target, 'multiply_indirect'): + self.circular_indirect_reference(target) + return + target.multiply_indirect = 1 + self.resolve_indirect_target(reftarget) # multiply indirect + del target.multiply_indirect + if reftarget.hasattr('refuri'): + target['refuri'] = reftarget['refuri'] + if 'refid' in target: + del target['refid'] + elif reftarget.hasattr('refid'): + target['refid'] = reftarget['refid'] + self.document.note_refid(target) + else: + if reftarget['ids']: + target['refid'] = reftarget_id + self.document.note_refid(target) + else: + self.nonexistent_indirect_target(target) + return + if refname is not None: + del target['refname'] + target.resolved = 1 + + def nonexistent_indirect_target(self, target): + if target['refname'] in self.document.nameids: + self.indirect_target_error(target, 'which is a duplicate, and ' + 'cannot be used as a unique reference') + else: + self.indirect_target_error(target, 'which does not exist') + + def circular_indirect_reference(self, target): + self.indirect_target_error(target, 'forming a circular reference') + + def indirect_target_error(self, target, explanation): + naming = '' + reflist = [] + if target['names']: + naming = '"%s" ' % target['names'][0] + for name in target['names']: + reflist.extend(self.document.refnames.get(name, [])) + for id in target['ids']: + reflist.extend(self.document.refids.get(id, [])) + if target['ids']: + naming += '(id="%s")' % target['ids'][0] + msg = self.document.reporter.error( + 'Indirect hyperlink target %s refers to target "%s", %s.' + % (naming, target['refname'], explanation), base_node=target) + msgid = self.document.set_id(msg) + for ref in utils.uniq(reflist): + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + target.resolved = 1 + + def resolve_indirect_references(self, target): + if target.hasattr('refid'): + attname = 'refid' + call_method = self.document.note_refid + elif target.hasattr('refuri'): + attname = 'refuri' + call_method = None + else: + return + attval = target[attname] + for name in target['names']: + reflist = self.document.refnames.get(name, []) + if reflist: + target.note_referenced_by(name=name) + for ref in reflist: + if ref.resolved: + continue + del ref['refname'] + ref[attname] = attval + if call_method: + call_method(ref) + ref.resolved = 1 + if isinstance(ref, nodes.target): + self.resolve_indirect_references(ref) + for id in target['ids']: + reflist = self.document.refids.get(id, []) + if reflist: + target.note_referenced_by(id=id) + for ref in reflist: + if ref.resolved: + continue + del ref['refid'] + ref[attname] = attval + if call_method: + call_method(ref) + ref.resolved = 1 + if isinstance(ref, nodes.target): + self.resolve_indirect_references(ref) + + +class ExternalTargets(Transform): + + """ + Given:: + + <paragraph> + <reference refname="direct external"> + direct external + <target id="id1" name="direct external" refuri="http://direct"> + + The "refname" attribute is replaced by the direct "refuri" attribute:: + + <paragraph> + <reference refuri="http://direct"> + direct external + <target id="id1" name="direct external" refuri="http://direct"> + """ + + default_priority = 640 + + def apply(self): + for target in self.document.findall(nodes.target): + if target.hasattr('refuri'): + refuri = target['refuri'] + for name in target['names']: + reflist = self.document.refnames.get(name, []) + if reflist: + target.note_referenced_by(name=name) + for ref in reflist: + if ref.resolved: + continue + del ref['refname'] + ref['refuri'] = refuri + ref.resolved = 1 + + +class InternalTargets(Transform): + + default_priority = 660 + + def apply(self): + for target in self.document.findall(nodes.target): + if not target.hasattr('refuri') and not target.hasattr('refid'): + self.resolve_reference_ids(target) + + def resolve_reference_ids(self, target): + """ + Given:: + + <paragraph> + <reference refname="direct internal"> + direct internal + <target id="id1" name="direct internal"> + + The "refname" attribute is replaced by "refid" linking to the target's + "id":: + + <paragraph> + <reference refid="id1"> + direct internal + <target id="id1" name="direct internal"> + """ + for name in target['names']: + refid = self.document.nameids.get(name) + reflist = self.document.refnames.get(name, []) + if reflist: + target.note_referenced_by(name=name) + for ref in reflist: + if ref.resolved: + continue + if refid: + del ref['refname'] + ref['refid'] = refid + ref.resolved = 1 + + +class Footnotes(Transform): + + """ + Assign numbers to autonumbered footnotes, and resolve links to footnotes, + citations, and their references. + + Given the following ``document`` as input:: + + <document> + <paragraph> + A labeled autonumbered footnote reference: + <footnote_reference auto="1" id="id1" refname="footnote"> + <paragraph> + An unlabeled autonumbered footnote reference: + <footnote_reference auto="1" id="id2"> + <footnote auto="1" id="id3"> + <paragraph> + Unlabeled autonumbered footnote. + <footnote auto="1" id="footnote" name="footnote"> + <paragraph> + Labeled autonumbered footnote. + + Auto-numbered footnotes have attribute ``auto="1"`` and no label. + Auto-numbered footnote_references have no reference text (they're + empty elements). When resolving the numbering, a ``label`` element + is added to the beginning of the ``footnote``, and reference text + to the ``footnote_reference``. + + The transformed result will be:: + + <document> + <paragraph> + A labeled autonumbered footnote reference: + <footnote_reference auto="1" id="id1" refid="footnote"> + 2 + <paragraph> + An unlabeled autonumbered footnote reference: + <footnote_reference auto="1" id="id2" refid="id3"> + 1 + <footnote auto="1" id="id3" backrefs="id2"> + <label> + 1 + <paragraph> + Unlabeled autonumbered footnote. + <footnote auto="1" id="footnote" name="footnote" backrefs="id1"> + <label> + 2 + <paragraph> + Labeled autonumbered footnote. + + Note that the footnotes are not in the same order as the references. + + The labels and reference text are added to the auto-numbered ``footnote`` + and ``footnote_reference`` elements. Footnote elements are backlinked to + their references via "refids" attributes. References are assigned "id" + and "refid" attributes. + + After adding labels and reference text, the "auto" attributes can be + ignored. + """ + + default_priority = 620 + + autofootnote_labels = None + """Keep track of unlabeled autonumbered footnotes.""" + + symbols = [ + # Entries 1-4 and 6 below are from section 12.51 of + # The Chicago Manual of Style, 14th edition. + '*', # asterisk/star + '\u2020', # † † dagger + '\u2021', # ‡ ‡ double dagger + '\u00A7', # § § section mark + '\u00B6', # ¶ ¶ paragraph mark (pilcrow) + # (parallels ['||'] in CMoS) + '#', # number sign + # The entries below were chosen arbitrarily. + '\u2660', # ♠ ♠ spade suit + '\u2665', # ♡ ♥ heart suit + '\u2666', # ♢ ♦ diamond suit + '\u2663', # ♣ ♣ club suit + ] + + def apply(self): + self.autofootnote_labels = [] + startnum = self.document.autofootnote_start + self.document.autofootnote_start = self.number_footnotes(startnum) + self.number_footnote_references(startnum) + self.symbolize_footnotes() + self.resolve_footnotes_and_citations() + + def number_footnotes(self, startnum): + """ + Assign numbers to autonumbered footnotes. + + For labeled autonumbered footnotes, copy the number over to + corresponding footnote references. + """ + for footnote in self.document.autofootnotes: + while True: + label = str(startnum) + startnum += 1 + if label not in self.document.nameids: + break + footnote.insert(0, nodes.label('', label)) + for name in footnote['names']: + for ref in self.document.footnote_refs.get(name, []): + ref += nodes.Text(label) + ref.delattr('refname') + assert len(footnote['ids']) == len(ref['ids']) == 1 + ref['refid'] = footnote['ids'][0] + footnote.add_backref(ref['ids'][0]) + self.document.note_refid(ref) + ref.resolved = 1 + if not footnote['names'] and not footnote['dupnames']: + footnote['names'].append(label) + self.document.note_explicit_target(footnote, footnote) + self.autofootnote_labels.append(label) + return startnum + + def number_footnote_references(self, startnum): + """Assign numbers to autonumbered footnote references.""" + i = 0 + for ref in self.document.autofootnote_refs: + if ref.resolved or ref.hasattr('refid'): + continue + try: + label = self.autofootnote_labels[i] + except IndexError: + msg = self.document.reporter.error( + 'Too many autonumbered footnote references: only %s ' + 'corresponding footnotes available.' + % len(self.autofootnote_labels), base_node=ref) + msgid = self.document.set_id(msg) + for ref in self.document.autofootnote_refs[i:]: + if ref.resolved or ref.hasattr('refname'): + continue + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + break + ref += nodes.Text(label) + id = self.document.nameids[label] + footnote = self.document.ids[id] + ref['refid'] = id + self.document.note_refid(ref) + assert len(ref['ids']) == 1 + footnote.add_backref(ref['ids'][0]) + ref.resolved = 1 + i += 1 + + def symbolize_footnotes(self): + """Add symbols indexes to "[*]"-style footnotes and references.""" + labels = [] + for footnote in self.document.symbol_footnotes: + reps, index = divmod(self.document.symbol_footnote_start, + len(self.symbols)) + labeltext = self.symbols[index] * (reps + 1) + labels.append(labeltext) + footnote.insert(0, nodes.label('', labeltext)) + self.document.symbol_footnote_start += 1 + self.document.set_id(footnote) + i = 0 + for ref in self.document.symbol_footnote_refs: + try: + ref += nodes.Text(labels[i]) + except IndexError: + msg = self.document.reporter.error( + 'Too many symbol footnote references: only %s ' + 'corresponding footnotes available.' % len(labels), + base_node=ref) + msgid = self.document.set_id(msg) + for ref in self.document.symbol_footnote_refs[i:]: + if ref.resolved or ref.hasattr('refid'): + continue + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + break + footnote = self.document.symbol_footnotes[i] + assert len(footnote['ids']) == 1 + ref['refid'] = footnote['ids'][0] + self.document.note_refid(ref) + footnote.add_backref(ref['ids'][0]) + i += 1 + + def resolve_footnotes_and_citations(self): + """ + Link manually-labeled footnotes and citations to/from their + references. + """ + for footnote in self.document.footnotes: + for label in footnote['names']: + if label in self.document.footnote_refs: + reflist = self.document.footnote_refs[label] + self.resolve_references(footnote, reflist) + for citation in self.document.citations: + for label in citation['names']: + if label in self.document.citation_refs: + reflist = self.document.citation_refs[label] + self.resolve_references(citation, reflist) + + def resolve_references(self, note, reflist): + assert len(note['ids']) == 1 + id = note['ids'][0] + for ref in reflist: + if ref.resolved: + continue + ref.delattr('refname') + ref['refid'] = id + assert len(ref['ids']) == 1 + note.add_backref(ref['ids'][0]) + ref.resolved = 1 + note.resolved = 1 + + +class CircularSubstitutionDefinitionError(Exception): + pass + + +class Substitutions(Transform): + + """ + Given the following ``document`` as input:: + + <document> + <paragraph> + The + <substitution_reference refname="biohazard"> + biohazard + symbol is deservedly scary-looking. + <substitution_definition name="biohazard"> + <image alt="biohazard" uri="biohazard.png"> + + The ``substitution_reference`` will simply be replaced by the + contents of the corresponding ``substitution_definition``. + + The transformed result will be:: + + <document> + <paragraph> + The + <image alt="biohazard" uri="biohazard.png"> + symbol is deservedly scary-looking. + <substitution_definition name="biohazard"> + <image alt="biohazard" uri="biohazard.png"> + """ + + default_priority = 220 + """The Substitutions transform has to be applied very early, before + `docutils.transforms.frontmatter.DocTitle` and others.""" + + def apply(self): + defs = self.document.substitution_defs + normed = self.document.substitution_names + nested = {} + line_length_limit = getattr(self.document.settings, + "line_length_limit", 10000) + + subreflist = list(self.document.findall(nodes.substitution_reference)) + for ref in subreflist: + msg = '' + refname = ref['refname'] + if refname in defs: + key = refname + else: + normed_name = refname.lower() + key = normed.get(normed_name, None) + if key is None: + msg = self.document.reporter.error( + 'Undefined substitution referenced: "%s".' + % refname, base_node=ref) + else: + subdef = defs[key] + if len(subdef.astext()) > line_length_limit: + msg = self.document.reporter.error( + 'Substitution definition "%s" exceeds the' + ' line-length-limit.' % key) + if msg: + msgid = self.document.set_id(msg) + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + continue + + parent = ref.parent + index = parent.index(ref) + if ('ltrim' in subdef.attributes + or 'trim' in subdef.attributes): + if index > 0 and isinstance(parent[index - 1], + nodes.Text): + parent[index - 1] = parent[index - 1].rstrip() + if ('rtrim' in subdef.attributes + or 'trim' in subdef.attributes): + if (len(parent) > index + 1 + and isinstance(parent[index + 1], nodes.Text)): + parent[index + 1] = parent[index + 1].lstrip() + subdef_copy = subdef.deepcopy() + try: + # Take care of nested substitution references: + for nested_ref in subdef_copy.findall( + nodes.substitution_reference): + nested_name = normed[nested_ref['refname'].lower()] + if nested_name in nested.setdefault(nested_name, []): + raise CircularSubstitutionDefinitionError + nested[nested_name].append(key) + nested_ref['ref-origin'] = ref + subreflist.append(nested_ref) + except CircularSubstitutionDefinitionError: + parent = ref.parent + if isinstance(parent, nodes.substitution_definition): + msg = self.document.reporter.error( + 'Circular substitution definition detected:', + nodes.literal_block(parent.rawsource, + parent.rawsource), + line=parent.line, base_node=parent) + parent.replace_self(msg) + else: + # find original ref substitution which caused this error + ref_origin = ref + while ref_origin.hasattr('ref-origin'): + ref_origin = ref_origin['ref-origin'] + msg = self.document.reporter.error( + 'Circular substitution definition referenced: ' + '"%s".' % refname, base_node=ref_origin) + msgid = self.document.set_id(msg) + prb = nodes.problematic( + ref.rawsource, ref.rawsource, refid=msgid) + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + ref.replace_self(prb) + continue + ref.replace_self(subdef_copy.children) + # register refname of the replacement node(s) + # (needed for resolution of references) + for node in subdef_copy.children: + if isinstance(node, nodes.Referential): + # HACK: verify refname attribute exists. + # Test with docs/dev/todo.txt, see. |donate| + if 'refname' in node: + self.document.note_refname(node) + + +class TargetNotes(Transform): + + """ + Creates a footnote for each external target in the text, and corresponding + footnote references after each reference. + """ + + default_priority = 540 + """The TargetNotes transform has to be applied after `IndirectHyperlinks` + but before `Footnotes`.""" + + def __init__(self, document, startnode): + Transform.__init__(self, document, startnode=startnode) + + self.classes = startnode.details.get('class', []) + + def apply(self): + notes = {} + nodelist = [] + for target in self.document.findall(nodes.target): + # Only external targets. + if not target.hasattr('refuri'): + continue + names = target['names'] + refs = [] + for name in names: + refs.extend(self.document.refnames.get(name, [])) + if not refs: + continue + footnote = self.make_target_footnote(target['refuri'], refs, + notes) + if target['refuri'] not in notes: + notes[target['refuri']] = footnote + nodelist.append(footnote) + # Take care of anonymous references. + for ref in self.document.findall(nodes.reference): + if not ref.get('anonymous'): + continue + if ref.hasattr('refuri'): + footnote = self.make_target_footnote(ref['refuri'], [ref], + notes) + if ref['refuri'] not in notes: + notes[ref['refuri']] = footnote + nodelist.append(footnote) + self.startnode.replace_self(nodelist) + + def make_target_footnote(self, refuri, refs, notes): + if refuri in notes: # duplicate? + footnote = notes[refuri] + assert len(footnote['names']) == 1 + footnote_name = footnote['names'][0] + else: # original + footnote = nodes.footnote() + footnote_id = self.document.set_id(footnote) + # Use uppercase letters and a colon; they can't be + # produced inside names by the parser. + footnote_name = 'TARGET_NOTE: ' + footnote_id + footnote['auto'] = 1 + footnote['names'] = [footnote_name] + footnote_paragraph = nodes.paragraph() + footnote_paragraph += nodes.reference('', refuri, refuri=refuri) + footnote += footnote_paragraph + self.document.note_autofootnote(footnote) + self.document.note_explicit_target(footnote, footnote) + for ref in refs: + if isinstance(ref, nodes.target): + continue + refnode = nodes.footnote_reference(refname=footnote_name, auto=1) + refnode['classes'] += self.classes + self.document.note_autofootnote_ref(refnode) + self.document.note_footnote_ref(refnode) + index = ref.parent.index(ref) + 1 + reflist = [refnode] + if not utils.get_trim_footnote_ref_space(self.document.settings): + if self.classes: + reflist.insert( + 0, nodes.inline(text=' ', Classes=self.classes)) + else: + reflist.insert(0, nodes.Text(' ')) + ref.parent.insert(index, reflist) + return footnote + + +class DanglingReferences(Transform): + + """ + Check for dangling references (incl. footnote & citation) and for + unreferenced targets. + """ + + default_priority = 850 + + def apply(self): + visitor = DanglingReferencesVisitor( + self.document, + self.document.transformer.unknown_reference_resolvers) + self.document.walk(visitor) + # *After* resolving all references, check for unreferenced + # targets: + for target in self.document.findall(nodes.target): + if not target.referenced: + if target.get('anonymous'): + # If we have unreferenced anonymous targets, there + # is already an error message about anonymous + # hyperlink mismatch; no need to generate another + # message. + continue + if target['names']: + naming = target['names'][0] + elif target['ids']: + naming = target['ids'][0] + else: + # Hack: Propagated targets always have their refid + # attribute set. + naming = target['refid'] + self.document.reporter.info( + 'Hyperlink target "%s" is not referenced.' + % naming, base_node=target) + + +class DanglingReferencesVisitor(nodes.SparseNodeVisitor): + + def __init__(self, document, unknown_reference_resolvers): + nodes.SparseNodeVisitor.__init__(self, document) + self.document = document + self.unknown_reference_resolvers = unknown_reference_resolvers + + def unknown_visit(self, node): + pass + + def visit_reference(self, node): + if node.resolved or not node.hasattr('refname'): + return + refname = node['refname'] + id = self.document.nameids.get(refname) + if id is None: + for resolver_function in self.unknown_reference_resolvers: + if resolver_function(node): + break + else: + if (getattr(self.document.settings, 'use_bibtex', False) + and isinstance(node, nodes.citation_reference)): + # targets added from BibTeX database by LaTeX + node.resolved = True + return + if refname in self.document.nameids: + msg = self.document.reporter.error( + 'Duplicate target name, cannot be used as a unique ' + 'reference: "%s".' % (node['refname']), base_node=node) + else: + msg = self.document.reporter.error( + f'Unknown target name: "{node["refname"]}".', + base_node=node) + msgid = self.document.set_id(msg) + prb = nodes.problematic( + node.rawsource, node.rawsource, refid=msgid) + try: + prbid = node['ids'][0] + except IndexError: + prbid = self.document.set_id(prb) + msg.add_backref(prbid) + node.replace_self(prb) + else: + del node['refname'] + node['refid'] = id + self.document.ids[id].note_referenced_by(id=id) + node.resolved = True + + visit_footnote_reference = visit_citation_reference = visit_reference diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/universal.py b/.venv/lib/python3.12/site-packages/docutils/transforms/universal.py new file mode 100644 index 00000000..00d57b4f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/universal.py @@ -0,0 +1,335 @@ +# $Id: universal.py 9502 2023-12-14 22:39:08Z milde $ +# Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Günter Milde +# Maintainer: docutils-develop@lists.sourceforge.net +# Copyright: This module has been placed in the public domain. + +""" +Transforms needed by most or all documents: + +- `Decorations`: Generate a document's header & footer. +- `ExposeInternals`: Expose internal attributes. +- `Messages`: Placement of system messages generated after parsing. +- `FilterMessages`: Remove system messages below verbosity threshold. +- `TestMessages`: Like `Messages`, used on test runs. +- `StripComments`: Remove comment elements from the document tree. +- `StripClassesAndElements`: Remove elements with classes + in `self.document.settings.strip_elements_with_classes` + and class values in `self.document.settings.strip_classes`. +- `SmartQuotes`: Replace ASCII quotation marks with typographic form. +""" + +__docformat__ = 'reStructuredText' + +import re +import time +from docutils import nodes, utils +from docutils.transforms import Transform +from docutils.utils import smartquotes + + +class Decorations(Transform): + + """ + Populate a document's decoration element (header, footer). + """ + + default_priority = 820 + + def apply(self): + header_nodes = self.generate_header() + if header_nodes: + decoration = self.document.get_decoration() + header = decoration.get_header() + header.extend(header_nodes) + footer_nodes = self.generate_footer() + if footer_nodes: + decoration = self.document.get_decoration() + footer = decoration.get_footer() + footer.extend(footer_nodes) + + def generate_header(self): + return None + + def generate_footer(self): + # @@@ Text is hard-coded for now. + # Should be made dynamic (language-dependent). + # @@@ Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable + # for the datestamp? + # See https://sourceforge.net/p/docutils/patches/132/ + # and https://reproducible-builds.org/specs/source-date-epoch/ + settings = self.document.settings + if (settings.generator or settings.datestamp + or settings.source_link or settings.source_url): + text = [] + if (settings.source_link and settings._source + or settings.source_url): + if settings.source_url: + source = settings.source_url + else: + source = utils.relative_path(settings._destination, + settings._source) + text.extend([ + nodes.reference('', 'View document source', + refuri=source), + nodes.Text('.\n')]) + if settings.datestamp: + datestamp = time.strftime(settings.datestamp, time.gmtime()) + text.append(nodes.Text('Generated on: ' + datestamp + '.\n')) + if settings.generator: + text.extend([ + nodes.Text('Generated by '), + nodes.reference('', 'Docutils', + refuri='https://docutils.sourceforge.io/'), + nodes.Text(' from '), + nodes.reference('', 'reStructuredText', + refuri='https://docutils.sourceforge.io/' + 'rst.html'), + nodes.Text(' source.\n')]) + return [nodes.paragraph('', '', *text)] + else: + return None + + +class ExposeInternals(Transform): + + """ + Expose internal attributes if ``expose_internals`` setting is set. + """ + + default_priority = 840 + + def not_Text(self, node): + return not isinstance(node, nodes.Text) + + def apply(self): + if self.document.settings.expose_internals: + for node in self.document.findall(self.not_Text): + for att in self.document.settings.expose_internals: + value = getattr(node, att, None) + if value is not None: + node['internal:' + att] = value + + +class Messages(Transform): + + """ + Place any system messages generated after parsing into a dedicated section + of the document. + """ + + default_priority = 860 + + def apply(self): + messages = self.document.transform_messages + loose_messages = [msg for msg in messages if not msg.parent] + if loose_messages: + section = nodes.section(classes=['system-messages']) + # @@@ get this from the language module? + section += nodes.title('', 'Docutils System Messages') + section += loose_messages + self.document.transform_messages[:] = [] + self.document += section + + +class FilterMessages(Transform): + + """ + Remove system messages below verbosity threshold. + + Also convert <problematic> nodes referencing removed messages + to <Text> nodes and remove "System Messages" section if empty. + """ + + default_priority = 870 + + def apply(self): + for node in tuple(self.document.findall(nodes.system_message)): + if node['level'] < self.document.reporter.report_level: + node.parent.remove(node) + try: # also remove id-entry + del self.document.ids[node['ids'][0]] + except (IndexError): + pass + for node in tuple(self.document.findall(nodes.problematic)): + if node['refid'] not in self.document.ids: + node.parent.replace(node, nodes.Text(node.astext())) + for node in self.document.findall(nodes.section): + if "system-messages" in node['classes'] and len(node) == 1: + node.parent.remove(node) + + +class TestMessages(Transform): + + """ + Append all post-parse system messages to the end of the document. + + Used for testing purposes. + """ + + # marker for pytest to ignore this class during test discovery + __test__ = False + + default_priority = 880 + + def apply(self): + for msg in self.document.transform_messages: + if not msg.parent: + self.document += msg + + +class StripComments(Transform): + + """ + Remove comment elements from the document tree (only if the + ``strip_comments`` setting is enabled). + """ + + default_priority = 740 + + def apply(self): + if self.document.settings.strip_comments: + for node in tuple(self.document.findall(nodes.comment)): + node.parent.remove(node) + + +class StripClassesAndElements(Transform): + + """ + Remove from the document tree all elements with classes in + `self.document.settings.strip_elements_with_classes` and all "classes" + attribute values in `self.document.settings.strip_classes`. + """ + + default_priority = 420 + + def apply(self): + if self.document.settings.strip_elements_with_classes: + self.strip_elements = {*self.document.settings + .strip_elements_with_classes} + # Iterate over a tuple as removing the current node + # corrupts the iterator returned by `iter`: + for node in tuple(self.document.findall(self.check_classes)): + node.parent.remove(node) + + if not self.document.settings.strip_classes: + return + strip_classes = self.document.settings.strip_classes + for node in self.document.findall(nodes.Element): + for class_value in strip_classes: + try: + node['classes'].remove(class_value) + except ValueError: + pass + + def check_classes(self, node): + if not isinstance(node, nodes.Element): + return False + for class_value in node['classes'][:]: + if class_value in self.strip_elements: + return True + return False + + +class SmartQuotes(Transform): + + """ + Replace ASCII quotation marks with typographic form. + + Also replace multiple dashes with em-dash/en-dash characters. + """ + + default_priority = 855 + + nodes_to_skip = (nodes.FixedTextElement, nodes.Special) + """Do not apply "smartquotes" to instances of these block-level nodes.""" + + literal_nodes = (nodes.FixedTextElement, nodes.Special, + nodes.image, nodes.literal, nodes.math, + nodes.raw, nodes.problematic) + """Do not apply smartquotes to instances of these inline nodes.""" + + smartquotes_action = 'qDe' + """Setting to select smartquote transformations. + + The default 'qDe' educates normal quote characters: (", '), + em- and en-dashes (---, --) and ellipses (...). + """ + + def __init__(self, document, startnode): + Transform.__init__(self, document, startnode=startnode) + self.unsupported_languages = set() + + def get_tokens(self, txtnodes): + # A generator that yields ``(texttype, nodetext)`` tuples for a list + # of "Text" nodes (interface to ``smartquotes.educate_tokens()``). + for node in txtnodes: + if (isinstance(node.parent, self.literal_nodes) + or isinstance(node.parent.parent, self.literal_nodes)): + yield 'literal', str(node) + else: + # SmartQuotes uses backslash escapes instead of null-escapes + # Insert backslashes before escaped "active" characters. + txt = re.sub('(?<=\x00)([-\\\'".`])', r'\\\1', str(node)) + yield 'plain', txt + + def apply(self): + smart_quotes = self.document.settings.setdefault('smart_quotes', + False) + if not smart_quotes: + return + try: + alternative = smart_quotes.startswith('alt') + except AttributeError: + alternative = False + + document_language = self.document.settings.language_code + lc_smartquotes = self.document.settings.smartquotes_locales + if lc_smartquotes: + smartquotes.smartchars.quotes.update(dict(lc_smartquotes)) + + # "Educate" quotes in normal text. Handle each block of text + # (TextElement node) as a unit to keep context around inline nodes: + for node in self.document.findall(nodes.TextElement): + # skip preformatted text blocks and special elements: + if isinstance(node, self.nodes_to_skip): + continue + # nested TextElements are not "block-level" elements: + if isinstance(node.parent, nodes.TextElement): + continue + + # list of text nodes in the "text block": + txtnodes = [txtnode for txtnode in node.findall(nodes.Text) + if not isinstance(txtnode.parent, + nodes.option_string)] + + # language: use typographical quotes for language "lang" + lang = node.get_language_code(document_language) + # use alternative form if `smart-quotes` setting starts with "alt": + if alternative: + if '-x-altquot' in lang: + lang = lang.replace('-x-altquot', '') + else: + lang += '-x-altquot' + # drop unsupported subtags: + for tag in utils.normalize_language_tag(lang): + if tag in smartquotes.smartchars.quotes: + lang = tag + break + else: # language not supported -- keep ASCII quotes + if lang not in self.unsupported_languages: + self.document.reporter.warning( + 'No smart quotes defined for language "%s".' % lang, + base_node=node) + self.unsupported_languages.add(lang) + lang = '' + + # Iterator educating quotes in plain text: + # (see "utils/smartquotes.py" for the attribute setting) + teacher = smartquotes.educate_tokens( + self.get_tokens(txtnodes), + attr=self.smartquotes_action, language=lang) + + for txtnode, newtext in zip(txtnodes, teacher): + txtnode.parent.replace(txtnode, nodes.Text(newtext)) + + self.unsupported_languages.clear() diff --git a/.venv/lib/python3.12/site-packages/docutils/transforms/writer_aux.py b/.venv/lib/python3.12/site-packages/docutils/transforms/writer_aux.py new file mode 100644 index 00000000..8f303be3 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/transforms/writer_aux.py @@ -0,0 +1,99 @@ +# $Id: writer_aux.py 9037 2022-03-05 23:31:10Z milde $ +# Author: Lea Wiemann <LeWiemann@gmail.com> +# Copyright: This module has been placed in the public domain. + +""" +Auxiliary transforms mainly to be used by Writer components. + +This module is called "writer_aux" because otherwise there would be +conflicting imports like this one:: + + from docutils import writers + from docutils.transforms import writers +""" + +__docformat__ = 'reStructuredText' + +import warnings + +from docutils import nodes, languages +from docutils.transforms import Transform + + +class Compound(Transform): + + """ + .. warning:: This transform is not used by Docutils since Dec 2010 + and will be removed in Docutils 0.21 or later. + + Flatten all compound paragraphs. For example, transform :: + + <compound> + <paragraph> + <literal_block> + <paragraph> + + into :: + + <paragraph> + <literal_block classes="continued"> + <paragraph classes="continued"> + """ + + default_priority = 910 + + def __init__(self, document, startnode=None): + warnings.warn('docutils.transforms.writer_aux.Compound is deprecated' + ' and will be removed in Docutils 0.21 or later.', + DeprecationWarning, stacklevel=2) + super().__init__(document, startnode) + + def apply(self): + for compound in self.document.findall(nodes.compound): + first_child = True + for child in compound: + if first_child: + if not isinstance(child, nodes.Invisible): + first_child = False + else: + child['classes'].append('continued') + # Substitute children for compound. + compound.replace_self(compound[:]) + + +class Admonitions(Transform): + + """ + Transform specific admonitions, like this: + + <note> + <paragraph> + Note contents ... + + into generic admonitions, like this:: + + <admonition classes="note"> + <title> + Note + <paragraph> + Note contents ... + + The admonition title is localized. + """ + + default_priority = 920 + + def apply(self): + language = languages.get_language(self.document.settings.language_code, + self.document.reporter) + for node in self.document.findall(nodes.Admonition): + node_name = node.__class__.__name__ + # Set class, so that we know what node this admonition came from. + node['classes'].append(node_name) + if not isinstance(node, nodes.admonition): + # Specific admonition. Transform into a generic admonition. + admonition = nodes.admonition(node.rawsource, *node.children, + **node.attributes) + title = nodes.title('', language.labels[node_name]) + admonition.insert(0, title) + node.replace_self(admonition) diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/__init__.py b/.venv/lib/python3.12/site-packages/docutils/utils/__init__.py new file mode 100644 index 00000000..777f8013 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/__init__.py @@ -0,0 +1,861 @@ +# $Id: __init__.py 9544 2024-02-17 10:37:45Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +Miscellaneous utilities for the documentation utilities. +""" + +__docformat__ = 'reStructuredText' + +import sys +import os +import os.path +from pathlib import PurePath, Path +import re +import itertools +import warnings +import unicodedata + +from docutils import ApplicationError, DataError, __version_info__ +from docutils import io, nodes +# for backwards compatibility +from docutils.nodes import unescape # noqa: F401 + + +class SystemMessage(ApplicationError): + + def __init__(self, system_message, level): + Exception.__init__(self, system_message.astext()) + self.level = level + + +class SystemMessagePropagation(ApplicationError): + pass + + +class Reporter: + + """ + Info/warning/error reporter and ``system_message`` element generator. + + Five levels of system messages are defined, along with corresponding + methods: `debug()`, `info()`, `warning()`, `error()`, and `severe()`. + + There is typically one Reporter object per process. A Reporter object is + instantiated with thresholds for reporting (generating warnings) and + halting processing (raising exceptions), a switch to turn debug output on + or off, and an I/O stream for warnings. These are stored as instance + attributes. + + When a system message is generated, its level is compared to the stored + thresholds, and a warning or error is generated as appropriate. Debug + messages are produced if the stored debug switch is on, independently of + other thresholds. Message output is sent to the stored warning stream if + not set to ''. + + The Reporter class also employs a modified form of the "Observer" pattern + [GoF95]_ to track system messages generated. The `attach_observer` method + should be called before parsing, with a bound method or function which + accepts system messages. The observer can be removed with + `detach_observer`, and another added in its place. + + .. [GoF95] Gamma, Helm, Johnson, Vlissides. *Design Patterns: Elements of + Reusable Object-Oriented Software*. Addison-Wesley, Reading, MA, USA, + 1995. + """ + + levels = 'DEBUG INFO WARNING ERROR SEVERE'.split() + """List of names for system message levels, indexed by level.""" + + # system message level constants: + (DEBUG_LEVEL, + INFO_LEVEL, + WARNING_LEVEL, + ERROR_LEVEL, + SEVERE_LEVEL) = range(5) + + def __init__(self, source, report_level, halt_level, stream=None, + debug=False, encoding=None, error_handler='backslashreplace'): + """ + :Parameters: + - `source`: The path to or description of the source data. + - `report_level`: The level at or above which warning output will + be sent to `stream`. + - `halt_level`: The level at or above which `SystemMessage` + exceptions will be raised, halting execution. + - `debug`: Show debug (level=0) system messages? + - `stream`: Where warning output is sent. Can be file-like (has a + ``.write`` method), a string (file name, opened for writing), + '' (empty string) or `False` (for discarding all stream messages) + or `None` (implies `sys.stderr`; default). + - `encoding`: The output encoding. + - `error_handler`: The error handler for stderr output encoding. + """ + + self.source = source + """The path to or description of the source data.""" + + self.error_handler = error_handler + """The character encoding error handler.""" + + self.debug_flag = debug + """Show debug (level=0) system messages?""" + + self.report_level = report_level + """The level at or above which warning output will be sent + to `self.stream`.""" + + self.halt_level = halt_level + """The level at or above which `SystemMessage` exceptions + will be raised, halting execution.""" + + if not isinstance(stream, io.ErrorOutput): + stream = io.ErrorOutput(stream, encoding, error_handler) + + self.stream = stream + """Where warning output is sent.""" + + self.encoding = encoding or getattr(stream, 'encoding', 'ascii') + """The output character encoding.""" + + self.observers = [] + """List of bound methods or functions to call with each system_message + created.""" + + self.max_level = -1 + """The highest level system message generated so far.""" + + def set_conditions(self, category, report_level, halt_level, + stream=None, debug=False): + warnings.warn('docutils.utils.Reporter.set_conditions() deprecated; ' + 'Will be removed in Docutils 0.21 or later. ' + 'Set attributes via configuration settings or directly.', + DeprecationWarning, stacklevel=2) + self.report_level = report_level + self.halt_level = halt_level + if not isinstance(stream, io.ErrorOutput): + stream = io.ErrorOutput(stream, self.encoding, self.error_handler) + self.stream = stream + self.debug_flag = debug + + def attach_observer(self, observer): + """ + The `observer` parameter is a function or bound method which takes one + argument, a `nodes.system_message` instance. + """ + self.observers.append(observer) + + def detach_observer(self, observer): + self.observers.remove(observer) + + def notify_observers(self, message): + for observer in self.observers: + observer(message) + + def system_message(self, level, message, *children, **kwargs): + """ + Return a system_message object. + + Raise an exception or generate a warning if appropriate. + """ + # `message` can be a `str` or `Exception` instance. + if isinstance(message, Exception): + message = str(message) + + attributes = kwargs.copy() + if 'base_node' in kwargs: + source, line = get_source_line(kwargs['base_node']) + del attributes['base_node'] + if source is not None: + attributes.setdefault('source', source) + if line is not None: + attributes.setdefault('line', line) + # assert source is not None, "line- but no source-argument" + if 'source' not in attributes: + # 'line' is absolute line number + try: + source, line = self.get_source_and_line(attributes.get('line')) + except AttributeError: + source, line = None, None + if source is not None: + attributes['source'] = source + if line is not None: + attributes['line'] = line + # assert attributes['line'] is not None, (message, kwargs) + # assert attributes['source'] is not None, (message, kwargs) + attributes.setdefault('source', self.source) + + msg = nodes.system_message(message, level=level, + type=self.levels[level], + *children, **attributes) + if self.stream and (level >= self.report_level + or self.debug_flag and level == self.DEBUG_LEVEL + or level >= self.halt_level): + self.stream.write(msg.astext() + '\n') + if level >= self.halt_level: + raise SystemMessage(msg, level) + if level > self.DEBUG_LEVEL or self.debug_flag: + self.notify_observers(msg) + self.max_level = max(level, self.max_level) + return msg + + def debug(self, *args, **kwargs): + """ + Level-0, "DEBUG": an internal reporting issue. Typically, there is no + effect on the processing. Level-0 system messages are handled + separately from the others. + """ + if self.debug_flag: + return self.system_message(self.DEBUG_LEVEL, *args, **kwargs) + + def info(self, *args, **kwargs): + """ + Level-1, "INFO": a minor issue that can be ignored. Typically there is + no effect on processing, and level-1 system messages are not reported. + """ + return self.system_message(self.INFO_LEVEL, *args, **kwargs) + + def warning(self, *args, **kwargs): + """ + Level-2, "WARNING": an issue that should be addressed. If ignored, + there may be unpredictable problems with the output. + """ + return self.system_message(self.WARNING_LEVEL, *args, **kwargs) + + def error(self, *args, **kwargs): + """ + Level-3, "ERROR": an error that should be addressed. If ignored, the + output will contain errors. + """ + return self.system_message(self.ERROR_LEVEL, *args, **kwargs) + + def severe(self, *args, **kwargs): + """ + Level-4, "SEVERE": a severe error that must be addressed. If ignored, + the output will contain severe errors. Typically level-4 system + messages are turned into exceptions which halt processing. + """ + return self.system_message(self.SEVERE_LEVEL, *args, **kwargs) + + +class ExtensionOptionError(DataError): pass +class BadOptionError(ExtensionOptionError): pass +class BadOptionDataError(ExtensionOptionError): pass +class DuplicateOptionError(ExtensionOptionError): pass + + +def extract_extension_options(field_list, options_spec): + """ + Return a dictionary mapping extension option names to converted values. + + :Parameters: + - `field_list`: A flat field list without field arguments, where each + field body consists of a single paragraph only. + - `options_spec`: Dictionary mapping known option names to a + conversion function such as `int` or `float`. + + :Exceptions: + - `KeyError` for unknown option names. + - `ValueError` for invalid option values (raised by the conversion + function). + - `TypeError` for invalid option value types (raised by conversion + function). + - `DuplicateOptionError` for duplicate options. + - `BadOptionError` for invalid fields. + - `BadOptionDataError` for invalid option data (missing name, + missing data, bad quotes, etc.). + """ + option_list = extract_options(field_list) + return assemble_option_dict(option_list, options_spec) + + +def extract_options(field_list): + """ + Return a list of option (name, value) pairs from field names & bodies. + + :Parameter: + `field_list`: A flat field list, where each field name is a single + word and each field body consists of a single paragraph only. + + :Exceptions: + - `BadOptionError` for invalid fields. + - `BadOptionDataError` for invalid option data (missing name, + missing data, bad quotes, etc.). + """ + option_list = [] + for field in field_list: + if len(field[0].astext().split()) != 1: + raise BadOptionError( + 'extension option field name may not contain multiple words') + name = str(field[0].astext().lower()) + body = field[1] + if len(body) == 0: + data = None + elif (len(body) > 1 + or not isinstance(body[0], nodes.paragraph) + or len(body[0]) != 1 + or not isinstance(body[0][0], nodes.Text)): + raise BadOptionDataError( + 'extension option field body may contain\n' + 'a single paragraph only (option "%s")' % name) + else: + data = body[0][0].astext() + option_list.append((name, data)) + return option_list + + +def assemble_option_dict(option_list, options_spec): + """ + Return a mapping of option names to values. + + :Parameters: + - `option_list`: A list of (name, value) pairs (the output of + `extract_options()`). + - `options_spec`: Dictionary mapping known option names to a + conversion function such as `int` or `float`. + + :Exceptions: + - `KeyError` for unknown option names. + - `DuplicateOptionError` for duplicate options. + - `ValueError` for invalid option values (raised by conversion + function). + - `TypeError` for invalid option value types (raised by conversion + function). + """ + options = {} + for name, value in option_list: + convertor = options_spec[name] # raises KeyError if unknown + if convertor is None: + raise KeyError(name) # or if explicitly disabled + if name in options: + raise DuplicateOptionError('duplicate option "%s"' % name) + try: + options[name] = convertor(value) + except (ValueError, TypeError) as detail: + raise detail.__class__('(option: "%s"; value: %r)\n%s' + % (name, value, ' '.join(detail.args))) + return options + + +class NameValueError(DataError): pass + + +def decode_path(path): + """ + Ensure `path` is Unicode. Return `str` instance. + + Decode file/path string in a failsafe manner if not already done. + """ + # TODO: is this still required with Python 3? + if isinstance(path, str): + return path + try: + path = path.decode(sys.getfilesystemencoding(), 'strict') + except AttributeError: # default value None has no decode method + if not path: + return '' + raise ValueError('`path` value must be a String or ``None``, ' + f'not {path!r}') + except UnicodeDecodeError: + try: + path = path.decode('utf-8', 'strict') + except UnicodeDecodeError: + path = path.decode('ascii', 'replace') + return path + + +def extract_name_value(line): + """ + Return a list of (name, value) from a line of the form "name=value ...". + + :Exception: + `NameValueError` for invalid input (missing name, missing data, bad + quotes, etc.). + """ + attlist = [] + while line: + equals = line.find('=') + if equals == -1: + raise NameValueError('missing "="') + attname = line[:equals].strip() + if equals == 0 or not attname: + raise NameValueError( + 'missing attribute name before "="') + line = line[equals+1:].lstrip() + if not line: + raise NameValueError( + 'missing value after "%s="' % attname) + if line[0] in '\'"': + endquote = line.find(line[0], 1) + if endquote == -1: + raise NameValueError( + 'attribute "%s" missing end quote (%s)' + % (attname, line[0])) + if len(line) > endquote + 1 and line[endquote + 1].strip(): + raise NameValueError( + 'attribute "%s" end quote (%s) not followed by ' + 'whitespace' % (attname, line[0])) + data = line[1:endquote] + line = line[endquote+1:].lstrip() + else: + space = line.find(' ') + if space == -1: + data = line + line = '' + else: + data = line[:space] + line = line[space+1:].lstrip() + attlist.append((attname.lower(), data)) + return attlist + + +def new_reporter(source_path, settings): + """ + Return a new Reporter object. + + :Parameters: + `source` : string + The path to or description of the source text of the document. + `settings` : optparse.Values object + Runtime settings. + """ + reporter = Reporter( + source_path, settings.report_level, settings.halt_level, + stream=settings.warning_stream, debug=settings.debug, + encoding=settings.error_encoding, + error_handler=settings.error_encoding_error_handler) + return reporter + + +def new_document(source_path, settings=None): + """ + Return a new empty document object. + + :Parameters: + `source_path` : string + The path to or description of the source text of the document. + `settings` : optparse.Values object + Runtime settings. If none are provided, a default core set will + be used. If you will use the document object with any Docutils + components, you must provide their default settings as well. + + For example, if parsing rST, at least provide the rst-parser + settings, obtainable as follows: + + Defaults for parser component:: + + settings = docutils.frontend.get_default_settings( + docutils.parsers.rst.Parser) + + Defaults and configuration file customizations:: + + settings = docutils.core.Publisher( + parser=docutils.parsers.rst.Parser).get_settings() + + """ + # Import at top of module would lead to circular dependency! + from docutils import frontend + if settings is None: + settings = frontend.get_default_settings() + source_path = decode_path(source_path) + reporter = new_reporter(source_path, settings) + document = nodes.document(settings, reporter, source=source_path) + document.note_source(source_path, -1) + return document + + +def clean_rcs_keywords(paragraph, keyword_substitutions): + if len(paragraph) == 1 and isinstance(paragraph[0], nodes.Text): + textnode = paragraph[0] + for pattern, substitution in keyword_substitutions: + match = pattern.search(textnode) + if match: + paragraph[0] = nodes.Text(pattern.sub(substitution, textnode)) + return + + +def relative_path(source, target): + """ + Build and return a path to `target`, relative to `source` (both files). + + The return value is a `str` suitable to be included in `source` + as a reference to `target`. + + :Parameters: + `source` : path-like object or None + Path of a file in the start directory for the relative path + (the file does not need to exist). + The value ``None`` is replaced with "<cwd>/dummy_file". + `target` : path-like object + End point of the returned relative path. + + Differences to `os.path.relpath()`: + + * Inverse argument order. + * `source` is assumed to be a FILE in the start directory (add a "dummy" + file name to obtain the path relative from a directory) + while `os.path.relpath()` expects a DIRECTORY as `start` argument. + * Always use Posix path separator ("/") for the output. + * Use `os.sep` for parsing the input + (changing the value of `os.sep` is ignored by `os.relpath()`). + * If there is no common prefix, return the absolute path to `target`. + + Differences to `pathlib.PurePath.relative_to(other)`: + + * pathlib offers an object oriented interface. + * `source` expects path to a FILE while `other` expects a DIRECTORY. + * `target` defaults to the cwd, no default value for `other`. + * `relative_path()` always returns a path (relative or absolute), + while `PurePath.relative_to()` raises a ValueError + if `target` is not a subpath of `other` (no ".." inserted). + """ + source_parts = os.path.abspath(source or type(target)('dummy_file') + ).split(os.sep) + target_parts = os.path.abspath(target).split(os.sep) + # Check first 2 parts because '/dir'.split('/') == ['', 'dir']: + if source_parts[:2] != target_parts[:2]: + # Nothing in common between paths. + # Return absolute path, using '/' for URLs: + return '/'.join(target_parts) + source_parts.reverse() + target_parts.reverse() + while (source_parts and target_parts + and source_parts[-1] == target_parts[-1]): + # Remove path components in common: + source_parts.pop() + target_parts.pop() + target_parts.reverse() + parts = ['..'] * (len(source_parts) - 1) + target_parts + return '/'.join(parts) + + +def get_stylesheet_reference(settings, relative_to=None): + """ + Retrieve a stylesheet reference from the settings object. + + Deprecated. Use get_stylesheet_list() instead to + enable specification of multiple stylesheets as a comma-separated + list. + """ + warnings.warn('utils.get_stylesheet_reference()' + ' is obsoleted by utils.get_stylesheet_list()' + ' and will be removed in Docutils 2.0.', + DeprecationWarning, stacklevel=2) + if settings.stylesheet_path: + assert not settings.stylesheet, ( + 'stylesheet and stylesheet_path are mutually exclusive.') + if relative_to is None: + relative_to = settings._destination + return relative_path(relative_to, settings.stylesheet_path) + else: + return settings.stylesheet + + +# Return 'stylesheet' or 'stylesheet_path' arguments as list. +# +# The original settings arguments are kept unchanged: you can test +# with e.g. ``if settings.stylesheet_path: ...``. +# +# Differences to the depracated `get_stylesheet_reference()`: +# * return value is a list +# * no re-writing of the path (and therefore no optional argument) +# (if required, use ``utils.relative_path(source, target)`` +# in the calling script) +def get_stylesheet_list(settings): + """ + Retrieve list of stylesheet references from the settings object. + """ + assert not (settings.stylesheet and settings.stylesheet_path), ( + 'stylesheet and stylesheet_path are mutually exclusive.') + stylesheets = settings.stylesheet_path or settings.stylesheet or [] + # programmatically set default may be string with comma separated list: + if not isinstance(stylesheets, list): + stylesheets = [path.strip() for path in stylesheets.split(',')] + if settings.stylesheet_path: + # expand relative paths if found in stylesheet-dirs: + stylesheets = [find_file_in_dirs(path, settings.stylesheet_dirs) + for path in stylesheets] + return stylesheets + + +def find_file_in_dirs(path, dirs): + """ + Search for `path` in the list of directories `dirs`. + + Return the first expansion that matches an existing file. + """ + path = Path(path) + if path.is_absolute(): + return path.as_posix() + for d in dirs: + f = Path(d).expanduser() / path + if f.exists(): + return f.as_posix() + return path.as_posix() + + +def get_trim_footnote_ref_space(settings): + """ + Return whether or not to trim footnote space. + + If trim_footnote_reference_space is not None, return it. + + If trim_footnote_reference_space is None, return False unless the + footnote reference style is 'superscript'. + """ + if settings.setdefault('trim_footnote_reference_space', None) is None: + return getattr(settings, 'footnote_references', None) == 'superscript' + else: + return settings.trim_footnote_reference_space + + +def get_source_line(node): + """ + Return the "source" and "line" attributes from the `node` given or from + its closest ancestor. + """ + while node: + if node.source or node.line: + return node.source, node.line + node = node.parent + return None, None + + +def escape2null(text): + """Return a string with escape-backslashes converted to nulls.""" + parts = [] + start = 0 + while True: + found = text.find('\\', start) + if found == -1: + parts.append(text[start:]) + return ''.join(parts) + parts.append(text[start:found]) + parts.append('\x00' + text[found+1:found+2]) + start = found + 2 # skip character after escape + + +def split_escaped_whitespace(text): + """ + Split `text` on escaped whitespace (null+space or null+newline). + Return a list of strings. + """ + strings = text.split('\x00 ') + strings = [string.split('\x00\n') for string in strings] + # flatten list of lists of strings to list of strings: + return list(itertools.chain(*strings)) + + +def strip_combining_chars(text): + return ''.join(c for c in text if not unicodedata.combining(c)) + + +def find_combining_chars(text): + """Return indices of all combining chars in Unicode string `text`. + + >>> from docutils.utils import find_combining_chars + >>> find_combining_chars('A t̆ab̆lĕ') + [3, 6, 9] + + """ + return [i for i, c in enumerate(text) if unicodedata.combining(c)] + + +def column_indices(text): + """Indices of Unicode string `text` when skipping combining characters. + + >>> from docutils.utils import column_indices + >>> column_indices('A t̆ab̆lĕ') + [0, 1, 2, 4, 5, 7, 8] + + """ + # TODO: account for asian wide chars here instead of using dummy + # replacements in the tableparser? + string_indices = list(range(len(text))) + for index in find_combining_chars(text): + string_indices[index] = None + return [i for i in string_indices if i is not None] + + +east_asian_widths = {'W': 2, # Wide + 'F': 2, # Full-width (wide) + 'Na': 1, # Narrow + 'H': 1, # Half-width (narrow) + 'N': 1, # Neutral (not East Asian, treated as narrow) + 'A': 1, # Ambiguous (s/b wide in East Asian context, + } # narrow otherwise, but that doesn't work) +"""Mapping of result codes from `unicodedata.east_asian_widt()` to character +column widths.""" + + +def column_width(text): + """Return the column width of text. + + Correct ``len(text)`` for wide East Asian and combining Unicode chars. + """ + width = sum(east_asian_widths[unicodedata.east_asian_width(c)] + for c in text) + # correction for combining chars: + width -= len(find_combining_chars(text)) + return width + + +def uniq(L): + r = [] + for item in L: + if item not in r: + r.append(item) + return r + + +def normalize_language_tag(tag): + """Return a list of normalized combinations for a `BCP 47` language tag. + + Example: + + >>> from docutils.utils import normalize_language_tag + >>> normalize_language_tag('de_AT-1901') + ['de-at-1901', 'de-at', 'de-1901', 'de'] + >>> normalize_language_tag('de-CH-x_altquot') + ['de-ch-x-altquot', 'de-ch', 'de-x-altquot', 'de'] + + """ + # normalize: + tag = tag.lower().replace('-', '_') + # split (except singletons, which mark the following tag as non-standard): + tag = re.sub(r'_([a-zA-Z0-9])_', r'_\1-', tag) + subtags = [subtag for subtag in tag.split('_')] + base_tag = (subtags.pop(0),) + # find all combinations of subtags + taglist = [] + for n in range(len(subtags), 0, -1): + for tags in itertools.combinations(subtags, n): + taglist.append('-'.join(base_tag+tags)) + taglist += base_tag + return taglist + + +def xml_declaration(encoding=None): + """Return an XML text declaration. + + Include an encoding declaration, if `encoding` + is not 'unicode', '', or None. + """ + if encoding and encoding.lower() != 'unicode': + encoding_declaration = f' encoding="{encoding}"' + else: + encoding_declaration = '' + return f'<?xml version="1.0"{encoding_declaration}?>\n' + + +class DependencyList: + + """ + List of dependencies, with file recording support. + + Note that the output file is not automatically closed. You have + to explicitly call the close() method. + """ + + def __init__(self, output_file=None, dependencies=()): + """ + Initialize the dependency list, automatically setting the + output file to `output_file` (see `set_output()`) and adding + all supplied dependencies. + + If output_file is None, no file output is done when calling add(). + """ + self.list = [] + self.file = None + if output_file: + self.set_output(output_file) + self.add(*dependencies) + + def set_output(self, output_file): + """ + Set the output file and clear the list of already added + dependencies. + + `output_file` must be a string. The specified file is + immediately overwritten. + + If output_file is '-', the output will be written to stdout. + """ + if output_file: + if output_file == '-': + self.file = sys.stdout + else: + self.file = open(output_file, 'w', encoding='utf-8') + + def add(self, *paths): + """ + Append `path` to `self.list` unless it is already there. + + Also append to `self.file` unless it is already there + or `self.file is `None`. + """ + for path in paths: + if isinstance(path, PurePath): + path = path.as_posix() # use '/' as separator + if path not in self.list: + self.list.append(path) + if self.file is not None: + self.file.write(path+'\n') + + def close(self): + """ + Close the output file. + """ + if self.file is not sys.stdout: + self.file.close() + self.file = None + + def __repr__(self): + try: + output_file = self.file.name + except AttributeError: + output_file = None + return '%s(%r, %s)' % (self.__class__.__name__, output_file, self.list) + + +release_level_abbreviations = { + 'alpha': 'a', + 'beta': 'b', + 'candidate': 'rc', + 'final': ''} + + +def version_identifier(version_info=None): + """ + Return a version identifier string built from `version_info`, a + `docutils.VersionInfo` namedtuple instance or compatible tuple. If + `version_info` is not provided, by default return a version identifier + string based on `docutils.__version_info__` (i.e. the current Docutils + version). + """ + if version_info is None: + version_info = __version_info__ + if version_info.micro: + micro = '.%s' % version_info.micro + else: + # 0 is omitted: + micro = '' + releaselevel = release_level_abbreviations[version_info.releaselevel] + if version_info.serial: + serial = version_info.serial + else: + # 0 is omitted: + serial = '' + if version_info.release: + dev = '' + else: + dev = '.dev' + version = '%s.%s%s%s%s%s' % ( + version_info.major, + version_info.minor, + micro, + releaselevel, + serial, + dev) + return version diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/code_analyzer.py b/.venv/lib/python3.12/site-packages/docutils/utils/code_analyzer.py new file mode 100644 index 00000000..cd020bb1 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/code_analyzer.py @@ -0,0 +1,136 @@ +# :Author: Georg Brandl; Lea Wiemann; Günter Milde +# :Date: $Date: 2022-11-16 15:01:31 +0100 (Mi, 16. Nov 2022) $ +# :Copyright: This module has been placed in the public domain. + +"""Lexical analysis of formal languages (i.e. code) using Pygments.""" + +from docutils import ApplicationError +try: + import pygments + from pygments.lexers import get_lexer_by_name + from pygments.formatters.html import _get_ttype_class + with_pygments = True +except ImportError: + with_pygments = False + +# Filter the following token types from the list of class arguments: +unstyled_tokens = ['token', # Token (base token type) + 'text', # Token.Text + ''] # short name for Token and Text +# (Add, e.g., Token.Punctuation with ``unstyled_tokens += 'punctuation'``.) + + +class LexerError(ApplicationError): + pass + + +class Lexer: + """Parse `code` lines and yield "classified" tokens. + + Arguments + + code -- string of source code to parse, + language -- formal language the code is written in, + tokennames -- either 'long', 'short', or 'none' (see below). + + Merge subsequent tokens of the same token-type. + + Iterating over an instance yields the tokens as ``(tokentype, value)`` + tuples. The value of `tokennames` configures the naming of the tokentype: + + 'long': downcased full token type name, + 'short': short name defined by pygments.token.STANDARD_TYPES + (= class argument used in pygments html output), + 'none': skip lexical analysis. + """ + + def __init__(self, code, language, tokennames='short'): + """ + Set up a lexical analyzer for `code` in `language`. + """ + self.code = code + self.language = language + self.tokennames = tokennames + self.lexer = None + # get lexical analyzer for `language`: + if language in ('', 'text') or tokennames == 'none': + return + if not with_pygments: + raise LexerError('Cannot analyze code. ' + 'Pygments package not found.') + try: + self.lexer = get_lexer_by_name(self.language) + except pygments.util.ClassNotFound: + raise LexerError('Cannot analyze code. ' + 'No Pygments lexer found for "%s".' % language) + # self.lexer.add_filter('tokenmerge') + # Since version 1.2. (released Jan 01, 2010) Pygments has a + # TokenMergeFilter. # ``self.merge(tokens)`` in __iter__ could + # be replaced by ``self.lexer.add_filter('tokenmerge')`` in __init__. + # However, `merge` below also strips a final newline added by pygments. + # + # self.lexer.add_filter('tokenmerge') + + def merge(self, tokens): + """Merge subsequent tokens of same token-type. + + Also strip the final newline (added by pygments). + """ + tokens = iter(tokens) + (lasttype, lastval) = next(tokens) + for ttype, value in tokens: + if ttype is lasttype: + lastval += value + else: + yield lasttype, lastval + (lasttype, lastval) = (ttype, value) + if lastval.endswith('\n'): + lastval = lastval[:-1] + if lastval: + yield lasttype, lastval + + def __iter__(self): + """Parse self.code and yield "classified" tokens. + """ + if self.lexer is None: + yield [], self.code + return + tokens = pygments.lex(self.code, self.lexer) + for tokentype, value in self.merge(tokens): + if self.tokennames == 'long': # long CSS class args + classes = str(tokentype).lower().split('.') + else: # short CSS class args + classes = [_get_ttype_class(tokentype)] + classes = [cls for cls in classes if cls not in unstyled_tokens] + yield classes, value + + +class NumberLines: + """Insert linenumber-tokens at the start of every code line. + + Arguments + + tokens -- iterable of ``(classes, value)`` tuples + startline -- first line number + endline -- last line number + + Iterating over an instance yields the tokens with a + ``(['ln'], '<the line number>')`` token added for every code line. + Multi-line tokens are split.""" + + def __init__(self, tokens, startline, endline): + self.tokens = tokens + self.startline = startline + # pad linenumbers, e.g. endline == 100 -> fmt_str = '%3d ' + self.fmt_str = '%%%dd ' % len(str(endline)) + + def __iter__(self): + lineno = self.startline + yield ['ln'], self.fmt_str % lineno + for ttype, value in self.tokens: + lines = value.split('\n') + for line in lines[:-1]: + yield ttype, line + '\n' + lineno += 1 + yield ['ln'], self.fmt_str % lineno + yield ttype, lines[-1] diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/error_reporting.py b/.venv/lib/python3.12/site-packages/docutils/utils/error_reporting.py new file mode 100644 index 00000000..805c95bb --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/error_reporting.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +# :Id: $Id: error_reporting.py 9078 2022-06-17 11:31:40Z milde $ +# :Copyright: © 2011 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 + +""" +Deprecated module to handle Exceptions across Python versions. + +.. warning:: + This module is deprecated with the end of support for Python 2.7 + and will be removed in Docutils 0.21 or later. + + Replacements: + | SafeString -> str + | ErrorString -> docutils.io.error_string() + | ErrorOutput -> docutils.io.ErrorOutput + +Error reporting should be safe from encoding/decoding errors. +However, implicit conversions of strings and exceptions like + +>>> u'%s world: %s' % ('H\xe4llo', Exception(u'H\xe4llo')) + +fail in some Python versions: + +* In Python <= 2.6, ``unicode(<exception instance>)`` uses + `__str__` and fails with non-ASCII chars in`unicode` arguments. + (work around http://bugs.python.org/issue2517): + +* In Python 2, unicode(<exception instance>) fails, with non-ASCII + chars in arguments. (Use case: in some locales, the errstr + argument of IOError contains non-ASCII chars.) + +* In Python 2, str(<exception instance>) fails, with non-ASCII chars + in `unicode` arguments. + +The `SafeString`, `ErrorString` and `ErrorOutput` classes handle +common exceptions. +""" + +import sys +import warnings + +from docutils.io import _locale_encoding as locale_encoding # noqa + +warnings.warn('The `docutils.utils.error_reporting` module is deprecated ' + 'and will be removed in Docutils 0.21 or later.\n' + 'Details with help("docutils.utils.error_reporting").', + DeprecationWarning, stacklevel=2) + + +if sys.version_info >= (3, 0): + unicode = str # noqa + + +class SafeString: + """ + A wrapper providing robust conversion to `str` and `unicode`. + """ + + def __init__(self, data, encoding=None, encoding_errors='backslashreplace', + decoding_errors='replace'): + self.data = data + self.encoding = (encoding or getattr(data, 'encoding', None) + or locale_encoding or 'ascii') + self.encoding_errors = encoding_errors + self.decoding_errors = decoding_errors + + def __str__(self): + try: + return str(self.data) + except UnicodeEncodeError: + if isinstance(self.data, Exception): + args = [str(SafeString(arg, self.encoding, + self.encoding_errors)) + for arg in self.data.args] + return ', '.join(args) + if isinstance(self.data, unicode): + if sys.version_info > (3, 0): + return self.data + else: + return self.data.encode(self.encoding, + self.encoding_errors) + raise + + def __unicode__(self): + """ + Return unicode representation of `self.data`. + + Try ``unicode(self.data)``, catch `UnicodeError` and + + * if `self.data` is an Exception instance, work around + http://bugs.python.org/issue2517 with an emulation of + Exception.__unicode__, + + * else decode with `self.encoding` and `self.decoding_errors`. + """ + try: + u = unicode(self.data) + if isinstance(self.data, EnvironmentError): + u = u.replace(": u'", ": '") # normalize filename quoting + return u + except UnicodeError as error: # catch ..Encode.. and ..Decode.. errors + if isinstance(self.data, EnvironmentError): + return "[Errno %s] %s: '%s'" % ( + self.data.errno, + SafeString(self.data.strerror, self.encoding, + self.decoding_errors), + SafeString(self.data.filename, self.encoding, + self.decoding_errors)) + if isinstance(self.data, Exception): + args = [unicode(SafeString( + arg, self.encoding, + decoding_errors=self.decoding_errors)) + for arg in self.data.args] + return u', '.join(args) + if isinstance(error, UnicodeDecodeError): + return unicode(self.data, self.encoding, self.decoding_errors) + raise + + +class ErrorString(SafeString): + """ + Safely report exception type and message. + """ + def __str__(self): + return '%s: %s' % (self.data.__class__.__name__, + super(ErrorString, self).__str__()) + + def __unicode__(self): + return u'%s: %s' % (self.data.__class__.__name__, + super(ErrorString, self).__unicode__()) + + +class ErrorOutput: + """ + Wrapper class for file-like error streams with + failsafe de- and encoding of `str`, `bytes`, `unicode` and + `Exception` instances. + """ + + def __init__(self, stream=None, encoding=None, + encoding_errors='backslashreplace', + decoding_errors='replace'): + """ + :Parameters: + - `stream`: a file-like object, + a string (path to a file), + `None` (write to `sys.stderr`, default), or + evaluating to `False` (write() requests are ignored). + - `encoding`: `stream` text encoding. Guessed if None. + - `encoding_errors`: how to treat encoding errors. + """ + if stream is None: + stream = sys.stderr + elif not stream: + stream = False + # if `stream` is a file name, open it + elif isinstance(stream, str): + stream = open(stream, 'w') + elif isinstance(stream, unicode): + stream = open(stream.encode(sys.getfilesystemencoding()), 'w') + + self.stream = stream + """Where warning output is sent.""" + + self.encoding = (encoding or getattr(stream, 'encoding', None) + or locale_encoding or 'ascii') + """The output character encoding.""" + + self.encoding_errors = encoding_errors + """Encoding error handler.""" + + self.decoding_errors = decoding_errors + """Decoding error handler.""" + + def write(self, data): + """ + Write `data` to self.stream. Ignore, if self.stream is False. + + `data` can be a `string`, `unicode`, or `Exception` instance. + """ + if self.stream is False: + return + if isinstance(data, Exception): + data = unicode(SafeString(data, self.encoding, + self.encoding_errors, + self.decoding_errors)) + try: + self.stream.write(data) + except UnicodeEncodeError: + self.stream.write(data.encode(self.encoding, self.encoding_errors)) + except TypeError: + if isinstance(data, unicode): # passed stream may expect bytes + self.stream.write(data.encode(self.encoding, + self.encoding_errors)) + return + if self.stream in (sys.stderr, sys.stdout): + self.stream.buffer.write(data) # write bytes to raw stream + else: + self.stream.write(unicode(data, self.encoding, + self.decoding_errors)) + + def close(self): + """ + Close the error-output stream. + + Ignored if the stream is` sys.stderr` or `sys.stdout` or has no + close() method. + """ + if self.stream in (sys.stdout, sys.stderr): + return + try: + self.stream.close() + except AttributeError: + pass diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/__init__.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/__init__.py new file mode 100644 index 00000000..2ad43b42 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/__init__.py @@ -0,0 +1,73 @@ +# :Id: $Id: __init__.py 9516 2024-01-15 16:11:08Z milde $ +# :Author: Guenter 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 + +""" +This is the Docutils (Python Documentation Utilities) "math" sub-package. + +It contains various modules for conversion between different math formats +(LaTeX, MathML, HTML). + +:math2html: LaTeX math -> HTML conversion from eLyXer +:latex2mathml: LaTeX math -> presentational MathML +:unichar2tex: Unicode character to LaTeX math translation table +:tex2unichar: LaTeX math to Unicode character translation dictionaries +:mathalphabet2unichar: LaTeX math alphabets to Unicode character translation +:tex2mathml_extern: Wrapper for 3rd party TeX -> MathML converters +""" + +# helpers for Docutils math support +# ================================= + + +class MathError(ValueError): + """Exception for math syntax and math conversion errors. + + The additional attribute `details` may hold a list of Docutils + nodes suitable as children for a ``<system_message>``. + """ + def __init__(self, msg, details=[]): + super().__init__(msg) + self.details = details + + +def toplevel_code(code): + """Return string (LaTeX math) `code` with environments stripped out.""" + chunks = code.split(r'\begin{') + return r'\begin{'.join(chunk.split(r'\end{')[-1] + for chunk in chunks) + + +def pick_math_environment(code, numbered=False): + """Return the right math environment to display `code`. + + The test simply looks for line-breaks (``\\``) outside environments. + Multi-line formulae are set with ``align``, one-liners with + ``equation``. + + If `numbered` evaluates to ``False``, the "starred" versions are used + to suppress numbering. + """ + if toplevel_code(code).find(r'\\') >= 0: + env = 'align' + else: + env = 'equation' + if not numbered: + env += '*' + return env + + +def wrap_math_code(code, as_block): + # Wrap math-code in mode-switching TeX command/environment. + # If `as_block` is True, use environment for displayed equation(s). + if as_block: + env = pick_math_environment(code) + return '\\begin{%s}\n%s\n\\end{%s}' % (env, code, env) + return '$%s$' % code diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/latex2mathml.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/latex2mathml.py new file mode 100644 index 00000000..b6ca3934 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/latex2mathml.py @@ -0,0 +1,1252 @@ +# :Id: $Id: latex2mathml.py 9536 2024-02-01 13:04:22Z milde $ +# :Copyright: © 2005 Jens Jørgen Mortensen [1]_ +# © 2010, 2021, 2024 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 +# +# .. [1] the original `rst2mathml.py` in `sandbox/jensj/latex_math` + +"""Convert LaTex maths code into presentational MathML. + +This module is provisional: +the API is not settled and may change with any minor Docutils version. +""" + +# Usage: +# +# >>> from latex2mathml import * + +import re +import unicodedata + +from docutils.utils.math import (MathError, mathalphabet2unichar, + tex2unichar, toplevel_code) +from docutils.utils.math.mathml_elements import ( + math, mtable, mrow, mtr, mtd, menclose, mphantom, msqrt, mi, mn, mo, + mtext, msub, msup, msubsup, munder, mover, munderover, mroot, mfrac, + mspace, MathRow) + + +# Character data +# -------------- + +# LaTeX math macro to Unicode mappings. +# Character categories. + +# identifiers -> <mi> + +letters = {'hbar': 'ℏ'} # Compatibility mapping: \hbar resembles italic ħ +# "unicode-math" unifies \hbar and \hslash to ℏ. +letters.update(tex2unichar.mathalpha) + +ordinary = tex2unichar.mathord # Miscellaneous symbols + +# special case: Capital Greek letters: (upright in TeX style) +greek_capitals = { + 'Phi': '\u03a6', 'Xi': '\u039e', 'Sigma': '\u03a3', + 'Psi': '\u03a8', 'Delta': '\u0394', 'Theta': '\u0398', + 'Upsilon': '\u03d2', 'Pi': '\u03a0', 'Omega': '\u03a9', + 'Gamma': '\u0393', 'Lambda': '\u039b'} + +# functions -> <mi> +functions = { + # functions with a space in the name + 'liminf': 'lim\u202finf', + 'limsup': 'lim\u202fsup', + 'injlim': 'inj\u202flim', + 'projlim': 'proj\u202flim', + # embellished function names (see handle_cmd() below) + 'varlimsup': 'lim', + 'varliminf': 'lim', + 'varprojlim': 'lim', + 'varinjlim': 'lim', + # custom function name + 'operatorname': None, +} +functions.update((name, name) for name in + ('arccos', 'arcsin', 'arctan', 'arg', 'cos', + 'cosh', 'cot', 'coth', 'csc', 'deg', + 'det', 'dim', 'exp', 'gcd', 'hom', + 'ker', 'lg', 'ln', 'log', 'Pr', + 'sec', 'sin', 'sinh', 'tan', 'tanh')) +# Function with limits: 'lim', 'sup', 'inf', 'max', 'min': +# use <mo> to allow "movablelimits" attribute (see below). + +# modulo operator/arithmetic +modulo_functions = { + # cmdname: (binary, named, parentheses, padding) + 'bmod': (True, True, False, '0.278em'), # a mod n + 'pmod': (False, True, True, '0.444em'), # a (mod n) + 'mod': (False, True, False, '0.667em'), # a mod n + 'pod': (False, False, True, '0.444em'), # a (n) + } + + +# "mathematical alphabets": map identifiers to the corresponding +# characters from the "Mathematical Alphanumeric Symbols" block +math_alphabets = { + # 'cmdname': 'mathvariant value' # package + 'mathbb': 'double-struck', # amssymb + 'mathbf': 'bold', + 'mathbfit': 'bold-italic', # isomath + 'mathcal': 'script', + 'mathfrak': 'fraktur', # amssymb + 'mathit': 'italic', + 'mathrm': 'normal', + 'mathscr': 'script', # mathrsfs et al + 'mathsf': 'sans-serif', + 'mathbfsfit': 'sans-serif-bold-italic', # unicode-math + 'mathsfbfit': 'sans-serif-bold-italic', # isomath + 'mathsfit': 'sans-serif-italic', # isomath + 'mathtt': 'monospace', + # unsupported: bold-fraktur + # bold-script + # bold-sans-serif +} + +# operator, fence, or separator -> <mo> + +stretchables = { + # extensible delimiters allowed in left/right cmds + 'backslash': '\\', + 'uparrow': '\u2191', # ↑ UPWARDS ARROW + 'downarrow': '\u2193', # ↓ DOWNWARDS ARROW + 'updownarrow': '\u2195', # ↕ UP DOWN ARROW + 'Uparrow': '\u21d1', # ⇑ UPWARDS DOUBLE ARROW + 'Downarrow': '\u21d3', # ⇓ DOWNWARDS DOUBLE ARROW + 'Updownarrow': '\u21d5', # ⇕ UP DOWN DOUBLE ARROW + 'lmoustache': '\u23b0', # ⎰ … CURLY BRACKET SECTION + 'rmoustache': '\u23b1', # ⎱ … LEFT CURLY BRACKET SECTION + 'arrowvert': '\u23d0', # ⏐ VERTICAL LINE EXTENSION + 'bracevert': '\u23aa', # ⎪ CURLY BRACKET EXTENSION + 'lvert': '|', # left | + 'lVert': '\u2016', # left ‖ + 'rvert': '|', # right | + 'rVert': '\u2016', # right ‖ + 'Arrowvert': '\u2016', # ‖ +} +stretchables.update(tex2unichar.mathfence) +stretchables.update(tex2unichar.mathopen) # Braces +stretchables.update(tex2unichar.mathclose) # Braces + +# >>> print(' '.join(sorted(set(stretchables.values())))) +# [ \ ] { | } ‖ ↑ ↓ ↕ ⇑ ⇓ ⇕ ⌈ ⌉ ⌊ ⌋ ⌜ ⌝ ⌞ ⌟ ⎪ ⎰ ⎱ ⏐ ⟅ ⟆ ⟦ ⟧ ⟨ ⟩ ⟮ ⟯ ⦇ ⦈ + +operators = { + # negated symbols without pre-composed Unicode character + 'nleqq': '\u2266\u0338', # ≦̸ + 'ngeqq': '\u2267\u0338', # ≧̸ + 'nleqslant': '\u2a7d\u0338', # ⩽̸ + 'ngeqslant': '\u2a7e\u0338', # ⩾̸ + 'ngtrless': '\u2277\u0338', # txfonts + 'nlessgtr': '\u2276\u0338', # txfonts + 'nsubseteqq': '\u2AC5\u0338', # ⫅̸ + 'nsupseteqq': '\u2AC6\u0338', # ⫆̸ + # compatibility definitions: + 'centerdot': '\u2B1D', # BLACK VERY SMALL SQUARE | mathbin + 'varnothing': '\u2300', # ⌀ DIAMETER SIGN | empty set + 'varpropto': '\u221d', # ∝ PROPORTIONAL TO | sans serif + 'triangle': '\u25B3', # WHITE UP-POINTING TRIANGLE | mathord + 'triangledown': '\u25BD', # WHITE DOWN-POINTING TRIANGLE | mathord + # alias commands: + 'dotsb': '\u22ef', # ⋯ with binary operators/relations + 'dotsc': '\u2026', # … with commas + 'dotsi': '\u22ef', # ⋯ with integrals + 'dotsm': '\u22ef', # ⋯ multiplication dots + 'dotso': '\u2026', # … other dots + # functions with movable limits (requires <mo>) + 'lim': 'lim', + 'sup': 'sup', + 'inf': 'inf', + 'max': 'max', + 'min': 'min', +} +operators.update(tex2unichar.mathbin) # Binary symbols +operators.update(tex2unichar.mathrel) # Relation symbols, arrow symbols +operators.update(tex2unichar.mathpunct) # Punctuation +operators.update(tex2unichar.mathop) # Variable-sized symbols +operators.update(stretchables) + + +# special cases + +thick_operators = { + # style='font-weight: bold;' + 'thicksim': '\u223C', # ∼ + 'thickapprox': '\u2248', # ≈ +} + +small_operators = { + # mathsize='75%' + 'shortmid': '\u2223', # ∣ + 'shortparallel': '\u2225', # ∥ + 'nshortmid': '\u2224', # ∤ + 'nshortparallel': '\u2226', # ∦ + 'smallfrown': '\u2322', # ⌢ FROWN + 'smallsmile': '\u2323', # ⌣ SMILE + 'smallint': '\u222b', # ∫ INTEGRAL +} + +# Operators and functions with limits above/below in display formulas +# and in index position inline (movablelimits=True) +movablelimits = ('bigcap', 'bigcup', 'bigodot', 'bigoplus', 'bigotimes', + 'bigsqcup', 'biguplus', 'bigvee', 'bigwedge', + 'coprod', 'intop', 'ointop', 'prod', 'sum', + 'lim', 'max', 'min', 'sup', 'inf') +# Depending on settings, integrals may also be in this category. +# (e.g. if "amsmath" is loaded with option "intlimits", see +# http://mirror.ctan.org/macros/latex/required/amsmath/amsldoc.pdf) +# movablelimits.extend(('fint', 'iiiint', 'iiint', 'iint', 'int', 'oiint', +# 'oint', 'ointctrclockwise', 'sqint', +# 'varointclockwise',)) + +# horizontal space -> <mspace> + +spaces = {'qquad': '2em', # two \quad + 'quad': '1em', # 18 mu + 'thickspace': '0.2778em', # 5mu = 5/18em + ';': '0.2778em', # 5mu thickspace + ' ': '0.25em', # inter word space + '\n': '0.25em', # inter word space + 'medspace': '0.2222em', # 4mu = 2/9em + ':': '0.2222em', # 4mu medspace + 'thinspace': '0.1667em', # 3mu = 1/6em + ',': '0.1667em', # 3mu thinspace + 'negthinspace': '-0.1667em', # -3mu = -1/6em + '!': '-0.1667em', # negthinspace + 'negmedspace': '-0.2222em', # -4mu = -2/9em + 'negthickspace': '-0.2778em', # -5mu = -5/18em + } + +# accents: -> <mo stretchy="false"> in <mover> +accents = { + # TeX: spacing combining + 'acute': '´', # '\u0301' + 'bar': 'ˉ', # '\u0304' + 'breve': '˘', # '\u0306' + 'check': 'ˇ', # '\u030C' + 'dot': '˙', # '\u0307' + 'ddot': '¨', # '\u0308' + 'dddot': '˙˙˙', # '\u20DB' # or … ? + 'ddddot': '˙˙˙˙', # '\u20DC' # or ¨¨ ? + 'grave': '`', # '\u0300' + 'hat': 'ˆ', # '\u0302' + 'mathring': '˚', # '\u030A' + 'tilde': '~', # '\u0303' # tilde ~ or small tilde ˜? + 'vec': '→', # '\u20d7' # → too heavy, use scriptlevel="+1" +} + +# limits etc. -> <mo> in <mover> or <munder> +over = { + # TeX: (char, offset-correction/em) + 'overbrace': ('\u23DE', -0.2), # DejaVu Math -0.6 + 'overleftarrow': ('\u2190', -0.2), + 'overleftrightarrow': ('\u2194', -0.2), + 'overline': ('_', -0.2), # \u2012 does not stretch + 'overrightarrow': ('\u2192', -0.2), + 'widehat': ('^', -0.5), + 'widetilde': ('~', -0.3), +} +under = {'underbrace': ('\u23DF', 0.1), # DejaVu Math -0.7 + 'underleftarrow': ('\u2190', -0.2), + 'underleftrightarrow': ('\u2194', -0.2), + 'underline': ('_', -0.8), + 'underrightarrow': ('\u2192', -0.2), + } + +# Character translations +# ---------------------- +# characters with preferred alternative in mathematical use +# cf. https://www.w3.org/TR/MathML3/chapter7.html#chars.anomalous +anomalous_chars = {'-': '\u2212', # HYPHEN-MINUS -> MINUS SIGN + ':': '\u2236', # COLON -> RATIO + '~': '\u00a0', # NO-BREAK SPACE + } + +# blackboard bold (Greek characters not working with "mathvariant" (Firefox 78) +mathbb = {'Γ': '\u213E', # ℾ + 'Π': '\u213F', # ℿ + 'Σ': '\u2140', # ⅀ + 'γ': '\u213D', # ℽ + 'π': '\u213C', # ℼ + } + +# Matrix environments +matrices = { + # name: fences + 'matrix': ('', ''), + 'smallmatrix': ('', ''), # smaller, see begin_environment()! + 'pmatrix': ('(', ')'), + 'bmatrix': ('[', ']'), + 'Bmatrix': ('{', '}'), + 'vmatrix': ('|', '|'), + 'Vmatrix': ('\u2016', '\u2016'), # ‖ + 'aligned': ('', ''), + 'cases': ('{', ''), +} + +layout_styles = { + 'displaystyle': {'displaystyle': True, 'scriptlevel': 0}, + 'textstyle': {'displaystyle': False, 'scriptlevel': 0}, + 'scriptstyle': {'displaystyle': False, 'scriptlevel': 1}, + 'scriptscriptstyle': {'displaystyle': False, 'scriptlevel': 2}, + } +# See also https://www.w3.org/TR/MathML3/chapter3.html#presm.scriptlevel + +fractions = { + # name: attributes + 'frac': {}, + 'cfrac': {'displaystyle': True, 'scriptlevel': 0, + 'class': 'cfrac'}, # in LaTeX with padding + 'dfrac': layout_styles['displaystyle'], + 'tfrac': layout_styles['textstyle'], + 'binom': {'linethickness': 0}, + 'dbinom': layout_styles['displaystyle'] | {'linethickness': 0}, + 'tbinom': layout_styles['textstyle'] | {'linethickness': 0}, +} + +delimiter_sizes = ['', '1.2em', '1.623em', '2.047em', '2.470em'] +bigdelimiters = {'left': 0, + 'right': 0, + 'bigl': 1, + 'bigr': 1, + 'Bigl': 2, + 'Bigr': 2, + 'biggl': 3, + 'biggr': 3, + 'Biggl': 4, + 'Biggr': 4, + } + + +# LaTeX to MathML translation +# --------------------------- + +# auxiliary functions +# ~~~~~~~~~~~~~~~~~~~ + +def tex_cmdname(string): + """Return leading TeX command name and remainder of `string`. + + >>> tex_cmdname('mymacro2') # up to first non-letter + ('mymacro', '2') + >>> tex_cmdname('name 2') # strip trailing whitespace + ('name', '2') + >>> tex_cmdname('_2') # single non-letter character + ('_', '2') + + """ + m = re.match(r'([a-zA-Z]+)[ \n]*(.*)', string, re.DOTALL) + if m is None: + m = re.match(r'(.?)(.*)', string, re.DOTALL) + return m.group(1), m.group(2) + + +# Test: +# +# >>> tex_cmdname('name\nnext') # strip trailing whitespace, also newlines +# ('name', 'next') +# >>> tex_cmdname('name_2') # first non-letter terminates +# ('name', '_2') +# >>> tex_cmdname('name_2\nnext line') # line-break allowed +# ('name', '_2\nnext line') +# >>> tex_cmdname(' next') # leading whitespace is returned +# (' ', 'next') +# >>> tex_cmdname('1 2') # whitespace after non-letter is kept +# ('1', ' 2') +# >>> tex_cmdname('1\n2\t3') # whitespace after non-letter is kept +# ('1', '\n2\t3') +# >>> tex_cmdname('') # empty string +# ('', '') + + +def tex_number(string): + """Return leading number literal and remainder of `string`. + + >>> tex_number('123.4') + ('123.4', '') + + """ + m = re.match(r'([0-9.,]*[0-9]+)(.*)', string, re.DOTALL) + if m is None: + return '', string + return m.group(1), m.group(2) + + +# Test: +# +# >>> tex_number(' 23.4b') # leading whitespace -> no number +# ('', ' 23.4b') +# >>> tex_number('23,400/2') # comma separator included +# ('23,400', '/2') +# >>> tex_number('23. 4/2') # trailing separator not included +# ('23', '. 4/2') +# >>> tex_number('4, 2') # trailing separator not included +# ('4', ', 2') +# >>> tex_number('1 000.4') +# ('1', ' 000.4') + + +def tex_token(string): + """Return first simple TeX token and remainder of `string`. + + >>> tex_token('\\command{without argument}') + ('\\command', '{without argument}') + >>> tex_token('or first character') + ('o', 'r first character') + + """ + m = re.match(r"""((?P<cmd>\\[a-zA-Z]+)\s* # TeX command, skip whitespace + |(?P<chcmd>\\.) # one-character TeX command + |(?P<ch>.?)) # first character (or empty) + (?P<remainder>.*$) # remaining part of string + """, string, re.VERBOSE | re.DOTALL) + cmd, chcmd, ch, remainder = m.group('cmd', 'chcmd', 'ch', 'remainder') + return cmd or chcmd or ch, remainder + +# Test: +# +# >>> tex_token('{opening bracket of group}') +# ('{', 'opening bracket of group}') +# >>> tex_token('\\skip whitespace after macro name') +# ('\\skip', 'whitespace after macro name') +# >>> tex_token('. but not after single char') +# ('.', ' but not after single char') +# >>> tex_token('') # empty string. +# ('', '') +# >>> tex_token('\{escaped bracket') +# ('\\{', 'escaped bracket') + + +def tex_group(string): + """Return first TeX group or token and remainder of `string`. + + >>> tex_group('{first group} returned without brackets') + ('first group', ' returned without brackets') + + """ + split_index = 0 + nest_level = 0 # level of {{nested} groups} + escape = False # the next character is escaped (\) + + if not string.startswith('{'): + # special case: there is no group, return first token and remainder + return string[:1], string[1:] + for c in string: + split_index += 1 + if escape: + escape = False + elif c == '\\': + escape = True + elif c == '{': + nest_level += 1 + elif c == '}': + nest_level -= 1 + if nest_level == 0: + break + else: + raise MathError('Group without closing bracket!') + return string[1:split_index-1], string[split_index:] + + +# >>> tex_group('{} empty group') +# ('', ' empty group') +# >>> tex_group('{group with {nested} group} ') +# ('group with {nested} group', ' ') +# >>> tex_group('{group with {nested group}} at the end') +# ('group with {nested group}', ' at the end') +# >>> tex_group('{{group} {with {{complex }nesting}} constructs}') +# ('{group} {with {{complex }nesting}} constructs', '') +# >>> tex_group('{group with \\{escaped\\} brackets}') +# ('group with \\{escaped\\} brackets', '') +# >>> tex_group('{group followed by closing bracket}} from outer group') +# ('group followed by closing bracket', '} from outer group') +# >>> tex_group('No group? Return first character.') +# ('N', 'o group? Return first character.') +# >>> tex_group(' {also whitespace}') +# (' ', '{also whitespace}') + + +def tex_token_or_group(string): + """Return first TeX group or token and remainder of `string`. + + >>> tex_token_or_group('\\command{without argument}') + ('\\command', '{without argument}') + >>> tex_token_or_group('first character') + ('f', 'irst character') + >>> tex_token_or_group(' also whitespace') + (' ', 'also whitespace') + >>> tex_token_or_group('{first group} keep rest') + ('first group', ' keep rest') + + """ + arg, remainder = tex_token(string) + if arg == '{': + arg, remainder = tex_group(string.lstrip()) + return arg, remainder + +# >>> tex_token_or_group('\{no group but left bracket') +# ('\\{', 'no group but left bracket') + + +def tex_optarg(string): + """Return optional argument and remainder. + + >>> tex_optarg('[optional argument] returned without brackets') + ('optional argument', ' returned without brackets') + >>> tex_optarg('{empty string, if there is no optional arg}') + ('', '{empty string, if there is no optional arg}') + + """ + m = re.match(r"""\s* # leading whitespace + \[(?P<optarg>(\\]|[^\[\]]|\\])*)\] # [group] without nested groups + (?P<remainder>.*$) + """, string, re.VERBOSE | re.DOTALL) + if m is None and not string.startswith('['): + return '', string + try: + return m.group('optarg'), m.group('remainder') + except AttributeError: + raise MathError(f'Could not extract optional argument from "{string}"!') + +# Test: +# >>> tex_optarg(' [optional argument] after whitespace') +# ('optional argument', ' after whitespace') +# >>> tex_optarg('[missing right bracket') +# Traceback (most recent call last): +# ... +# docutils.utils.math.MathError: Could not extract optional argument from "[missing right bracket"! +# >>> tex_optarg('[group with [nested group]]') +# Traceback (most recent call last): +# ... +# docutils.utils.math.MathError: Could not extract optional argument from "[group with [nested group]]"! + + +def parse_latex_math(root, source): + """Append MathML conversion of `string` to `node` and return it. + + >>> parse_latex_math(math(), r'\alpha') + math(mi('α')) + >>> parse_latex_math(mrow(), r'x_{n}') + mrow(msub(mi('x'), mi('n'))) + + """ + # Normalize white-space: + string = source # not-yet handled part of source + node = root # the current "insertion point" + + # Loop over `string` while changing it. + while len(string) > 0: + # Take off first character: + c, string = string[0], string[1:] + + if c in ' \n': + continue # whitespace is ignored in LaTeX math mode + if c == '\\': # start of a LaTeX macro + cmdname, string = tex_cmdname(string) + node, string = handle_cmd(cmdname, node, string) + elif c in "_^": + node = handle_script_or_limit(node, c) + elif c == '{': + if isinstance(node, MathRow) and node.nchildren == 1: + # LaTeX takes one arg, MathML node accepts a group + node.nchildren = None # allow appending until closed by '}' + else: # wrap group in an <mrow> + new_node = mrow() + node.append(new_node) + node = new_node + elif c == '}': + node = node.close() + elif c == '&': + new_node = mtd() + node.close().append(new_node) + node = new_node + elif c.isalpha(): + node = node.append(mi(c)) + elif c.isdigit(): + number, string = tex_number(string) + node = node.append(mn(c+number)) + elif c in anomalous_chars: + # characters with a special meaning in LaTeX math mode + # fix spacing before "unary" minus. + attributes = {} + if c == '-' and len(node): + previous_node = node[-1] + if (previous_node.text and previous_node.text in '([=' + or previous_node.get('class') == 'mathopen'): + attributes['form'] = 'prefix' + node = node.append(mo(anomalous_chars[c], **attributes)) + elif c in "/()[]|": + node = node.append(mo(c, stretchy=False)) + elif c in "+*=<>,.!?`';@": + node = node.append(mo(c)) + else: + raise MathError(f'Unsupported character: "{c}"!') + # TODO: append as <mi>? + if node is None: + if not string: + return root # ignore unbalanced braces + raise MathError(f'No insertion point for "{string}". ' + f'Unbalanced braces in "{source[:-len(string)]}"?') + if node.nchildren and len(node) < node.nchildren: + raise MathError('Last node missing children. Source incomplete?') + return root + +# Test: + +# >>> parse_latex_math(math(), '') +# math() +# >>> parse_latex_math(math(), ' \\sqrt{ \\alpha}') +# math(msqrt(mi('α'))) +# >>> parse_latex_math(math(), '23.4x') +# math(mn('23.4'), mi('x')) +# >>> parse_latex_math(math(), '\\sqrt 2 \\ne 3') +# math(msqrt(mn('2')), mo('≠'), mn('3')) +# >>> parse_latex_math(math(), '\\sqrt{2 + 3} < 10') +# math(msqrt(mn('2'), mo('+'), mn('3'), nchildren=3), mo('<'), mn('10')) +# >>> parse_latex_math(math(), '\\sqrt[3]{2 + 3}') +# math(mroot(mrow(mn('2'), mo('+'), mn('3'), nchildren=3), mn('3'))) +# >>> parse_latex_math(math(), '\max_x') # function takes limits +# math(munder(mo('max', movablelimits='true'), mi('x'))) +# >>> parse_latex_math(math(), 'x^j_i') # ensure correct order: base, sub, sup +# math(msubsup(mi('x'), mi('i'), mi('j'))) +# >>> parse_latex_math(math(), '\int^j_i') # ensure correct order +# math(msubsup(mo('∫'), mi('i'), mi('j'))) +# >>> parse_latex_math(math(), 'x_{\\alpha}') +# math(msub(mi('x'), mi('α'))) +# >>> parse_latex_math(math(), 'x_\\text{in}') +# math(msub(mi('x'), mtext('in'))) +# >>> parse_latex_math(math(), '2⌘') +# Traceback (most recent call last): +# docutils.utils.math.MathError: Unsupported character: "⌘"! +# >>> parse_latex_math(math(), '23}x') # doctest: +ELLIPSIS +# Traceback (most recent call last): +# ... +# docutils.utils.math.MathError: ... Unbalanced braces in "23}"? +# >>> parse_latex_math(math(), '\\frac{2}') +# Traceback (most recent call last): +# ... +# docutils.utils.math.MathError: Last node missing children. Source incomplete? + + +def handle_cmd(name, node, string): # noqa: C901 TODO make this less complex + """Process LaTeX command `name` followed by `string`. + + Append result to `node`. + If needed, parse `string` for command argument. + Return new current node and remainder of `string`: + + >>> handle_cmd('hbar', math(), r' \frac') + (math(mi('ℏ')), ' \\frac') + >>> handle_cmd('hspace', math(), r'{1ex} (x)') + (math(mspace(width='1ex')), ' (x)') + + """ + + # Token elements + # ============== + + # identifier -> <mi> + + if name in letters: + new_node = mi(letters[name]) + if name in greek_capitals: + # upright in "TeX style" but MathML sets them italic ("ISO style"). + # CSS styling does not change the font style in Firefox 78. + # Use 'mathvariant="normal"'? + new_node.set('class', 'capital-greek') + node = node.append(new_node) + return node, string + + if name in ordinary: + # <mi mathvariant="normal"> well supported by Chromium but + # Firefox 115.5.0 puts additional space around the symbol, e.g. + # <mi mathvariant="normal">∂</mi><mi>t</mi> looks like ∂ t, not ∂t + # return node.append(mi(ordinary[name], mathvariant='normal')), string + return node.append(mi(ordinary[name])), string + + if name in functions: + # use <mi> followed by invisible function applicator character + # (see https://www.w3.org/TR/MathML3/chapter3.html#presm.mi) + if name == 'operatorname': + # custom function name, e.g. ``\operatorname{abs}(x)`` + # TODO: \operatorname* -> with limits + arg, string = tex_token_or_group(string) + new_node = mi(arg, mathvariant='normal') + else: + new_node = mi(functions[name]) + # embellished function names: + if name == 'varliminf': # \underline\lim + new_node = munder(new_node, mo('_')) + elif name == 'varlimsup': # \overline\lim + new_node = mover(new_node, mo('¯'), accent=False) + elif name == 'varprojlim': # \underleftarrow\lim + new_node = munder(new_node, mo('\u2190')) + elif name == 'varinjlim': # \underrightarrow\lim + new_node = munder(new_node, mo('\u2192')) + + node = node.append(new_node) + # add ApplyFunction when appropriate (not \sin^2(x), say) + # cf. https://www.w3.org/TR/MathML3/chapter3.html#presm.mi + if string and string[0] not in ('^', '_'): + node = node.append(mo('\u2061')) # ⁡ + return node, string + + if name in modulo_functions: + (binary, named, parentheses, padding) = modulo_functions[name] + if binary: + node = node.append(mo('mod', lspace=padding, rspace=padding)) + return node, string + # left padding + if node.in_block(): + padding = '1em' + node = node.append(mspace(width=padding)) + if parentheses: + node = node.append(mo('(', stretchy=False)) + if named: + node = node.append(mi('mod')) + node = node.append(mspace(width='0.333em')) + arg, string = tex_token_or_group(string) + node = parse_latex_math(node, arg) + if parentheses: + node = node.append(mo(')', stretchy=False)) + return node, string + + # font changes or mathematical alphanumeric characters + + if name in ('boldsymbol', 'pmb'): # \pmb is "poor mans bold" + new_node = mrow(CLASS='boldsymbol') + node.append(new_node) + return new_node, string + + if name in math_alphabets: + return handle_math_alphabet(name, node, string) + + # operator, fence, or separator -> <mo> + + if name == 'colon': # trailing punctuation, not binary relation + node = node.append(mo(':', form='postfix', lspace='0', rspace='0.28em')) + return node, string + + if name == 'idotsint': # AMS shortcut for ∫︀···∫︀ + node = parse_latex_math(node, r'\int\dotsi\int') + return node, string + + if name in thick_operators: + node = node.append(mo(thick_operators[name], style='font-weight: bold')) + return node, string + + if name in small_operators: + node = node.append(mo(small_operators[name], mathsize='75%')) + return node, string + + if name in operators: + attributes = {} + if name in movablelimits and string and string[0] in ' _^': + attributes['movablelimits'] = True + elif name in ('lvert', 'lVert'): + attributes['class'] = 'mathopen' + node = node.append(mo(operators[name], **attributes)) + return node, string + + if name in bigdelimiters: + delimiter_attributes = {} + size = delimiter_sizes[bigdelimiters[name]] + delimiter, string = tex_token_or_group(string) + if delimiter not in '()[]/|.': + try: + delimiter = stretchables[delimiter.lstrip('\\')] + except KeyError: + raise MathError(f'Unsupported "\\{name}" delimiter ' + f'"{delimiter}"!') + if size: + delimiter_attributes['maxsize'] = size + delimiter_attributes['minsize'] = size + delimiter_attributes['symmetric'] = True + if name == 'left' or name.endswith('l'): + row = mrow() + node.append(row) + node = row + if delimiter != '.': # '.' stands for "empty delimiter" + node.append(mo(delimiter, **delimiter_attributes)) + if name == 'right' or name.endswith('r'): + node = node.close() + return node, string + + if name == 'not': + # negation: LaTeX just overlays next symbol with "/". + arg, string = tex_token(string) + if arg == '{': + return node, '{\\not ' + string + if arg.startswith('\\'): # LaTeX macro + try: + arg = operators[arg[1:]] + except KeyError: + raise MathError(rf'"\not" cannot negate: "{arg}"!') + arg = unicodedata.normalize('NFC', arg+'\u0338') + node = node.append(mo(arg)) + return node, string + + # arbitrary text (usually comments) -> <mtext> + if name in ('text', 'mbox', 'textrm'): + arg, string = tex_token_or_group(string) + parts = arg.split('$') # extract inline math + for i, part in enumerate(parts): + if i % 2 == 0: # i is even + # LaTeX keeps whitespace in, e.g., ``\text{ foo }``, + # <mtext> displays only internal whitespace. + # → replace marginal whitespace with NBSP + part = re.sub('(^[ \n]|[ \n]$)', '\u00a0', part) + node = node.append(mtext(part)) + else: + parse_latex_math(node, part) + return node, string + + # horizontal space -> <mspace> + if name in spaces: + node = node.append(mspace(width='%s'%spaces[name])) + return node, string + + if name in ('hspace', 'mspace'): + arg, string = tex_group(string) + if arg.endswith('mu'): + # unit "mu" (1mu=1/18em) not supported by MathML + arg = '%sem' % (float(arg[:-2])/18) + node = node.append(mspace(width='%s'%arg)) + return node, string + + if name == 'phantom': + new_node = mphantom() + node.append(new_node) + return new_node, string + + if name == 'boxed': + # CSS padding is broken in Firefox 115.6.0esr + # therefore we still need the deprecated <menclose> element + new_node = menclose(notation='box', CLASS='boxed') + node.append(new_node) + return new_node, string + + # Complex elements (Layout schemata) + # ================================== + + if name == 'sqrt': + radix, string = tex_optarg(string) + if radix: + indexnode = mrow() + new_node = mroot(indexnode, switch=True) + parse_latex_math(indexnode, radix) + indexnode.close() + else: + new_node = msqrt() + node.append(new_node) + return new_node, string + + if name in fractions: + attributes = fractions[name] + if name == 'cfrac': + optarg, string = tex_optarg(string) + optargs = {'l': 'left', 'r': 'right'} + if optarg in optargs: + attributes = attributes.copy() + attributes['numalign'] = optargs[optarg] # "numalign" is deprecated + attributes['class'] += ' numalign-' + optargs[optarg] + new_node = frac = mfrac(**attributes) + if name.endswith('binom'): + new_node = mrow(mo('('), new_node, mo(')'), CLASS='binom') + new_node.nchildren = 3 + node.append(new_node) + return frac, string + + if name == '\\': # end of a row + entry = mtd() + new_node = mtr(entry) + node.close().close().append(new_node) + return entry, string + + if name in accents: + accent_node = mo(accents[name], stretchy=False) + # mi() would be simpler, but semantically wrong + # --- https://w3c.github.io/mathml-core/#operator-fence-separator-or-accent-mo + if name == 'vec': + accent_node.set('scriptlevel', '+1') # scale down arrow + new_node = mover(accent_node, accent=True, switch=True) + node.append(new_node) + return new_node, string + + if name in over: + # set "accent" to False (otherwise dots on i and j are dropped) + # but to True on accent node get "textstyle" (full size) symbols on top + new_node = mover(mo(over[name][0], accent=True), + switch=True, accent=False) + node.append(new_node) + return new_node, string + + if name == 'overset': + new_node = mover(switch=True) + node.append(new_node) + return new_node, string + + if name in under: + new_node = munder(mo(under[name][0]), switch=True) + node.append(new_node) + return new_node, string + + if name == 'underset': + new_node = munder(switch=True) + node.append(new_node) + return new_node, string + + if name in ('xleftarrow', 'xrightarrow'): + subscript, string = tex_optarg(string) + base = mo(operators['long'+name[1:]]) + if subscript: + new_node = munderover(base) + sub_node = parse_latex_math(mrow(), subscript) + if len(sub_node) == 1: + sub_node = sub_node[0] + new_node.append(sub_node) + else: + new_node = mover(base) + node.append(new_node) + return new_node, string + + if name in layout_styles: # 'displaystyle', 'textstyle', ... + if len(node) > 0: + raise MathError(rf'Declaration "\{name}" must be first command ' + 'in a group!') + for k, v in layout_styles[name].items(): + node.set(k, v) + return node, string + + if name.endswith('limits'): + arg, remainder = tex_token(string) + if arg in '_^': # else ignore + string = remainder + node = handle_script_or_limit(node, arg, limits=name) + return node, string + + # Environments + + if name == 'begin': + return begin_environment(node, string) + + if name == 'end': + return end_environment(node, string) + + raise MathError(rf'Unknown LaTeX command "\{name}".') + +# >>> handle_cmd('left', math(), '[a\\right]') +# (mrow(mo('[')), 'a\\right]') +# >>> handle_cmd('left', math(), '. a)') # empty \left +# (mrow(), ' a)') +# >>> handle_cmd('left', math(), '\\uparrow a)') # cmd +# (mrow(mo('↑')), 'a)') +# >>> handle_cmd('not', math(), '\\equiv \\alpha)') # cmd +# (math(mo('≢')), '\\alpha)') +# >>> handle_cmd('text', math(), '{ for } i>0') # group +# (math(mtext('\xa0for\xa0')), ' i>0') +# >>> handle_cmd('text', math(), '{B}T') # group +# (math(mtext('B')), 'T') +# >>> handle_cmd('text', math(), '{number of apples}}') # group +# (math(mtext('number of apples')), '}') +# >>> handle_cmd('text', math(), 'i \\sin(x)') # single char +# (math(mtext('i')), ' \\sin(x)') +# >>> handle_cmd(' ', math(), ' next') # inter word space +# (math(mspace(width='0.25em')), ' next') +# >>> handle_cmd('\n', math(), '\nnext') # inter word space +# (math(mspace(width='0.25em')), '\nnext') +# >>> handle_cmd('sin', math(), '(\\alpha)') +# (math(mi('sin'), mo('\u2061')), '(\\alpha)') +# >>> handle_cmd('sin', math(), ' \\alpha') +# (math(mi('sin'), mo('\u2061')), ' \\alpha') +# >>> handle_cmd('operatorname', math(), '{abs}(x)') +# (math(mi('abs', mathvariant='normal'), mo('\u2061')), '(x)') +# >>> handle_cmd('overline', math(), '{981}') +# (mover(mo('_', accent='true'), switch=True, accent='false'), '{981}') +# >>> handle_cmd('bar', math(), '{x}') +# (mover(mo('ˉ', stretchy='false'), switch=True, accent='true'), '{x}') +# >>> handle_cmd('xleftarrow', math(), r'[\alpha]{10}') +# (munderover(mo('⟵'), mi('α')), '{10}') +# >>> handle_cmd('xleftarrow', math(), r'[\alpha=5]{10}') +# (munderover(mo('⟵'), mrow(mi('α'), mo('='), mn('5'))), '{10}') +# >>> handle_cmd('left', math(), '< a)') +# Traceback (most recent call last): +# docutils.utils.math.MathError: Unsupported "\left" delimiter "<"! +# >>> handle_cmd('not', math(), '{< b} c') # LaTeX ignores the braces, too. +# (math(), '{\\not < b} c') + + +def handle_math_alphabet(name, node, string): + attributes = {} + if name == 'mathscr': + attributes['class'] = 'mathscr' + arg, string = tex_token_or_group(string) + # Shortcut for text arg like \mathrm{out} with more than one letter: + if name == 'mathrm' and arg.isalpha() and len(arg) > 1: + node = node.append(mi(arg)) # <mi> defaults to "normal" font + return node, string + # Parse into an <mrow> + container = mrow(**attributes) + node.append(container) + parse_latex_math(container, arg) + key = name.replace('mathscr', 'mathcal').replace('mathbfsfit', 'mathsfbfit') + a2ch = getattr(mathalphabet2unichar, key, {}) + for subnode in container.iter(): + if isinstance(subnode, mn): + # a number may consist of more than one digit + subnode.text = ''.join(a2ch.get(ch, ch) for ch in subnode.text) + elif isinstance(subnode, mi): + # don't convert multi-letter identifiers (functions) + subnode.text = a2ch.get(subnode.text, subnode.text) + if name == 'mathrm' and subnode.text.isalpha(): + subnode.set('mathvariant', 'normal') + return container.close(), string + +# >>> handle_math_alphabet('mathrm', math(), '\\alpha') +# (math(mi('α', mathvariant='normal')), '') +# >>> handle_math_alphabet('mathbb', math(), '{R} = 3') +# (math(mi('ℝ')), ' = 3') +# >>> handle_math_alphabet('mathcal', math(), '{F = 3}') +# (math(mrow(mi('ℱ'), mo('='), mn('3'), nchildren=3)), '') +# >>> handle_math_alphabet('mathrm', math(), '{out} = 3') # drop <mrow> +# (math(mi('out')), ' = 3') +# +# Single letters in \mathrm require "mathvariant='normal'": +# >>> handle_math_alphabet('mathrm', math(), '{V = 3}') # doctest: +ELLIPSIS +# (math(mrow(mi('V', mathvariant='normal'), mo('='), mn('3'), ...)), '') + + +def handle_script_or_limit(node, c, limits=''): + """Append script or limit element to `node`.""" + child = node.pop() + if limits == 'limits': + child.set('movablelimits', 'false') + elif (limits == 'movablelimits' + or getattr(child, 'text', '') in movablelimits): + child.set('movablelimits', 'true') + + if c == '_': + if isinstance(child, mover): + new_node = munderover(*child, switch=True) + elif isinstance(child, msup): + new_node = msubsup(*child, switch=True) + elif (limits in ('limits', 'movablelimits') + or limits == '' and child.get('movablelimits', None)): + new_node = munder(child) + else: + new_node = msub(child) + elif c == '^': + if isinstance(child, munder): + new_node = munderover(*child) + elif isinstance(child, msub): + new_node = msubsup(*child) + elif (limits in ('limits', 'movablelimits') + or limits == '' and child.get('movablelimits', None)): + new_node = mover(child) + else: + new_node = msup(child) + node.append(new_node) + return new_node + + +def begin_environment(node, string): + name, string = tex_group(string) + if name in matrices: + left_delimiter = matrices[name][0] + attributes = {} + if left_delimiter: + wrapper = mrow(mo(left_delimiter)) + if name == 'cases': + wrapper = mrow(mo(left_delimiter, rspace='0.17em')) + attributes['columnalign'] = 'left' + attributes['class'] = 'cases' + node.append(wrapper) + node = wrapper + elif name == 'smallmatrix': + attributes['rowspacing'] = '0.02em' + attributes['columnspacing'] = '0.333em' + attributes['scriptlevel'] = '1' + elif name == 'aligned': + attributes['class'] = 'ams-align' + # TODO: array, aligned & alignedat take an optional [t], [b], or [c]. + entry = mtd() + node.append(mtable(mtr(entry), **attributes)) + node = entry + else: + raise MathError(f'Environment "{name}" not supported!') + return node, string + + +def end_environment(node, string): + name, string = tex_group(string) + if name in matrices: + node = node.close().close().close() # close: mtd, mdr, mtable + right_delimiter = matrices[name][1] + if right_delimiter: + node = node.append(mo(right_delimiter)) + node = node.close() + elif name == 'cases': + node = node.close() + else: + raise MathError(f'Environment "{name}" not supported!') + return node, string + + +# Return the number of "equation_columns" in `code_lines`. cf. "alignat" +# in http://mirror.ctan.org/macros/latex/required/amsmath/amsldoc.pdf +def tex_equation_columns(rows): + tabs = max(row.count('&') - row.count(r'\&') for row in rows) + if tabs == 0: + return 0 + return int(tabs/2 + 1) + +# >>> tex_equation_columns(['a = b']) +# 0 +# >>> tex_equation_columns(['a &= b']) +# 1 +# >>> tex_equation_columns(['a &= b & a \in S']) +# 2 +# >>> tex_equation_columns(['a &= b & c &= d']) +# 2 + + +# Return dictionary with attributes to style an <mtable> as align environment: +# Not used with HTML. Replaced by CSS rule for "mtable.ams-align" in +# "minimal.css" as "columnalign" is disregarded by Chromium and webkit. +def align_attributes(rows): + atts = {'class': 'ams-align', + 'displaystyle': True} + # get maximal number of non-escaped "next column" markup characters: + tabs = max(row.count('&') - row.count(r'\&') for row in rows) + if tabs: + aligns = ['right', 'left'] * tabs + spacing = ['0', '2em'] * tabs + atts['columnalign'] = ' '.join(aligns[:tabs+1]) + atts['columnspacing'] = ' '.join(spacing[:tabs]) + return atts + +# >>> align_attributes(['a = b']) +# {'class': 'ams-align', 'displaystyle': True} +# >>> align_attributes(['a &= b']) +# {'class': 'ams-align', 'displaystyle': True, 'columnalign': 'right left', 'columnspacing': '0'} +# >>> align_attributes(['a &= b & a \in S']) +# {'class': 'ams-align', 'displaystyle': True, 'columnalign': 'right left right', 'columnspacing': '0 2em'} +# >>> align_attributes(['a &= b & c &= d']) +# {'class': 'ams-align', 'displaystyle': True, 'columnalign': 'right left right left', 'columnspacing': '0 2em 0'} +# >>> align_attributes([r'a &= b & c &= d \& e']) +# {'class': 'ams-align', 'displaystyle': True, 'columnalign': 'right left right left', 'columnspacing': '0 2em 0'} +# >>> align_attributes([r'a &= b & c &= d & e']) +# {'class': 'ams-align', 'displaystyle': True, 'columnalign': 'right left right left right', 'columnspacing': '0 2em 0 2em'} + + +def tex2mathml(tex_math, as_block=False): + """Return string with MathML code corresponding to `tex_math`. + + Set `as_block` to ``True`` for displayed formulas. + """ + # Set up tree + math_tree = math(xmlns='http://www.w3.org/1998/Math/MathML') + node = math_tree + if as_block: + math_tree.set('display', 'block') + rows = toplevel_code(tex_math).split(r'\\') + if len(rows) > 1: + # emulate "align*" environment with a math table + node = mtd() + math_tree.append(mtable(mtr(node), CLASS='ams-align', + displaystyle=True)) + parse_latex_math(node, tex_math) + math_tree.indent_xml() + return math_tree.toxml() + +# >>> print(tex2mathml('3')) +# <math xmlns="http://www.w3.org/1998/Math/MathML"> +# <mn>3</mn> +# </math> +# >>> print(tex2mathml('3', as_block=True)) +# <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +# <mn>3</mn> +# </math> +# >>> print(tex2mathml(r'a & b \\ c & d', as_block=True)) +# <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +# <mtable class="ams-align" displaystyle="true"> +# <mtr> +# <mtd> +# <mi>a</mi> +# </mtd> +# <mtd> +# <mi>b</mi> +# </mtd> +# </mtr> +# <mtr> +# <mtd> +# <mi>c</mi> +# </mtd> +# <mtd> +# <mi>d</mi> +# </mtd> +# </mtr> +# </mtable> +# </math> +# >>> print(tex2mathml(r'a \\ b', as_block=True)) +# <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +# <mtable class="ams-align" displaystyle="true"> +# <mtr> +# <mtd> +# <mi>a</mi> +# </mtd> +# </mtr> +# <mtr> +# <mtd> +# <mi>b</mi> +# </mtd> +# </mtr> +# </mtable> +# </math> + + +# TODO: look up more symbols from tr25, e.g. +# +# +# Table 2.8 Using Vertical Line or Solidus Overlay +# some of the negated forms of mathematical relations that can only be +# encoded by using either U+0338 COMBINING LONG SOLIDUS OVERLAY or U+20D2 +# COMBINING LONG VERTICAL LINE OVERLAY . (For issues with using 0338 in +# MathML, see Section 3.2.7, Combining Marks. +# +# Table 2.9 Variants of Mathematical Symbols using VS1? +# +# Sequence Description +# 0030 + VS1 DIGIT ZERO - short diagonal stroke form +# 2205 + VS1 EMPTY SET - zero with long diagonal stroke overlay form +# 2229 + VS1 INTERSECTION - with serifs +# 222A + VS1 UNION - with serifs +# 2268 + VS1 LESS-THAN BUT NOT EQUAL TO - with vertical stroke +# 2269 + VS1 GREATER-THAN BUT NOT EQUAL TO - with vertical stroke +# 2272 + VS1 LESS-THAN OR EQUIVALENT TO - following the slant of the lower leg +# 2273 + VS1 GREATER-THAN OR EQUIVALENT TO - following the slant of the lower leg +# 228A + VS1 SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members +# 228B + VS1 SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members +# 2293 + VS1 SQUARE CAP - with serifs +# 2294 + VS1 SQUARE CUP - with serifs +# 2295 + VS1 CIRCLED PLUS - with white rim +# 2297 + VS1 CIRCLED TIMES - with white rim +# 229C + VS1 CIRCLED EQUALS - equal sign inside and touching the circle +# 22DA + VS1 LESS-THAN slanted EQUAL TO OR GREATER-THAN +# 22DB + VS1 GREATER-THAN slanted EQUAL TO OR LESS-THAN +# 2A3C + VS1 INTERIOR PRODUCT - tall variant with narrow foot +# 2A3D + VS1 RIGHTHAND INTERIOR PRODUCT - tall variant with narrow foot +# 2A9D + VS1 SIMILAR OR LESS-THAN - following the slant of the upper leg +# 2A9E + VS1 SIMILAR OR GREATER-THAN - following the slant of the upper leg +# 2AAC + VS1 SMALLER THAN OR slanted EQUAL +# 2AAD + VS1 LARGER THAN OR slanted EQUAL +# 2ACB + VS1 SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members +# 2ACC + VS1 SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/math2html.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/math2html.py new file mode 100755 index 00000000..dc94cff7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/math2html.py @@ -0,0 +1,3165 @@ +#! /usr/bin/env python3 +# math2html: convert LaTeX equations to HTML output. +# +# Copyright (C) 2009-2011 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: https://opensource.org/licenses/BSD-2-Clause + +# Based on eLyXer: convert LyX source files to HTML output. +# http://alexfernandez.github.io/elyxer/ + +# Versions: +# 1.2.5 2015-02-26 eLyXer standalone formula conversion to HTML. +# 1.3 2021-06-02 Removed code for conversion of LyX files not +# required for LaTeX math. +# Support for more math commands from the AMS "math-guide". +# 2.0 2021-12-31 Drop 2.7 compatibility code. + +import pathlib +import sys +import unicodedata + +from docutils.utils.math import tex2unichar + + +__version__ = '1.3 (2021-06-02)' + + +class Trace: + "A tracing class" + + debugmode = False + quietmode = False + showlinesmode = False + + prefix = None + + def debug(cls, message): + "Show a debug message" + if not Trace.debugmode or Trace.quietmode: + return + Trace.show(message, sys.stdout) + + def message(cls, message): + "Show a trace message" + if Trace.quietmode: + return + if Trace.prefix and Trace.showlinesmode: + message = Trace.prefix + message + Trace.show(message, sys.stdout) + + def error(cls, message): + "Show an error message" + message = '* ' + message + if Trace.prefix and Trace.showlinesmode: + message = Trace.prefix + message + Trace.show(message, sys.stderr) + + def show(cls, message, channel): + "Show a message out of a channel" + channel.write(message + '\n') + + debug = classmethod(debug) + message = classmethod(message) + error = classmethod(error) + show = classmethod(show) + + +class ContainerConfig: + "Configuration class from elyxer.config file" + + extracttext = { + 'allowed': ['FormulaConstant'], + 'extracted': ['AlphaCommand', + 'Bracket', + 'BracketCommand', + 'CombiningFunction', + 'EmptyCommand', + 'FontFunction', + 'Formula', + 'FormulaNumber', + 'FormulaSymbol', + 'OneParamFunction', + 'OversetFunction', + 'RawText', + 'SpacedCommand', + 'SymbolFunction', + 'TextFunction', + 'UndersetFunction', + ], + } + + +class EscapeConfig: + "Configuration class from elyxer.config file" + + chars = { + '\n': '', + "'": '’', + '`': '‘', + } + + entities = { + '&': '&', + '<': '<', + '>': '>', + } + + +class FormulaConfig: + "Configuration class from elyxer.config file" + + alphacommands = { + '\\AmS': '<span class="textsc">AmS</span>', + '\\AA': 'Å', + '\\AE': 'Æ', + '\\DH': 'Ð', + '\\L': 'Ł', + '\\O': 'Ø', + '\\OE': 'Œ', + '\\TH': 'Þ', + '\\aa': 'å', + '\\ae': 'æ', + '\\dh': 'ð', + '\\i': 'ı', + '\\j': 'ȷ', + '\\l': 'ł', + '\\o': 'ø', + '\\oe': 'œ', + '\\ss': 'ß', + '\\th': 'þ', + '\\hbar': 'ħ', # cf. \hslash: ℏ in tex2unichar + } + for key, value in tex2unichar.mathalpha.items(): + alphacommands['\\'+key] = value + + array = { + 'begin': r'\begin', + 'cellseparator': '&', + 'end': r'\end', + 'rowseparator': r'\\', + } + + bigbrackets = {'(': ['⎛', '⎜', '⎝'], + ')': ['⎞', '⎟', '⎠'], + '[': ['⎡', '⎢', '⎣'], + ']': ['⎤', '⎥', '⎦'], + '{': ['⎧', '⎪', '⎨', '⎩'], + '}': ['⎫', '⎪', '⎬', '⎭'], + # TODO: 2-row brackets with ⎰⎱ (\lmoustache \rmoustache) + '|': ['|'], # 007C VERTICAL LINE + # '|': ['⎮'], # 23AE INTEGRAL EXTENSION + # '|': ['⎪'], # 23AA CURLY BRACKET EXTENSION + '‖': ['‖'], # 2016 DOUBLE VERTICAL LINE + # '∥': ['∥'], # 2225 PARALLEL TO + } + + bracketcommands = { + '\\left': 'span class="stretchy"', + '\\left.': '<span class="leftdot"></span>', + '\\middle': 'span class="stretchy"', + '\\right': 'span class="stretchy"', + '\\right.': '<span class="rightdot"></span>', + } + + combiningfunctions = { + "\\'": '\u0301', # x́ + '\\"': '\u0308', # ẍ + '\\^': '\u0302', # x̂ + '\\`': '\u0300', # x̀ + '\\~': '\u0303', # x̃ + '\\c': '\u0327', # x̧ + '\\r': '\u030a', # x̊ + '\\s': '\u0329', # x̩ + '\\textcircled': '\u20dd', # x⃝ + '\\textsubring': '\u0325', # x̥ + '\\v': '\u030c', # x̌ + } + for key, value in tex2unichar.mathaccent.items(): + combiningfunctions['\\'+key] = value + + commands = { + '\\\\': '<br/>', + '\\\n': ' ', # escaped whitespace + '\\\t': ' ', # escaped whitespace + '\\centerdot': '\u2B1D', # BLACK VERY SMALL SQUARE, mathbin + '\\colon': ': ', + '\\copyright': '©', + '\\dotminus': '∸', + '\\dots': '…', + '\\dotsb': '⋯', + '\\dotsc': '…', + '\\dotsi': '⋯', + '\\dotsm': '⋯', + '\\dotso': '…', + '\\euro': '€', + '\\guillemotleft': '«', + '\\guillemotright': '»', + '\\lVert': '‖', + '\\Arrowvert': '‖', + '\\lvert': '|', + '\\newline': '<br/>', + '\\nobreakspace': ' ', + '\\nolimits': '', + '\\nonumber': '', + '\\qquad': ' ', + '\\rVert': '‖', + '\\rvert': '|', + '\\textasciicircum': '^', + '\\textasciitilde': '~', + '\\textbackslash': '\\', + '\\textcopyright': '©', + '\\textdegree': '°', + '\\textellipsis': '…', + '\\textemdash': '—', + '\\textendash': '—', + '\\texteuro': '€', + '\\textgreater': '>', + '\\textless': '<', + '\\textordfeminine': 'ª', + '\\textordmasculine': 'º', + '\\textquotedblleft': '“', + '\\textquotedblright': '”', + '\\textquoteright': '’', + '\\textregistered': '®', + '\\textrightarrow': '→', + '\\textsection': '§', + '\\texttrademark': '™', + '\\texttwosuperior': '²', + '\\textvisiblespace': ' ', + '\\thickspace': '<span class="thickspace"> </span>', # 5/13 em + '\\;': '<span class="thickspace"> </span>', # 5/13 em + '\\triangle': '\u25B3', # WHITE UP-POINTING TRIANGLE, mathord + '\\triangledown': '\u25BD', # WHITE DOWN-POINTING TRIANGLE, mathord + '\\varnothing': '\u2300', # ⌀ DIAMETER SIGN + # functions + '\\Pr': 'Pr', + '\\arccos': 'arccos', + '\\arcsin': 'arcsin', + '\\arctan': 'arctan', + '\\arg': 'arg', + '\\cos': 'cos', + '\\cosh': 'cosh', + '\\cot': 'cot', + '\\coth': 'coth', + '\\csc': 'csc', + '\\deg': 'deg', + '\\det': 'det', + '\\dim': 'dim', + '\\exp': 'exp', + '\\gcd': 'gcd', + '\\hom': 'hom', + '\\injlim': 'inj lim', + '\\ker': 'ker', + '\\lg': 'lg', + '\\liminf': 'lim inf', + '\\limsup': 'lim sup', + '\\ln': 'ln', + '\\log': 'log', + '\\projlim': 'proj lim', + '\\sec': 'sec', + '\\sin': 'sin', + '\\sinh': 'sinh', + '\\tan': 'tan', + '\\tanh': 'tanh', + } + cmddict = {} + cmddict.update(tex2unichar.mathbin) # TODO: spacing around binary operators + cmddict.update(tex2unichar.mathopen) + cmddict.update(tex2unichar.mathclose) + cmddict.update(tex2unichar.mathfence) + cmddict.update(tex2unichar.mathord) + cmddict.update(tex2unichar.mathpunct) + cmddict.update(tex2unichar.space) + commands.update(('\\' + key, value) for key, value in cmddict.items()) + + oversetfunctions = { + # math accents (cf. combiningfunctions) + # '\\acute': '´', + '\\bar': '‒', # FIGURE DASH + # '\\breve': '˘', + # '\\check': 'ˇ', + '\\dddot': '<span class="smallsymbol">⋯</span>', + # '\\ddot': '··', # ¨ too high + # '\\dot': '·', + # '\\grave': '`', + # '\\hat': '^', + # '\\mathring': '˚', + # '\\tilde': '~', + '\\vec': '<span class="smallsymbol">→</span>', + # embellishments + '\\overleftarrow': '⟵', + '\\overleftrightarrow': '⟷', + '\\overrightarrow': '⟶', + '\\widehat': '^', + '\\widetilde': '~', + } + + undersetfunctions = { + '\\underleftarrow': '⟵', + '\\underleftrightarrow': '⟷', + '\\underrightarrow': '⟶', + } + + endings = { + 'bracket': '}', + 'complex': '\\]', + 'endafter': '}', + 'endbefore': '\\end{', + 'squarebracket': ']', + } + + environments = { + 'align': ['r', 'l'], + 'eqnarray': ['r', 'c', 'l'], + 'gathered': ['l', 'l'], + 'smallmatrix': ['c', 'c'], + } + + fontfunctions = { + '\\boldsymbol': 'b', '\\mathbb': 'span class="blackboard"', + '\\mathbb{A}': '𝔸', '\\mathbb{B}': '𝔹', '\\mathbb{C}': 'ℂ', + '\\mathbb{D}': '𝔻', '\\mathbb{E}': '𝔼', '\\mathbb{F}': '𝔽', + '\\mathbb{G}': '𝔾', '\\mathbb{H}': 'ℍ', '\\mathbb{J}': '𝕁', + '\\mathbb{K}': '𝕂', '\\mathbb{L}': '𝕃', '\\mathbb{N}': 'ℕ', + '\\mathbb{O}': '𝕆', '\\mathbb{P}': 'ℙ', '\\mathbb{Q}': 'ℚ', + '\\mathbb{R}': 'ℝ', '\\mathbb{S}': '𝕊', '\\mathbb{T}': '𝕋', + '\\mathbb{W}': '𝕎', '\\mathbb{Z}': 'ℤ', '\\mathbf': 'b', + '\\mathcal': 'span class="scriptfont"', + '\\mathcal{B}': 'ℬ', '\\mathcal{E}': 'ℰ', '\\mathcal{F}': + 'ℱ', '\\mathcal{H}': 'ℋ', '\\mathcal{I}': 'ℐ', + '\\mathcal{L}': 'ℒ', '\\mathcal{M}': 'ℳ', '\\mathcal{R}': 'ℛ', + '\\mathfrak': 'span class="fraktur"', + '\\mathfrak{C}': 'ℭ', '\\mathfrak{F}': '𝔉', '\\mathfrak{H}': 'ℌ', + '\\mathfrak{I}': 'ℑ', '\\mathfrak{R}': 'ℜ', '\\mathfrak{Z}': 'ℨ', + '\\mathit': 'i', + '\\mathring{A}': 'Å', '\\mathring{U}': 'Ů', + '\\mathring{a}': 'å', '\\mathring{u}': 'ů', '\\mathring{w}': 'ẘ', + '\\mathring{y}': 'ẙ', + '\\mathrm': 'span class="mathrm"', + '\\mathscr': 'span class="mathscr"', + '\\mathscr{B}': 'ℬ', '\\mathscr{E}': 'ℰ', '\\mathscr{F}': 'ℱ', + '\\mathscr{H}': 'ℋ', '\\mathscr{I}': 'ℐ', '\\mathscr{L}': 'ℒ', + '\\mathscr{M}': 'ℳ', '\\mathscr{R}': 'ℛ', + '\\mathsf': 'span class="mathsf"', + '\\mathtt': 'span class="mathtt"', + '\\operatorname': 'span class="mathrm"', + } + + hybridfunctions = { + '\\addcontentsline': ['{$p!}{$q!}{$r!}', 'f0{}', 'ignored'], + '\\addtocontents': ['{$p!}{$q!}', 'f0{}', 'ignored'], + '\\backmatter': ['', 'f0{}', 'ignored'], + '\\binom': ['{$1}{$2}', 'f2{(}f0{f1{$1}f1{$2}}f2{)}', 'span class="binom"', 'span class="binomstack"', 'span class="bigdelimiter size2"'], + '\\boxed': ['{$1}', 'f0{$1}', 'span class="boxed"'], + '\\cfrac': ['[$p!]{$1}{$2}', 'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', 'span class="fullfraction"', 'span class="numerator align-$p"', 'span class="denominator"', 'span class="ignored"'], + '\\color': ['{$p!}{$1}', 'f0{$1}', 'span style="color: $p;"'], + '\\colorbox': ['{$p!}{$1}', 'f0{$1}', 'span class="colorbox" style="background: $p;"'], + '\\dbinom': ['{$1}{$2}', '(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})', 'span class="binomial"', 'span class="binomrow"', 'span class="binomcell"'], + '\\dfrac': ['{$1}{$2}', 'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', 'span class="fullfraction"', 'span class="numerator"', 'span class="denominator"', 'span class="ignored"'], + '\\displaystyle': ['{$1}', 'f0{$1}', 'span class="displaystyle"'], + '\\fancyfoot': ['[$p!]{$q!}', 'f0{}', 'ignored'], + '\\fancyhead': ['[$p!]{$q!}', 'f0{}', 'ignored'], + '\\fbox': ['{$1}', 'f0{$1}', 'span class="fbox"'], + '\\fboxrule': ['{$p!}', 'f0{}', 'ignored'], + '\\fboxsep': ['{$p!}', 'f0{}', 'ignored'], + '\\fcolorbox': ['{$p!}{$q!}{$1}', 'f0{$1}', 'span class="boxed" style="border-color: $p; background: $q;"'], + '\\frac': ['{$1}{$2}', 'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', 'span class="fraction"', 'span class="numerator"', 'span class="denominator"', 'span class="ignored"'], + '\\framebox': ['[$p!][$q!]{$1}', 'f0{$1}', 'span class="framebox align-$q" style="width: $p;"'], + '\\frontmatter': ['', 'f0{}', 'ignored'], + '\\href': ['[$o]{$u!}{$t!}', 'f0{$t}', 'a href="$u"'], + '\\hspace': ['{$p!}', 'f0{ }', 'span class="hspace" style="width: $p;"'], + '\\leftroot': ['{$p!}', 'f0{ }', 'span class="leftroot" style="width: $p;px"'], + # TODO: convert 1 mu to 1/18 em + # '\\mspace': ['{$p!}', 'f0{ }', 'span class="hspace" style="width: $p;"'], + '\\nicefrac': ['{$1}{$2}', 'f0{f1{$1}⁄f2{$2}}', 'span class="fraction"', 'sup class="numerator"', 'sub class="denominator"', 'span class="ignored"'], + '\\parbox': ['[$p!]{$w!}{$1}', 'f0{1}', 'div class="Boxed" style="width: $w;"'], + '\\raisebox': ['{$p!}{$1}', 'f0{$1.font}', 'span class="raisebox" style="vertical-align: $p;"'], + '\\renewenvironment': ['{$1!}{$2!}{$3!}', ''], + '\\rule': ['[$v!]{$w!}{$h!}', 'f0/', 'hr class="line" style="width: $w; height: $h;"'], + '\\scriptscriptstyle': ['{$1}', 'f0{$1}', 'span class="scriptscriptstyle"'], + '\\scriptstyle': ['{$1}', 'f0{$1}', 'span class="scriptstyle"'], + # TODO: increase √-size with argument (\frac in display mode, ...) + '\\sqrt': ['[$0]{$1}', 'f0{f1{$0}f2{√}f4{(}f3{$1}f4{)}}', 'span class="sqrt"', 'sup class="root"', 'span class="radical"', 'span class="root"', 'span class="ignored"'], + '\\stackrel': ['{$1}{$2}', 'f0{f1{$1}f2{$2}}', 'span class="stackrel"', 'span class="upstackrel"', 'span class="downstackrel"'], + '\\tbinom': ['{$1}{$2}', '(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})', 'span class="binomial"', 'span class="binomrow"', 'span class="binomcell"'], + '\\tfrac': ['{$1}{$2}', 'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', 'span class="textfraction"', 'span class="numerator"', 'span class="denominator"', 'span class="ignored"'], + '\\textcolor': ['{$p!}{$1}', 'f0{$1}', 'span style="color: $p;"'], + '\\textstyle': ['{$1}', 'f0{$1}', 'span class="textstyle"'], + '\\thispagestyle': ['{$p!}', 'f0{}', 'ignored'], + '\\unit': ['[$0]{$1}', '$0f0{$1.font}', 'span class="unit"'], + '\\unitfrac': ['[$0]{$1}{$2}', '$0f0{f1{$1.font}⁄f2{$2.font}}', 'span class="fraction"', 'sup class="unit"', 'sub class="unit"'], + '\\uproot': ['{$p!}', 'f0{ }', 'span class="uproot" style="width: $p;px"'], + '\\url': ['{$u!}', 'f0{$u}', 'a href="$u"'], + '\\vspace': ['{$p!}', 'f0{ }', 'span class="vspace" style="height: $p;"'], + } + + hybridsizes = { + '\\binom': '$1+$2', '\\cfrac': '$1+$2', '\\dbinom': '$1+$2+1', + '\\dfrac': '$1+$2', '\\frac': '$1+$2', '\\tbinom': '$1+$2+1', + } + + labelfunctions = { + '\\label': 'a name="#"', + } + + limitcommands = { + '\\biginterleave': '⫼', + '\\inf': 'inf', + '\\lim': 'lim', + '\\max': 'max', + '\\min': 'min', + '\\sup': 'sup', + '\\ointop': '<span class="bigoperator integral">∮</span>', + '\\bigcap': '<span class="bigoperator">⋂</span>', + '\\bigcup': '<span class="bigoperator">⋃</span>', + '\\bigodot': '<span class="bigoperator">⨀</span>', + '\\bigoplus': '<span class="bigoperator">⨁</span>', + '\\bigotimes': '<span class="bigoperator">⨂</span>', + '\\bigsqcap': '<span class="bigoperator">⨅</span>', + '\\bigsqcup': '<span class="bigoperator">⨆</span>', + '\\biguplus': '<span class="bigoperator">⨄</span>', + '\\bigvee': '<span class="bigoperator">⋁</span>', + '\\bigwedge': '<span class="bigoperator">⋀</span>', + '\\coprod': '<span class="bigoperator">∐</span>', + '\\intop': '<span class="bigoperator integral">∫</span>', + '\\prod': '<span class="bigoperator">∏</span>', + '\\sum': '<span class="bigoperator">∑</span>', + '\\varprod': '<span class="bigoperator">⨉</span>', + '\\zcmp': '⨟', '\\zhide': '⧹', '\\zpipe': '⨠', '\\zproject': '⨡', + # integrals have limits in index position with LaTeX default settings + # TODO: move to commands? + '\\int': '<span class="bigoperator integral">∫</span>', + '\\iint': '<span class="bigoperator integral">∬</span>', + '\\iiint': '<span class="bigoperator integral">∭</span>', + '\\iiiint': '<span class="bigoperator integral">⨌</span>', + '\\fint': '<span class="bigoperator integral">⨏</span>', + '\\idotsint': '<span class="bigoperator integral">∫⋯∫</span>', + '\\oint': '<span class="bigoperator integral">∮</span>', + '\\oiint': '<span class="bigoperator integral">∯</span>', + '\\oiiint': '<span class="bigoperator integral">∰</span>', + '\\ointclockwise': '<span class="bigoperator integral">∲</span>', + '\\ointctrclockwise': '<span class="bigoperator integral">∳</span>', + '\\smallint': '<span class="smallsymbol integral">∫</span>', + '\\sqint': '<span class="bigoperator integral">⨖</span>', + '\\varointclockwise': '<span class="bigoperator integral">∲</span>', + } + + modified = { + '\n': '', ' ': '', '$': '', '&': ' ', '\'': '’', '+': '\u2009+\u2009', + ',': ',\u2009', '-': '\u2009−\u2009', '/': '\u2009⁄\u2009', ':': ' : ', '<': '\u2009<\u2009', + '=': '\u2009=\u2009', '>': '\u2009>\u2009', '@': '', '~': '\u00a0', + } + + onefunctions = { + '\\big': 'span class="bigdelimiter size1"', + '\\bigl': 'span class="bigdelimiter size1"', + '\\bigr': 'span class="bigdelimiter size1"', + '\\Big': 'span class="bigdelimiter size2"', + '\\Bigl': 'span class="bigdelimiter size2"', + '\\Bigr': 'span class="bigdelimiter size2"', + '\\bigg': 'span class="bigdelimiter size3"', + '\\biggl': 'span class="bigdelimiter size3"', + '\\biggr': 'span class="bigdelimiter size3"', + '\\Bigg': 'span class="bigdelimiter size4"', + '\\Biggl': 'span class="bigdelimiter size4"', + '\\Biggr': 'span class="bigdelimiter size4"', + # '\\bar': 'span class="bar"', + '\\begin{array}': 'span class="arraydef"', + '\\centering': 'span class="align-center"', + '\\ensuremath': 'span class="ensuremath"', + '\\hphantom': 'span class="phantom"', + '\\noindent': 'span class="noindent"', + '\\overbrace': 'span class="overbrace"', + '\\overline': 'span class="overline"', + '\\phantom': 'span class="phantom"', + '\\underbrace': 'span class="underbrace"', + '\\underline': '', + '\\vphantom': 'span class="phantom"', + } + + # relations (put additional space before and after the symbol) + spacedcommands = { + # negated symbols without pre-composed Unicode character + '\\nleqq': '\u2266\u0338', # ≦̸ + '\\ngeqq': '\u2267\u0338', # ≧̸ + '\\nleqslant': '\u2a7d\u0338', # ⩽̸ + '\\ngeqslant': '\u2a7e\u0338', # ⩾̸ + '\\nsubseteqq': '\u2AC5\u0338', # ⫅̸ + '\\nsupseteqq': '\u2AC6\u0338', # ⫆̸ + '\\nsqsubset': '\u2276\u228F', # ⊏̸ + # modified glyphs + '\\shortmid': '<span class="smallsymbol">∣</span>', + '\\shortparallel': '<span class="smallsymbol">∥</span>', + '\\nshortmid': '<span class="smallsymbol">∤</span>', + '\\nshortparallel': '<span class="smallsymbol">∦</span>', + '\\smallfrown': '<span class="smallsymbol">⌢</span>', + '\\smallsmile': '<span class="smallsymbol">⌣</span>', + '\\thickapprox': '<span class="boldsymbol">≈</span>', + '\\thicksim': '<span class="boldsymbol">∼</span>', + '\\varpropto': '<span class="mathsf">\u221d</span>', # ∝ PROPORTIONAL TO + } + for key, value in tex2unichar.mathrel.items(): + spacedcommands['\\'+key] = value + starts = { + 'beginafter': '}', 'beginbefore': '\\begin{', 'bracket': '{', + 'command': '\\', 'comment': '%', 'complex': '\\[', 'simple': '$', + 'squarebracket': '[', 'unnumbered': '*', + } + + symbolfunctions = { + '^': 'sup', '_': 'sub', + } + + textfunctions = { + '\\mbox': 'span class="mbox"', + '\\text': 'span class="text"', + '\\textbf': 'span class="textbf"', + '\\textit': 'span class="textit"', + '\\textnormal': 'span class="textnormal"', + '\\textrm': 'span class="textrm"', + '\\textsc': 'span class="textsc"', + '\\textsf': 'span class="textsf"', + '\\textsl': 'span class="textsl"', + '\\texttt': 'span class="texttt"', + '\\textup': 'span class="normal"', + } + + unmodified = { + 'characters': ['.', '*', '€', '(', ')', '[', ']', + '·', '!', ';', '|', '§', '"', '?'], + } + + +class CommandLineParser: + "A parser for runtime options" + + def __init__(self, options): + self.options = options + + def parseoptions(self, args): + "Parse command line options" + if len(args) == 0: + return None + while len(args) > 0 and args[0].startswith('--'): + key, value = self.readoption(args) + if not key: + return 'Option ' + value + ' not recognized' + if not value: + return 'Option ' + key + ' needs a value' + setattr(self.options, key, value) + return None + + def readoption(self, args): + "Read the key and value for an option" + arg = args[0][2:] + del args[0] + if '=' in arg: + key = self.readequalskey(arg, args) + else: + key = arg.replace('-', '') + if not hasattr(self.options, key): + return None, key + current = getattr(self.options, key) + if isinstance(current, bool): + return key, True + # read value + if len(args) == 0: + return key, None + if args[0].startswith('"'): + initial = args[0] + del args[0] + return key, self.readquoted(args, initial) + value = args[0].decode('utf-8') + del args[0] + if isinstance(current, list): + current.append(value) + return key, current + return key, value + + def readquoted(self, args, initial): + "Read a value between quotes" + Trace.error('Oops') + value = initial[1:] + while len(args) > 0 and not args[0].endswith('"') and not args[0].startswith('--'): + Trace.error('Appending ' + args[0]) + value += ' ' + args[0] + del args[0] + if len(args) == 0 or args[0].startswith('--'): + return None + value += ' ' + args[0:-1] + return value + + def readequalskey(self, arg, args): + "Read a key using equals" + split = arg.split('=', 1) + key = split[0] + value = split[1] + args.insert(0, value) + return key + + +class Options: + "A set of runtime options" + + location = None + + debug = False + quiet = False + version = False + help = False + simplemath = False + showlines = True + + branches = {} + + def parseoptions(self, args): + "Parse command line options" + Options.location = args[0] + del args[0] + parser = CommandLineParser(Options) + result = parser.parseoptions(args) + if result: + Trace.error(result) + self.usage() + self.processoptions() + + def processoptions(self): + "Process all options parsed." + if Options.help: + self.usage() + if Options.version: + self.showversion() + # set in Trace if necessary + for param in dir(Trace): + if param.endswith('mode'): + setattr(Trace, param, getattr(self, param[:-4])) + + def usage(self): + "Show correct usage" + Trace.error(f'Usage: {pathlib.Path(Options.location).parent}' + ' [options] "input string"') + Trace.error('Convert input string with LaTeX math to MathML') + self.showoptions() + + def showoptions(self): + "Show all possible options" + Trace.error(' --help: show this online help') + Trace.error(' --quiet: disables all runtime messages') + Trace.error(' --debug: enable debugging messages (for developers)') + Trace.error(' --version: show version number and release date') + Trace.error(' --simplemath: do not generate fancy math constructions') + sys.exit() + + def showversion(self): + "Return the current eLyXer version string" + Trace.error('math2html '+__version__) + sys.exit() + + +class Cloner: + "An object used to clone other objects." + + def clone(cls, original): + "Return an exact copy of an object." + "The original object must have an empty constructor." + return cls.create(original.__class__) + + def create(cls, type): + "Create an object of a given class." + clone = type.__new__(type) + clone.__init__() + return clone + + clone = classmethod(clone) + create = classmethod(create) + + +class ContainerExtractor: + """A class to extract certain containers. + + The config parameter is a map containing three lists: + allowed, copied and extracted. + Each of the three is a list of class names for containers. + Allowed containers are included as is into the result. + Cloned containers are cloned and placed into the result. + Extracted containers are looked into. + All other containers are silently ignored. + """ + + def __init__(self, config): + self.allowed = config['allowed'] + self.extracted = config['extracted'] + + def extract(self, container): + "Extract a group of selected containers from a container." + list = [] + locate = lambda c: c.__class__.__name__ in self.allowed + recursive = lambda c: c.__class__.__name__ in self.extracted + process = lambda c: self.process(c, list) + container.recursivesearch(locate, recursive, process) + return list + + def process(self, container, list): + "Add allowed containers." + name = container.__class__.__name__ + if name in self.allowed: + list.append(container) + else: + Trace.error('Unknown container class ' + name) + + def safeclone(self, container): + "Return a new container with contents only in a safe list, recursively." + clone = Cloner.clone(container) + clone.output = container.output + clone.contents = self.extract(container) + return clone + + +class Parser: + "A generic parser" + + def __init__(self): + self.begin = 0 + self.parameters = {} + + def parseheader(self, reader): + "Parse the header" + header = reader.currentline().split() + reader.nextline() + self.begin = reader.linenumber + return header + + def parseparameter(self, reader): + "Parse a parameter" + split = reader.currentline().strip().split(' ', 1) + reader.nextline() + if len(split) == 0: + return + key = split[0] + if len(split) == 1: + self.parameters[key] = True + return + if '"' not in split[1]: + self.parameters[key] = split[1].strip() + return + doublesplit = split[1].split('"') + self.parameters[key] = doublesplit[1] + + def parseending(self, reader, process): + "Parse until the current ending is found" + if not self.ending: + Trace.error('No ending for ' + str(self)) + return + while not reader.currentline().startswith(self.ending): + process() + + def parsecontainer(self, reader, contents): + container = self.factory.createcontainer(reader) + if container: + container.parent = self.parent + contents.append(container) + + def __str__(self): + "Return a description" + return self.__class__.__name__ + ' (' + str(self.begin) + ')' + + +class LoneCommand(Parser): + "A parser for just one command line" + + def parse(self, reader): + "Read nothing" + return [] + + +class TextParser(Parser): + "A parser for a command and a bit of text" + + stack = [] + + def __init__(self, container): + Parser.__init__(self) + self.ending = None + if container.__class__.__name__ in ContainerConfig.endings: + self.ending = ContainerConfig.endings[container.__class__.__name__] + self.endings = [] + + def parse(self, reader): + "Parse lines as long as they are text" + TextParser.stack.append(self.ending) + self.endings = TextParser.stack + [ContainerConfig.endings['Layout'], + ContainerConfig.endings['Inset'], + self.ending] + contents = [] + while not self.isending(reader): + self.parsecontainer(reader, contents) + return contents + + def isending(self, reader): + "Check if text is ending" + current = reader.currentline().split() + if len(current) == 0: + return False + if current[0] in self.endings: + if current[0] in TextParser.stack: + TextParser.stack.remove(current[0]) + else: + TextParser.stack = [] + return True + return False + + +class ExcludingParser(Parser): + "A parser that excludes the final line" + + def parse(self, reader): + "Parse everything up to (and excluding) the final line" + contents = [] + self.parseending(reader, lambda: self.parsecontainer(reader, contents)) + return contents + + +class BoundedParser(ExcludingParser): + "A parser bound by a final line" + + def parse(self, reader): + "Parse everything, including the final line" + contents = ExcludingParser.parse(self, reader) + # skip last line + reader.nextline() + return contents + + +class BoundedDummy(Parser): + "A bound parser that ignores everything" + + def parse(self, reader): + "Parse the contents of the container" + self.parseending(reader, lambda: reader.nextline()) + # skip last line + reader.nextline() + return [] + + +class StringParser(Parser): + "Parses just a string" + + def parseheader(self, reader): + "Do nothing, just take note" + self.begin = reader.linenumber + 1 + return [] + + def parse(self, reader): + "Parse a single line" + contents = reader.currentline() + reader.nextline() + return contents + + +class ContainerOutput: + "The generic HTML output for a container." + + def gethtml(self, container): + "Show an error." + Trace.error('gethtml() not implemented for ' + str(self)) + + def isempty(self): + "Decide if the output is empty: by default, not empty." + return False + + +class EmptyOutput(ContainerOutput): + + def gethtml(self, container): + "Return empty HTML code." + return [] + + def isempty(self): + "This output is particularly empty." + return True + + +class FixedOutput(ContainerOutput): + "Fixed output" + + def gethtml(self, container): + "Return constant HTML code" + return container.html + + +class ContentsOutput(ContainerOutput): + "Outputs the contents converted to HTML" + + def gethtml(self, container): + "Return the HTML code" + html = [] + if container.contents is None: + return html + for element in container.contents: + if not hasattr(element, 'gethtml'): + Trace.error('No html in ' + element.__class__.__name__ + ': ' + str(element)) + return html + html += element.gethtml() + return html + + +class TaggedOutput(ContentsOutput): + "Outputs an HTML tag surrounding the contents." + + tag = None + breaklines = False + empty = False + + def settag(self, tag, breaklines=False, empty=False): + "Set the value for the tag and other attributes." + self.tag = tag + if breaklines: + self.breaklines = breaklines + if empty: + self.empty = empty + return self + + def setbreaklines(self, breaklines): + "Set the value for breaklines." + self.breaklines = breaklines + return self + + def gethtml(self, container): + "Return the HTML code." + if self.empty: + return [self.selfclosing(container)] + html = [self.open(container)] + html += ContentsOutput.gethtml(self, container) + html.append(self.close(container)) + return html + + def open(self, container): + "Get opening line." + if not self.checktag(container): + return '' + open = '<' + self.tag + '>' + if self.breaklines: + return open + '\n' + return open + + def close(self, container): + "Get closing line." + if not self.checktag(container): + return '' + close = '</' + self.tag.split()[0] + '>' + if self.breaklines: + return '\n' + close + '\n' + return close + + def selfclosing(self, container): + "Get self-closing line." + if not self.checktag(container): + return '' + selfclosing = '<' + self.tag + '/>' + if self.breaklines: + return selfclosing + '\n' + return selfclosing + + def checktag(self, container): + "Check that the tag is valid." + if not self.tag: + Trace.error('No tag in ' + str(container)) + return False + if self.tag == '': + return False + return True + + +class FilteredOutput(ContentsOutput): + "Returns the output in the contents, but filtered:" + "some strings are replaced by others." + + def __init__(self): + "Initialize the filters." + self.filters = [] + + def addfilter(self, original, replacement): + "Add a new filter: replace the original by the replacement." + self.filters.append((original, replacement)) + + def gethtml(self, container): + "Return the HTML code" + result = [] + html = ContentsOutput.gethtml(self, container) + for line in html: + result.append(self.filter(line)) + return result + + def filter(self, line): + "Filter a single line with all available filters." + for original, replacement in self.filters: + if original in line: + line = line.replace(original, replacement) + return line + + +class StringOutput(ContainerOutput): + "Returns a bare string as output" + + def gethtml(self, container): + "Return a bare string" + return [container.string] + + +class Globable: + """A bit of text which can be globbed (lumped together in bits). + Methods current(), skipcurrent(), checkfor() and isout() have to be + implemented by subclasses.""" + + leavepending = False + + def __init__(self): + self.endinglist = EndingList() + + def checkbytemark(self): + "Check for a Unicode byte mark and skip it." + if self.finished(): + return + if ord(self.current()) == 0xfeff: + self.skipcurrent() + + def isout(self): + "Find out if we are out of the position yet." + Trace.error('Unimplemented isout()') + return True + + def current(self): + "Return the current character." + Trace.error('Unimplemented current()') + return '' + + def checkfor(self, string): + "Check for the given string in the current position." + Trace.error('Unimplemented checkfor()') + return False + + def finished(self): + "Find out if the current text has finished." + if self.isout(): + if not self.leavepending: + self.endinglist.checkpending() + return True + return self.endinglist.checkin(self) + + def skipcurrent(self): + "Return the current character and skip it." + Trace.error('Unimplemented skipcurrent()') + return '' + + def glob(self, currentcheck): + "Glob a bit of text that satisfies a check on the current char." + glob = '' + while not self.finished() and currentcheck(): + glob += self.skipcurrent() + return glob + + def globalpha(self): + "Glob a bit of alpha text" + return self.glob(lambda: self.current().isalpha()) + + def globnumber(self): + "Glob a row of digits." + return self.glob(lambda: self.current().isdigit()) + + def isidentifier(self): + "Return if the current character is alphanumeric or _." + if self.current().isalnum() or self.current() == '_': + return True + return False + + def globidentifier(self): + "Glob alphanumeric and _ symbols." + return self.glob(self.isidentifier) + + def isvalue(self): + "Return if the current character is a value character:" + "not a bracket or a space." + if self.current().isspace(): + return False + if self.current() in '{}()': + return False + return True + + def globvalue(self): + "Glob a value: any symbols but brackets." + return self.glob(self.isvalue) + + def skipspace(self): + "Skip all whitespace at current position." + return self.glob(lambda: self.current().isspace()) + + def globincluding(self, magicchar): + "Glob a bit of text up to (including) the magic char." + glob = self.glob(lambda: self.current() != magicchar) + magicchar + self.skip(magicchar) + return glob + + def globexcluding(self, excluded): + "Glob a bit of text up until (excluding) any excluded character." + return self.glob(lambda: self.current() not in excluded) + + def pushending(self, ending, optional=False): + "Push a new ending to the bottom" + self.endinglist.add(ending, optional) + + def popending(self, expected=None): + "Pop the ending found at the current position" + if self.isout() and self.leavepending: + return expected + ending = self.endinglist.pop(self) + if expected and expected != ending: + Trace.error('Expected ending ' + expected + ', got ' + ending) + self.skip(ending) + return ending + + def nextending(self): + "Return the next ending in the queue." + nextending = self.endinglist.findending(self) + if not nextending: + return None + return nextending.ending + + +class EndingList: + "A list of position endings" + + def __init__(self): + self.endings = [] + + def add(self, ending, optional=False): + "Add a new ending to the list" + self.endings.append(PositionEnding(ending, optional)) + + def pickpending(self, pos): + "Pick any pending endings from a parse position." + self.endings += pos.endinglist.endings + + def checkin(self, pos): + "Search for an ending" + if self.findending(pos): + return True + return False + + def pop(self, pos): + "Remove the ending at the current position" + if pos.isout(): + Trace.error('No ending out of bounds') + return '' + ending = self.findending(pos) + if not ending: + Trace.error('No ending at ' + pos.current()) + return '' + for each in reversed(self.endings): + self.endings.remove(each) + if each == ending: + return each.ending + elif not each.optional: + Trace.error('Removed non-optional ending ' + each) + Trace.error('No endings left') + return '' + + def findending(self, pos): + "Find the ending at the current position" + if len(self.endings) == 0: + return None + for index, ending in enumerate(reversed(self.endings)): + if ending.checkin(pos): + return ending + if not ending.optional: + return None + return None + + def checkpending(self): + "Check if there are any pending endings" + if len(self.endings) != 0: + Trace.error('Pending ' + str(self) + ' left open') + + def __str__(self): + "Printable representation" + string = 'endings [' + for ending in self.endings: + string += str(ending) + ',' + if len(self.endings) > 0: + string = string[:-1] + return string + ']' + + +class PositionEnding: + "An ending for a parsing position" + + def __init__(self, ending, optional): + self.ending = ending + self.optional = optional + + def checkin(self, pos): + "Check for the ending" + return pos.checkfor(self.ending) + + def __str__(self): + "Printable representation" + string = 'Ending ' + self.ending + if self.optional: + string += ' (optional)' + return string + + +class Position(Globable): + """A position in a text to parse. + Including those in Globable, functions to implement by subclasses are: + skip(), identifier(), extract(), isout() and current().""" + + def __init__(self): + Globable.__init__(self) + + def skip(self, string): + "Skip a string" + Trace.error('Unimplemented skip()') + + def identifier(self): + "Return an identifier for the current position." + Trace.error('Unimplemented identifier()') + return 'Error' + + def extract(self, length): + "Extract the next string of the given length, or None if not enough text," + "without advancing the parse position." + Trace.error('Unimplemented extract()') + return None + + def checkfor(self, string): + "Check for a string at the given position." + return string == self.extract(len(string)) + + def checkforlower(self, string): + "Check for a string in lower case." + extracted = self.extract(len(string)) + if not extracted: + return False + return string.lower() == self.extract(len(string)).lower() + + def skipcurrent(self): + "Return the current character and skip it." + current = self.current() + self.skip(current) + return current + + def __next__(self): + "Advance the position and return the next character." + self.skipcurrent() + return self.current() + + def checkskip(self, string): + "Check for a string at the given position; if there, skip it" + if not self.checkfor(string): + return False + self.skip(string) + return True + + def error(self, message): + "Show an error message and the position identifier." + Trace.error(message + ': ' + self.identifier()) + + +class TextPosition(Position): + "A parse position based on a raw text." + + def __init__(self, text): + "Create the position from some text." + Position.__init__(self) + self.pos = 0 + self.text = text + self.checkbytemark() + + def skip(self, string): + "Skip a string of characters." + self.pos += len(string) + + def identifier(self): + "Return a sample of the remaining text." + length = 30 + if self.pos + length > len(self.text): + length = len(self.text) - self.pos + return '*' + self.text[self.pos:self.pos + length] + '*' + + def isout(self): + "Find out if we are out of the text yet." + return self.pos >= len(self.text) + + def current(self): + "Return the current character, assuming we are not out." + return self.text[self.pos] + + def extract(self, length): + "Extract the next string of the given length, or None if not enough text." + if self.pos + length > len(self.text): + return None + return self.text[self.pos : self.pos + length] # noqa: E203 + + +class Container: + "A container for text and objects in a lyx file" + + partkey = None + parent = None + begin = None + + def __init__(self): + self.contents = list() + + def process(self): + "Process contents" + pass + + def gethtml(self): + "Get the resulting HTML" + html = self.output.gethtml(self) + if isinstance(html, str): + Trace.error('Raw string ' + html) + html = [html] + return html + + def escape(self, line, replacements=EscapeConfig.entities): + "Escape a line with replacements from a map" + pieces = sorted(replacements.keys()) + # do them in order + for piece in pieces: + if piece in line: + line = line.replace(piece, replacements[piece]) + return line + + def escapeentities(self, line): + "Escape all Unicode characters to HTML entities." + result = '' + pos = TextPosition(line) + while not pos.finished(): + if ord(pos.current()) > 128: + codepoint = hex(ord(pos.current())) + if codepoint == '0xd835': + codepoint = hex(ord(next(pos)) + 0xf800) + result += '&#' + codepoint[1:] + ';' + else: + result += pos.current() + pos.skipcurrent() + return result + + def searchall(self, type): + "Search for all embedded containers of a given type" + list = [] + self.searchprocess(type, lambda container: list.append(container)) + return list + + def searchremove(self, type): + "Search for all containers of a type and remove them" + list = self.searchall(type) + for container in list: + container.parent.contents.remove(container) + return list + + def searchprocess(self, type, process): + "Search for elements of a given type and process them" + self.locateprocess(lambda container: isinstance(container, type), process) + + def locateprocess(self, locate, process): + "Search for all embedded containers and process them" + for container in self.contents: + container.locateprocess(locate, process) + if locate(container): + process(container) + + def recursivesearch(self, locate, recursive, process): + "Perform a recursive search in the container." + for container in self.contents: + if recursive(container): + container.recursivesearch(locate, recursive, process) + if locate(container): + process(container) + + def extracttext(self): + "Extract all text from allowed containers." + constants = ContainerExtractor(ContainerConfig.extracttext).extract(self) + return ''.join(constant.string for constant in constants) + + def group(self, index, group, isingroup): + "Group some adjoining elements into a group" + if index >= len(self.contents): + return + if hasattr(self.contents[index], 'grouped'): + return + while index < len(self.contents) and isingroup(self.contents[index]): + self.contents[index].grouped = True + group.contents.append(self.contents[index]) + self.contents.pop(index) + self.contents.insert(index, group) + + def remove(self, index): + "Remove a container but leave its contents" + container = self.contents[index] + self.contents.pop(index) + while len(container.contents) > 0: + self.contents.insert(index, container.contents.pop()) + + def tree(self, level=0): + "Show in a tree" + Trace.debug(" " * level + str(self)) + for container in self.contents: + container.tree(level + 1) + + def getparameter(self, name): + "Get the value of a parameter, if present." + if name not in self.parameters: + return None + return self.parameters[name] + + def getparameterlist(self, name): + "Get the value of a comma-separated parameter as a list." + paramtext = self.getparameter(name) + if not paramtext: + return [] + return paramtext.split(',') + + def hasemptyoutput(self): + "Check if the parent's output is empty." + current = self.parent + while current: + if current.output.isempty(): + return True + current = current.parent + return False + + def __str__(self): + "Get a description" + if not self.begin: + return self.__class__.__name__ + return self.__class__.__name__ + '@' + str(self.begin) + + +class BlackBox(Container): + "A container that does not output anything" + + def __init__(self): + self.parser = LoneCommand() + self.output = EmptyOutput() + self.contents = [] + + +class StringContainer(Container): + "A container for a single string" + + parsed = None + + def __init__(self): + self.parser = StringParser() + self.output = StringOutput() + self.string = '' + + def process(self): + "Replace special chars from the contents." + if self.parsed: + self.string = self.replacespecial(self.parsed) + self.parsed = None + + def replacespecial(self, line): + "Replace all special chars from a line" + replaced = self.escape(line, EscapeConfig.entities) + replaced = self.changeline(replaced) + if ContainerConfig.string['startcommand'] in replaced and len(replaced) > 1: + # unprocessed commands + if self.begin: + message = 'Unknown command at ' + str(self.begin) + ': ' + else: + message = 'Unknown command: ' + Trace.error(message + replaced.strip()) + return replaced + + def changeline(self, line): + return self.escape(line, EscapeConfig.chars) + + def extracttext(self): + "Return all text." + return self.string + + def __str__(self): + "Return a printable representation." + result = 'StringContainer' + if self.begin: + result += '@' + str(self.begin) + ellipsis = '...' + if len(self.string.strip()) <= 15: + ellipsis = '' + return result + ' (' + self.string.strip()[:15] + ellipsis + ')' + + +class Constant(StringContainer): + "A constant string" + + def __init__(self, text): + self.contents = [] + self.string = text + self.output = StringOutput() + + def __str__(self): + return 'Constant: ' + self.string + + +class DocumentParameters: + "Global parameters for the document." + + displaymode = False + + +class FormulaParser(Parser): + "Parses a formula" + + def parseheader(self, reader): + "See if the formula is inlined" + self.begin = reader.linenumber + 1 + type = self.parsetype(reader) + if not type: + reader.nextline() + type = self.parsetype(reader) + if not type: + Trace.error('Unknown formula type in ' + reader.currentline().strip()) + return ['unknown'] + return [type] + + def parsetype(self, reader): + "Get the formula type from the first line." + if reader.currentline().find(FormulaConfig.starts['simple']) >= 0: + return 'inline' + if reader.currentline().find(FormulaConfig.starts['complex']) >= 0: + return 'block' + if reader.currentline().find(FormulaConfig.starts['unnumbered']) >= 0: + return 'block' + if reader.currentline().find(FormulaConfig.starts['beginbefore']) >= 0: + return 'numbered' + return None + + def parse(self, reader): + "Parse the formula until the end" + formula = self.parseformula(reader) + while not reader.currentline().startswith(self.ending): + stripped = reader.currentline().strip() + if len(stripped) > 0: + Trace.error('Unparsed formula line ' + stripped) + reader.nextline() + reader.nextline() + return formula + + def parseformula(self, reader): + "Parse the formula contents" + simple = FormulaConfig.starts['simple'] + if simple in reader.currentline(): + rest = reader.currentline().split(simple, 1)[1] + if simple in rest: + # formula is $...$ + return self.parsesingleliner(reader, simple, simple) + # formula is multiline $...$ + return self.parsemultiliner(reader, simple, simple) + if FormulaConfig.starts['complex'] in reader.currentline(): + # formula of the form \[...\] + return self.parsemultiliner(reader, FormulaConfig.starts['complex'], + FormulaConfig.endings['complex']) + beginbefore = FormulaConfig.starts['beginbefore'] + beginafter = FormulaConfig.starts['beginafter'] + if beginbefore in reader.currentline(): + if reader.currentline().strip().endswith(beginafter): + current = reader.currentline().strip() + endsplit = current.split(beginbefore)[1].split(beginafter) + startpiece = beginbefore + endsplit[0] + beginafter + endbefore = FormulaConfig.endings['endbefore'] + endafter = FormulaConfig.endings['endafter'] + endpiece = endbefore + endsplit[0] + endafter + return startpiece + self.parsemultiliner(reader, startpiece, endpiece) + endpiece + Trace.error('Missing ' + beginafter + ' in ' + reader.currentline()) + return '' + begincommand = FormulaConfig.starts['command'] + beginbracket = FormulaConfig.starts['bracket'] + if begincommand in reader.currentline() and beginbracket in reader.currentline(): + endbracket = FormulaConfig.endings['bracket'] + return self.parsemultiliner(reader, beginbracket, endbracket) + Trace.error('Formula beginning ' + reader.currentline() + ' is unknown') + return '' + + def parsesingleliner(self, reader, start, ending): + "Parse a formula in one line" + line = reader.currentline().strip() + if start not in line: + Trace.error('Line ' + line + ' does not contain formula start ' + start) + return '' + if not line.endswith(ending): + Trace.error('Formula ' + line + ' does not end with ' + ending) + return '' + index = line.index(start) + rest = line[index + len(start):-len(ending)] + reader.nextline() + return rest + + def parsemultiliner(self, reader, start, ending): + "Parse a formula in multiple lines" + formula = '' + line = reader.currentline() + if start not in line: + Trace.error('Line ' + line.strip() + ' does not contain formula start ' + start) + return '' + index = line.index(start) + line = line[index + len(start):].strip() + while not line.endswith(ending): + formula += line + '\n' + reader.nextline() + line = reader.currentline() + formula += line[:-len(ending)] + reader.nextline() + return formula + + +class FormulaBit(Container): + "A bit of a formula" + + type = None + size = 1 + original = '' + + def __init__(self): + "The formula bit type can be 'alpha', 'number', 'font'." + self.contents = [] + self.output = ContentsOutput() + + def setfactory(self, factory): + "Set the internal formula factory." + self.factory = factory + return self + + def add(self, bit): + "Add any kind of formula bit already processed" + self.contents.append(bit) + self.original += bit.original + bit.parent = self + + def skiporiginal(self, string, pos): + "Skip a string and add it to the original formula" + self.original += string + if not pos.checkskip(string): + Trace.error('String ' + string + ' not at ' + pos.identifier()) + + def computesize(self): + "Compute the size of the bit as the max of the sizes of all contents." + if len(self.contents) == 0: + return 1 + self.size = max(element.size for element in self.contents) + return self.size + + def clone(self): + "Return a copy of itself." + return self.factory.parseformula(self.original) + + def __str__(self): + "Get a string representation" + return self.__class__.__name__ + ' read in ' + self.original + + +class TaggedBit(FormulaBit): + "A tagged string in a formula" + + def constant(self, constant, tag): + "Set the constant and the tag" + self.output = TaggedOutput().settag(tag) + self.add(FormulaConstant(constant)) + return self + + def complete(self, contents, tag, breaklines=False): + "Set the constant and the tag" + self.contents = contents + self.output = TaggedOutput().settag(tag, breaklines) + return self + + def selfcomplete(self, tag): + "Set the self-closing tag, no contents (as in <hr/>)." + self.output = TaggedOutput().settag(tag, empty=True) + return self + + +class FormulaConstant(Constant): + "A constant string in a formula" + + def __init__(self, string): + "Set the constant string" + Constant.__init__(self, string) + self.original = string + self.size = 1 + self.type = None + + def computesize(self): + "Compute the size of the constant: always 1." + return self.size + + def clone(self): + "Return a copy of itself." + return FormulaConstant(self.original) + + def __str__(self): + "Return a printable representation." + return 'Formula constant: ' + self.string + + +class RawText(FormulaBit): + "A bit of text inside a formula" + + def detect(self, pos): + "Detect a bit of raw text" + return pos.current().isalpha() + + def parsebit(self, pos): + "Parse alphabetic text" + alpha = pos.globalpha() + self.add(FormulaConstant(alpha)) + self.type = 'alpha' + + +class FormulaSymbol(FormulaBit): + "A symbol inside a formula" + + modified = FormulaConfig.modified + unmodified = FormulaConfig.unmodified['characters'] + + def detect(self, pos): + "Detect a symbol" + if pos.current() in FormulaSymbol.unmodified: + return True + if pos.current() in FormulaSymbol.modified: + return True + return False + + def parsebit(self, pos): + "Parse the symbol" + if pos.current() in FormulaSymbol.unmodified: + self.addsymbol(pos.current(), pos) + return + if pos.current() in FormulaSymbol.modified: + self.addsymbol(FormulaSymbol.modified[pos.current()], pos) + return + Trace.error('Symbol ' + pos.current() + ' not found') + + def addsymbol(self, symbol, pos): + "Add a symbol" + self.skiporiginal(pos.current(), pos) + self.contents.append(FormulaConstant(symbol)) + + +class FormulaNumber(FormulaBit): + "A string of digits in a formula" + + def detect(self, pos): + "Detect a digit" + return pos.current().isdigit() + + def parsebit(self, pos): + "Parse a bunch of digits" + digits = pos.glob(lambda: pos.current().isdigit()) + self.add(FormulaConstant(digits)) + self.type = 'number' + + +class Comment(FormulaBit): + "A LaTeX comment: % to the end of the line." + + start = FormulaConfig.starts['comment'] + + def detect(self, pos): + "Detect the %." + return pos.current() == self.start + + def parsebit(self, pos): + "Parse to the end of the line." + self.original += pos.globincluding('\n') + + +class WhiteSpace(FormulaBit): + "Some white space inside a formula." + + def detect(self, pos): + "Detect the white space." + return pos.current().isspace() + + def parsebit(self, pos): + "Parse all whitespace." + self.original += pos.skipspace() + + def __str__(self): + "Return a printable representation." + return 'Whitespace: *' + self.original + '*' + + +class Bracket(FormulaBit): + "A {} bracket inside a formula" + + start = FormulaConfig.starts['bracket'] + ending = FormulaConfig.endings['bracket'] + + def __init__(self): + "Create a (possibly literal) new bracket" + FormulaBit.__init__(self) + self.inner = None + + def detect(self, pos): + "Detect the start of a bracket" + return pos.checkfor(self.start) + + def parsebit(self, pos): + "Parse the bracket" + self.parsecomplete(pos, self.innerformula) + return self + + def parsetext(self, pos): + "Parse a text bracket" + self.parsecomplete(pos, self.innertext) + return self + + def parseliteral(self, pos): + "Parse a literal bracket" + self.parsecomplete(pos, self.innerliteral) + return self + + def parsecomplete(self, pos, innerparser): + "Parse the start and end marks" + if not pos.checkfor(self.start): + Trace.error('Bracket should start with ' + self.start + ' at ' + pos.identifier()) + return None + self.skiporiginal(self.start, pos) + pos.pushending(self.ending) + innerparser(pos) + self.original += pos.popending(self.ending) + self.computesize() + + def innerformula(self, pos): + "Parse a whole formula inside the bracket" + while not pos.finished(): + self.add(self.factory.parseany(pos)) + + def innertext(self, pos): + "Parse some text inside the bracket, following textual rules." + specialchars = list(FormulaConfig.symbolfunctions.keys()) + specialchars.append(FormulaConfig.starts['command']) + specialchars.append(FormulaConfig.starts['bracket']) + specialchars.append(Comment.start) + while not pos.finished(): + if pos.current() in specialchars: + self.add(self.factory.parseany(pos)) + if pos.checkskip(' '): + self.original += ' ' + else: + self.add(FormulaConstant(pos.skipcurrent())) + + def innerliteral(self, pos): + "Parse a literal inside the bracket, which does not generate HTML." + self.literal = '' + while not pos.finished() and not pos.current() == self.ending: + if pos.current() == self.start: + self.parseliteral(pos) + else: + self.literal += pos.skipcurrent() + self.original += self.literal + + +class SquareBracket(Bracket): + "A [] bracket inside a formula" + + start = FormulaConfig.starts['squarebracket'] + ending = FormulaConfig.endings['squarebracket'] + + def clone(self): + "Return a new square bracket with the same contents." + bracket = SquareBracket() + bracket.contents = self.contents + return bracket + + +class MathsProcessor: + "A processor for a maths construction inside the FormulaProcessor." + + def process(self, contents, index): + "Process an element inside a formula." + Trace.error('Unimplemented process() in ' + str(self)) + + def __str__(self): + "Return a printable description." + return 'Maths processor ' + self.__class__.__name__ + + +class FormulaProcessor: + "A processor specifically for formulas." + + processors = [] + + def process(self, bit): + "Process the contents of every formula bit, recursively." + self.processcontents(bit) + self.processinsides(bit) + self.traversewhole(bit) + + def processcontents(self, bit): + "Process the contents of a formula bit." + if not isinstance(bit, FormulaBit): + return + bit.process() + for element in bit.contents: + self.processcontents(element) + + def processinsides(self, bit): + "Process the insides (limits, brackets) in a formula bit." + if not isinstance(bit, FormulaBit): + return + for index, element in enumerate(bit.contents): + for processor in self.processors: + processor.process(bit.contents, index) + # continue with recursive processing + self.processinsides(element) + + def traversewhole(self, formula): + "Traverse over the contents to alter variables and space units." + last = None + for bit, contents in self.traverse(formula): + if bit.type == 'alpha': + self.italicize(bit, contents) + elif bit.type == 'font' and last and last.type == 'number': + bit.contents.insert(0, FormulaConstant('\u2009')) + last = bit + + def traverse(self, bit): + "Traverse a formula and yield a flattened structure of (bit, list) pairs." + for element in bit.contents: + if hasattr(element, 'type') and element.type: + yield element, bit.contents + elif isinstance(element, FormulaBit): + yield from self.traverse(element) + + def italicize(self, bit, contents): + "Italicize the given bit of text." + index = contents.index(bit) + contents[index] = TaggedBit().complete([bit], 'i') + + +class Formula(Container): + "A LaTeX formula" + + def __init__(self): + self.parser = FormulaParser() + self.output = TaggedOutput().settag('span class="formula"') + + def process(self): + "Convert the formula to tags" + if self.header[0] == 'inline': + DocumentParameters.displaymode = False + else: + DocumentParameters.displaymode = True + self.output.settag('div class="formula"', True) + self.classic() + + def classic(self): + "Make the contents using classic output generation with XHTML and CSS." + whole = FormulaFactory().parseformula(self.parsed) + FormulaProcessor().process(whole) + whole.parent = self + self.contents = [whole] + + def parse(self, pos): + "Parse using a parse position instead of self.parser." + if pos.checkskip('$$'): + self.parsedollarblock(pos) + elif pos.checkskip('$'): + self.parsedollarinline(pos) + elif pos.checkskip('\\('): + self.parseinlineto(pos, '\\)') + elif pos.checkskip('\\['): + self.parseblockto(pos, '\\]') + else: + pos.error('Unparseable formula') + self.process() + return self + + def parsedollarinline(self, pos): + "Parse a $...$ formula." + self.header = ['inline'] + self.parsedollar(pos) + + def parsedollarblock(self, pos): + "Parse a $$...$$ formula." + self.header = ['block'] + self.parsedollar(pos) + if not pos.checkskip('$'): + pos.error('Formula should be $$...$$, but last $ is missing.') + + def parsedollar(self, pos): + "Parse to the next $." + pos.pushending('$') + self.parsed = pos.globexcluding('$') + pos.popending('$') + + def parseinlineto(self, pos, limit): + "Parse a \\(...\\) formula." + self.header = ['inline'] + self.parseupto(pos, limit) + + def parseblockto(self, pos, limit): + "Parse a \\[...\\] formula." + self.header = ['block'] + self.parseupto(pos, limit) + + def parseupto(self, pos, limit): + "Parse a formula that ends with the given command." + pos.pushending(limit) + self.parsed = pos.glob(lambda: True) + pos.popending(limit) + + def __str__(self): + "Return a printable representation." + if self.partkey and self.partkey.number: + return 'Formula (' + self.partkey.number + ')' + return 'Unnumbered formula' + + +class WholeFormula(FormulaBit): + "Parse a whole formula" + + def detect(self, pos): + "Not outside the formula is enough." + return not pos.finished() + + def parsebit(self, pos): + "Parse with any formula bit" + while not pos.finished(): + self.add(self.factory.parseany(pos)) + + +class FormulaFactory: + "Construct bits of formula" + + # bit types will be appended later + types = [FormulaSymbol, RawText, FormulaNumber, Bracket, Comment, WhiteSpace] + skippedtypes = [Comment, WhiteSpace] + defining = False + + def __init__(self): + "Initialize the map of instances." + self.instances = {} + + def detecttype(self, type, pos): + "Detect a bit of a given type." + if pos.finished(): + return False + return self.instance(type).detect(pos) + + def instance(self, type): + "Get an instance of the given type." + if type not in self.instances or not self.instances[type]: + self.instances[type] = self.create(type) + return self.instances[type] + + def create(self, type): + "Create a new formula bit of the given type." + return Cloner.create(type).setfactory(self) + + def clearskipped(self, pos): + "Clear any skipped types." + while not pos.finished(): + if not self.skipany(pos): + return + return + + def skipany(self, pos): + "Skip any skipped types." + for type in self.skippedtypes: + if self.instance(type).detect(pos): + return self.parsetype(type, pos) + return None + + def parseany(self, pos): + "Parse any formula bit at the current location." + for type in self.types + self.skippedtypes: + if self.detecttype(type, pos): + return self.parsetype(type, pos) + Trace.error('Unrecognized formula at ' + pos.identifier()) + return FormulaConstant(pos.skipcurrent()) + + def parsetype(self, type, pos): + "Parse the given type and return it." + bit = self.instance(type) + self.instances[type] = None + returnedbit = bit.parsebit(pos) + if returnedbit: + return returnedbit.setfactory(self) + return bit + + def parseformula(self, formula): + "Parse a string of text that contains a whole formula." + pos = TextPosition(formula) + whole = self.create(WholeFormula) + if whole.detect(pos): + whole.parsebit(pos) + return whole + # no formula found + if not pos.finished(): + Trace.error('Unknown formula at: ' + pos.identifier()) + whole.add(TaggedBit().constant(formula, 'span class="unknown"')) + return whole + + +class FormulaCommand(FormulaBit): + "A LaTeX command inside a formula" + + types = [] + start = FormulaConfig.starts['command'] + commandmap = None + + def detect(self, pos): + "Find the current command." + return pos.checkfor(FormulaCommand.start) + + def parsebit(self, pos): + "Parse the command." + command = self.extractcommand(pos) + bit = self.parsewithcommand(command, pos) + if bit: + return bit + if command.startswith('\\up') or command.startswith('\\Up'): + upgreek = self.parseupgreek(command, pos) + if upgreek: + return upgreek + if not self.factory.defining: + Trace.error('Unknown command ' + command) + self.output = TaggedOutput().settag('span class="unknown"') + self.add(FormulaConstant(command)) + return None + + def parsewithcommand(self, command, pos): + "Parse the command type once we have the command." + for type in FormulaCommand.types: + if command in type.commandmap: + return self.parsecommandtype(command, type, pos) + return None + + def parsecommandtype(self, command, type, pos): + "Parse a given command type." + bit = self.factory.create(type) + bit.setcommand(command) + returned = bit.parsebit(pos) + if returned: + return returned + return bit + + def extractcommand(self, pos): + "Extract the command from the current position." + if not pos.checkskip(FormulaCommand.start): + pos.error('Missing command start ' + FormulaCommand.start) + return + if pos.finished(): + return self.emptycommand(pos) + if pos.current().isalpha(): + # alpha command + command = FormulaCommand.start + pos.globalpha() + # skip mark of short command + pos.checkskip('*') + return command + # symbol command + return FormulaCommand.start + pos.skipcurrent() + + def emptycommand(self, pos): + """Check for an empty command: look for command disguised as ending. + Special case against '{ \\{ \\} }' situation.""" + command = '' + if not pos.isout(): + ending = pos.nextending() + if ending and pos.checkskip(ending): + command = ending + return FormulaCommand.start + command + + def parseupgreek(self, command, pos): + "Parse the Greek \\up command.." + if len(command) < 4: + return None + if command.startswith('\\up'): + upcommand = '\\' + command[3:] + elif pos.checkskip('\\Up'): + upcommand = '\\' + command[3:4].upper() + command[4:] + else: + Trace.error('Impossible upgreek command: ' + command) + return + upgreek = self.parsewithcommand(upcommand, pos) + if upgreek: + upgreek.type = 'font' + return upgreek + + +class CommandBit(FormulaCommand): + "A formula bit that includes a command" + + def setcommand(self, command): + "Set the command in the bit" + self.command = command + if self.commandmap: + self.original += command + self.translated = self.commandmap[self.command] + + def parseparameter(self, pos): + "Parse a parameter at the current position" + self.factory.clearskipped(pos) + if pos.finished(): + return None + parameter = self.factory.parseany(pos) + self.add(parameter) + return parameter + + def parsesquare(self, pos): + "Parse a square bracket" + self.factory.clearskipped(pos) + if not self.factory.detecttype(SquareBracket, pos): + return None + bracket = self.factory.parsetype(SquareBracket, pos) + self.add(bracket) + return bracket + + def parseliteral(self, pos): + "Parse a literal bracket." + self.factory.clearskipped(pos) + if not self.factory.detecttype(Bracket, pos): + if not pos.isvalue(): + Trace.error('No literal parameter found at: ' + pos.identifier()) + return None + return pos.globvalue() + bracket = Bracket().setfactory(self.factory) + self.add(bracket.parseliteral(pos)) + return bracket.literal + + def parsesquareliteral(self, pos): + "Parse a square bracket literally." + self.factory.clearskipped(pos) + if not self.factory.detecttype(SquareBracket, pos): + return None + bracket = SquareBracket().setfactory(self.factory) + self.add(bracket.parseliteral(pos)) + return bracket.literal + + def parsetext(self, pos): + "Parse a text parameter." + self.factory.clearskipped(pos) + if not self.factory.detecttype(Bracket, pos): + Trace.error('No text parameter for ' + self.command) + return None + bracket = Bracket().setfactory(self.factory).parsetext(pos) + self.add(bracket) + return bracket + + +class EmptyCommand(CommandBit): + "An empty command (without parameters)" + + commandmap = FormulaConfig.commands + + def parsebit(self, pos): + "Parse a command without parameters" + self.contents = [FormulaConstant(self.translated)] + + +class SpacedCommand(CommandBit): + """An empty command which should have math spacing in formulas.""" + + commandmap = FormulaConfig.spacedcommands + + def parsebit(self, pos): + "Place as contents the command translated and spaced." + # pad with MEDIUM MATHEMATICAL SPACE (4/18 em): too wide in STIX fonts :( + # self.contents = [FormulaConstant('\u205f' + self.translated + '\u205f')] + # pad with THIN SPACE (1/5 em) + self.contents = [FormulaConstant('\u2009' + self.translated + '\u2009')] + + +class AlphaCommand(EmptyCommand): + """A command without parameters whose result is alphabetical.""" + + commandmap = FormulaConfig.alphacommands + greek_capitals = ('\\Xi', '\\Theta', '\\Pi', '\\Sigma', '\\Gamma', + '\\Lambda', '\\Phi', '\\Psi', '\\Delta', + '\\Upsilon', '\\Omega') + + def parsebit(self, pos): + "Parse the command and set type to alpha" + EmptyCommand.parsebit(self, pos) + if self.command not in self.greek_capitals: + # Greek Capital letters are upright in LaTeX default math-style. + # TODO: use italic, like in MathML and "iso" math-style? + self.type = 'alpha' + + +class OneParamFunction(CommandBit): + "A function of one parameter" + + commandmap = FormulaConfig.onefunctions + simplified = False + + def parsebit(self, pos): + "Parse a function with one parameter" + self.output = TaggedOutput().settag(self.translated) + self.parseparameter(pos) + self.simplifyifpossible() + + def simplifyifpossible(self): + "Try to simplify to a single character." + if self.original in self.commandmap: + self.output = FixedOutput() + self.html = [self.commandmap[self.original]] + self.simplified = True + + +class SymbolFunction(CommandBit): + "Find a function which is represented by a symbol (like _ or ^)" + + commandmap = FormulaConfig.symbolfunctions + + def detect(self, pos): + "Find the symbol" + return pos.current() in SymbolFunction.commandmap + + def parsebit(self, pos): + "Parse the symbol" + self.setcommand(pos.current()) + pos.skip(self.command) + self.output = TaggedOutput().settag(self.translated) + self.parseparameter(pos) + + +class TextFunction(CommandBit): + "A function where parameters are read as text." + + commandmap = FormulaConfig.textfunctions + + def parsebit(self, pos): + "Parse a text parameter" + self.output = TaggedOutput().settag(self.translated) + self.parsetext(pos) + + def process(self): + "Set the type to font" + self.type = 'font' + + +class FontFunction(OneParamFunction): + """A function of one parameter that changes the font.""" + # TODO: keep letters italic with \boldsymbol. + + commandmap = FormulaConfig.fontfunctions + + def process(self): + "Simplify if possible using a single character." + self.type = 'font' + self.simplifyifpossible() + + +FormulaFactory.types += [FormulaCommand, SymbolFunction] +FormulaCommand.types = [ + AlphaCommand, EmptyCommand, OneParamFunction, FontFunction, + TextFunction, SpacedCommand] + + +class BigBracket: + "A big bracket generator." + + def __init__(self, size, bracket, alignment='l'): + "Set the size and symbol for the bracket." + self.size = size + self.original = bracket + self.alignment = alignment + self.pieces = None + if bracket in FormulaConfig.bigbrackets: + self.pieces = FormulaConfig.bigbrackets[bracket] + + def getpiece(self, index): + "Return the nth piece for the bracket." + function = getattr(self, 'getpiece' + str(len(self.pieces))) + return function(index) + + def getpiece1(self, index): + "Return the only piece for a single-piece bracket." + return self.pieces[0] + + def getpiece3(self, index): + "Get the nth piece for a 3-piece bracket: parenthesis or square bracket." + if index == 0: + return self.pieces[0] + if index == self.size - 1: + return self.pieces[-1] + return self.pieces[1] + + def getpiece4(self, index): + "Get the nth piece for a 4-piece bracket: curly bracket." + if index == 0: + return self.pieces[0] + if index == self.size - 1: + return self.pieces[3] + if index == (self.size - 1)/2: + return self.pieces[2] + return self.pieces[1] + + def getcell(self, index): + "Get the bracket piece as an array cell." + piece = self.getpiece(index) + span = 'span class="bracket align-' + self.alignment + '"' + return TaggedBit().constant(piece, span) + + def getcontents(self): + "Get the bracket as an array or as a single bracket." + if self.size == 1 or not self.pieces: + return self.getsinglebracket() + rows = [] + for index in range(self.size): + cell = self.getcell(index) + rows.append(TaggedBit().complete([cell], 'span class="arrayrow"')) + return [TaggedBit().complete(rows, 'span class="array"')] + + def getsinglebracket(self): + "Return the bracket as a single sign." + if self.original == '.': + return [TaggedBit().constant('', 'span class="emptydot"')] + return [TaggedBit().constant(self.original, 'span class="stretchy"')] + + +class FormulaEquation(CommandBit): + "A simple numbered equation." + + piece = 'equation' + + def parsebit(self, pos): + "Parse the array" + self.output = ContentsOutput() + self.add(self.factory.parsetype(WholeFormula, pos)) + + +class FormulaCell(FormulaCommand): + "An array cell inside a row" + + def setalignment(self, alignment): + self.alignment = alignment + self.output = TaggedOutput().settag('span class="arraycell align-' + + alignment + '"', True) + return self + + def parsebit(self, pos): + self.factory.clearskipped(pos) + if pos.finished(): + return + self.add(self.factory.parsetype(WholeFormula, pos)) + + +class FormulaRow(FormulaCommand): + "An array row inside an array" + + cellseparator = FormulaConfig.array['cellseparator'] + + def setalignments(self, alignments): + self.alignments = alignments + self.output = TaggedOutput().settag('span class="arrayrow"', True) + return self + + def parsebit(self, pos): + "Parse a whole row" + index = 0 + pos.pushending(self.cellseparator, optional=True) + while not pos.finished(): + cell = self.createcell(index) + cell.parsebit(pos) + self.add(cell) + index += 1 + pos.checkskip(self.cellseparator) + if len(self.contents) == 0: + self.output = EmptyOutput() + + def createcell(self, index): + "Create the cell that corresponds to the given index." + alignment = self.alignments[index % len(self.alignments)] + return self.factory.create(FormulaCell).setalignment(alignment) + + +class MultiRowFormula(CommandBit): + "A formula with multiple rows." + + def parserows(self, pos): + "Parse all rows, finish when no more row ends" + self.rows = [] + first = True + for row in self.iteraterows(pos): + if first: + first = False + else: + # intersparse empty rows + self.addempty() + row.parsebit(pos) + self.addrow(row) + self.size = len(self.rows) + + def iteraterows(self, pos): + "Iterate over all rows, end when no more row ends" + rowseparator = FormulaConfig.array['rowseparator'] + while True: + pos.pushending(rowseparator, True) + row = self.factory.create(FormulaRow) + yield row.setalignments(self.alignments) + if pos.checkfor(rowseparator): + self.original += pos.popending(rowseparator) + else: + return + + def addempty(self): + "Add an empty row." + row = self.factory.create(FormulaRow).setalignments(self.alignments) + for index, originalcell in enumerate(self.rows[-1].contents): + cell = row.createcell(index) + cell.add(FormulaConstant(' ')) + row.add(cell) + self.addrow(row) + + def addrow(self, row): + "Add a row to the contents and to the list of rows." + self.rows.append(row) + self.add(row) + + +class FormulaArray(MultiRowFormula): + "An array within a formula" + + piece = 'array' + + def parsebit(self, pos): + "Parse the array" + self.output = TaggedOutput().settag('span class="array"', False) + self.parsealignments(pos) + self.parserows(pos) + + def parsealignments(self, pos): + "Parse the different alignments" + # vertical + self.valign = 'c' + literal = self.parsesquareliteral(pos) + if literal: + self.valign = literal + # horizontal + literal = self.parseliteral(pos) + self.alignments = [] + for s in literal: + self.alignments.append(s) + + +class FormulaMatrix(MultiRowFormula): + "A matrix (array with center alignment)." + + piece = 'matrix' + + def parsebit(self, pos): + "Parse the matrix, set alignments to 'c'." + self.output = TaggedOutput().settag('span class="array"', False) + self.valign = 'c' + self.alignments = ['c'] + self.parserows(pos) + + +class FormulaCases(MultiRowFormula): + "A cases statement" + + piece = 'cases' + + def parsebit(self, pos): + "Parse the cases" + self.output = ContentsOutput() + self.alignments = ['l', 'l'] + self.parserows(pos) + for row in self.contents: + for cell in row.contents: + cell.output.settag('span class="case align-l"', True) + cell.contents.append(FormulaConstant(' ')) + array = TaggedBit().complete(self.contents, 'span class="bracketcases"', True) + brace = BigBracket(len(self.contents), '{', 'l') + self.contents = brace.getcontents() + [array] + + +class EquationEnvironment(MultiRowFormula): + "A \\begin{}...\\end equation environment with rows and cells." + + def parsebit(self, pos): + "Parse the whole environment." + environment = self.piece.replace('*', '') + self.output = TaggedOutput().settag( + 'span class="environment %s"'%environment, False) + if environment in FormulaConfig.environments: + self.alignments = FormulaConfig.environments[environment] + else: + Trace.error('Unknown equation environment ' + self.piece) + # print in red + self.output = TaggedOutput().settag('span class="unknown"') + self.add(FormulaConstant('\\begin{%s} '%environment)) + + self.alignments = ['l'] + self.parserows(pos) + + +class BeginCommand(CommandBit): + "A \\begin{}...\\end command and what it entails (array, cases, aligned)" + + commandmap = {FormulaConfig.array['begin']: ''} + + types = [FormulaEquation, FormulaArray, FormulaCases, FormulaMatrix] + + def parsebit(self, pos): + "Parse the begin command" + command = self.parseliteral(pos) + bit = self.findbit(command) + ending = FormulaConfig.array['end'] + '{' + command + '}' + pos.pushending(ending) + bit.parsebit(pos) + self.add(bit) + self.original += pos.popending(ending) + self.size = bit.size + + def findbit(self, piece): + "Find the command bit corresponding to the \\begin{piece}" + for type in BeginCommand.types: + if piece.replace('*', '') == type.piece: + return self.factory.create(type) + bit = self.factory.create(EquationEnvironment) + bit.piece = piece + return bit + + +FormulaCommand.types += [BeginCommand] + + +class CombiningFunction(OneParamFunction): + + commandmap = FormulaConfig.combiningfunctions + + def parsebit(self, pos): + "Parse a combining function." + combining = self.translated + parameter = self.parsesingleparameter(pos) + if not parameter: + Trace.error('Missing parameter for combining function ' + self.command) + return + # Trace.message('apply %s to %r'%(self.command, parameter.extracttext())) + # parameter.tree() + if not isinstance(parameter, FormulaConstant): + try: + extractor = ContainerExtractor(ContainerConfig.extracttext) + parameter = extractor.extract(parameter)[0] + except IndexError: + Trace.error('No base character found for "%s".' % self.command) + return + # Trace.message(' basechar: %r' % parameter.string) + # Insert combining character after the first character: + if parameter.string.startswith('\u2009'): + i = 2 # skip padding by SpacedCommand and FormulaConfig.modified + else: + i = 1 + parameter.string = parameter.string[:i] + combining + parameter.string[i:] + # Use pre-composed characters if possible: \not{=} -> ≠, say. + parameter.string = unicodedata.normalize('NFC', parameter.string) + + def parsesingleparameter(self, pos): + "Parse a parameter, or a single letter." + self.factory.clearskipped(pos) + if pos.finished(): + return None + return self.parseparameter(pos) + + +class OversetFunction(OneParamFunction): + "A function that decorates some bit of text with an overset." + + commandmap = FormulaConfig.oversetfunctions + + def parsebit(self, pos): + "Parse an overset-function" + symbol = self.translated + self.symbol = TaggedBit().constant(symbol, 'sup') + self.parameter = self.parseparameter(pos) + self.output = TaggedOutput().settag('span class="embellished"') + self.contents.insert(0, self.symbol) + self.parameter.output = TaggedOutput().settag('span class="base"') + self.simplifyifpossible() + + +class UndersetFunction(OneParamFunction): + "A function that decorates some bit of text with an underset." + + commandmap = FormulaConfig.undersetfunctions + + def parsebit(self, pos): + "Parse an underset-function" + symbol = self.translated + self.symbol = TaggedBit().constant(symbol, 'sub') + self.parameter = self.parseparameter(pos) + self.output = TaggedOutput().settag('span class="embellished"') + self.contents.insert(0, self.symbol) + self.parameter.output = TaggedOutput().settag('span class="base"') + self.simplifyifpossible() + + +class LimitCommand(EmptyCommand): + "A command which accepts limits above and below, in display mode." + + commandmap = FormulaConfig.limitcommands + + def parsebit(self, pos): + "Parse a limit command." + self.output = TaggedOutput().settag('span class="limits"') + symbol = self.translated + self.contents.append(TaggedBit().constant(symbol, 'span class="limit"')) + + +class LimitPreviousCommand(LimitCommand): + "A command to limit the previous command." + + commandmap = None + + def parsebit(self, pos): + "Do nothing." + self.output = TaggedOutput().settag('span class="limits"') + self.factory.clearskipped(pos) + + def __str__(self): + "Return a printable representation." + return 'Limit previous command' + + +class LimitsProcessor(MathsProcessor): + "A processor for limits inside an element." + + def process(self, contents, index): + "Process the limits for an element." + if Options.simplemath: + return + if self.checklimits(contents, index): + self.modifylimits(contents, index) + if self.checkscript(contents, index) and self.checkscript(contents, index + 1): + self.modifyscripts(contents, index) + + def checklimits(self, contents, index): + "Check if the current position has a limits command." + # TODO: check for \limits macro + if not DocumentParameters.displaymode: + return False + if self.checkcommand(contents, index + 1, LimitPreviousCommand): + self.limitsahead(contents, index) + return False + if not isinstance(contents[index], LimitCommand): + return False + return self.checkscript(contents, index + 1) + + def limitsahead(self, contents, index): + "Limit the current element based on the next." + contents[index + 1].add(contents[index].clone()) + contents[index].output = EmptyOutput() + + def modifylimits(self, contents, index): + "Modify a limits commands so that the limits appear above and below." + limited = contents[index] + subscript = self.getlimit(contents, index + 1) + if self.checkscript(contents, index + 1): + superscript = self.getlimit(contents, index + 1) + else: + superscript = TaggedBit().constant('\u2009', 'sup class="limit"') + # fix order if source is x^i + if subscript.command == '^': + superscript, subscript = subscript, superscript + limited.contents.append(subscript) + limited.contents.insert(0, superscript) + + def getlimit(self, contents, index): + "Get the limit for a limits command." + limit = self.getscript(contents, index) + limit.output.tag = limit.output.tag.replace('script', 'limit') + return limit + + def modifyscripts(self, contents, index): + "Modify the super- and subscript to appear vertically aligned." + subscript = self.getscript(contents, index) + # subscript removed so instead of index + 1 we get index again + superscript = self.getscript(contents, index) + # super-/subscript are reversed if source is x^i_j + if subscript.command == '^': + superscript, subscript = subscript, superscript + scripts = TaggedBit().complete([superscript, subscript], 'span class="scripts"') + contents.insert(index, scripts) + + def checkscript(self, contents, index): + "Check if the current element is a sub- or superscript." + return self.checkcommand(contents, index, SymbolFunction) + + def checkcommand(self, contents, index, type): + "Check for the given type as the current element." + if len(contents) <= index: + return False + return isinstance(contents[index], type) + + def getscript(self, contents, index): + "Get the sub- or superscript." + bit = contents[index] + bit.output.tag += ' class="script"' + del contents[index] + return bit + + +class BracketCommand(OneParamFunction): + "A command which defines a bracket." + + commandmap = FormulaConfig.bracketcommands + + def parsebit(self, pos): + "Parse the bracket." + OneParamFunction.parsebit(self, pos) + + def create(self, direction, character): + "Create the bracket for the given character." + self.original = character + self.command = '\\' + direction + self.contents = [FormulaConstant(character)] + return self + + +class BracketProcessor(MathsProcessor): + "A processor for bracket commands." + + def process(self, contents, index): + "Convert the bracket using Unicode pieces, if possible." + if Options.simplemath: + return + if self.checkleft(contents, index): + return self.processleft(contents, index) + + def processleft(self, contents, index): + "Process a left bracket." + rightindex = self.findright(contents, index + 1) + if not rightindex: + return + size = self.findmax(contents, index, rightindex) + self.resize(contents[index], size) + self.resize(contents[rightindex], size) + + def checkleft(self, contents, index): + "Check if the command at the given index is left." + return self.checkdirection(contents[index], '\\left') + + def checkright(self, contents, index): + "Check if the command at the given index is right." + return self.checkdirection(contents[index], '\\right') + + def checkdirection(self, bit, command): + "Check if the given bit is the desired bracket command." + if not isinstance(bit, BracketCommand): + return False + return bit.command == command + + def findright(self, contents, index): + "Find the right bracket starting at the given index, or 0." + depth = 1 + while index < len(contents): + if self.checkleft(contents, index): + depth += 1 + if self.checkright(contents, index): + depth -= 1 + if depth == 0: + return index + index += 1 + return None + + def findmax(self, contents, leftindex, rightindex): + "Find the max size of the contents between the two given indices." + sliced = contents[leftindex:rightindex] + return max(element.size for element in sliced) + + def resize(self, command, size): + "Resize a bracket command to the given size." + character = command.extracttext() + alignment = command.command.replace('\\', '') + bracket = BigBracket(size, character, alignment) + command.output = ContentsOutput() + command.contents = bracket.getcontents() + + +FormulaCommand.types += [OversetFunction, UndersetFunction, + CombiningFunction, LimitCommand, BracketCommand] + +FormulaProcessor.processors += [ + LimitsProcessor(), BracketProcessor(), +] + + +class ParameterDefinition: + "The definition of a parameter in a hybrid function." + "[] parameters are optional, {} parameters are mandatory." + "Each parameter has a one-character name, like {$1} or {$p}." + "A parameter that ends in ! like {$p!} is a literal." + "Example: [$1]{$p!} reads an optional parameter $1 and a literal mandatory parameter p." + + parambrackets = [('[', ']'), ('{', '}')] + + def __init__(self): + self.name = None + self.literal = False + self.optional = False + self.value = None + self.literalvalue = None + + def parse(self, pos): + "Parse a parameter definition: [$0], {$x}, {$1!}..." + for (opening, closing) in ParameterDefinition.parambrackets: + if pos.checkskip(opening): + if opening == '[': + self.optional = True + if not pos.checkskip('$'): + Trace.error('Wrong parameter name, did you mean $' + pos.current() + '?') + return None + self.name = pos.skipcurrent() + if pos.checkskip('!'): + self.literal = True + if not pos.checkskip(closing): + Trace.error('Wrong parameter closing ' + pos.skipcurrent()) + return None + return self + Trace.error('Wrong character in parameter template: ' + pos.skipcurrent()) + return None + + def read(self, pos, function): + "Read the parameter itself using the definition." + if self.literal: + if self.optional: + self.literalvalue = function.parsesquareliteral(pos) + else: + self.literalvalue = function.parseliteral(pos) + if self.literalvalue: + self.value = FormulaConstant(self.literalvalue) + elif self.optional: + self.value = function.parsesquare(pos) + else: + self.value = function.parseparameter(pos) + + def __str__(self): + "Return a printable representation." + result = 'param ' + self.name + if self.value: + result += ': ' + str(self.value) + else: + result += ' (empty)' + return result + + +class ParameterFunction(CommandBit): + "A function with a variable number of parameters defined in a template." + "The parameters are defined as a parameter definition." + + def readparams(self, readtemplate, pos): + "Read the params according to the template." + self.params = {} + for paramdef in self.paramdefs(readtemplate): + paramdef.read(pos, self) + self.params['$' + paramdef.name] = paramdef + + def paramdefs(self, readtemplate): + "Read each param definition in the template" + pos = TextPosition(readtemplate) + while not pos.finished(): + paramdef = ParameterDefinition().parse(pos) + if paramdef: + yield paramdef + + def getparam(self, name): + "Get a parameter as parsed." + if name not in self.params: + return None + return self.params[name] + + def getvalue(self, name): + "Get the value of a parameter." + return self.getparam(name).value + + def getliteralvalue(self, name): + "Get the literal value of a parameter." + param = self.getparam(name) + if not param or not param.literalvalue: + return None + return param.literalvalue + + +class HybridFunction(ParameterFunction): + """ + A parameter function where the output is also defined using a template. + The template can use a number of functions; each function has an associated + tag. + Example: [f0{$1},span class="fbox"] defines a function f0 which corresponds + to a span of class fbox, yielding <span class="fbox">$1</span>. + Literal parameters can be used in tags definitions: + [f0{$1},span style="color: $p;"] + yields <span style="color: $p;">$1</span>, where $p is a literal parameter. + Sizes can be specified in hybridsizes, e.g. adding parameter sizes. By + default the resulting size is the max of all arguments. Sizes are used + to generate the right parameters. + A function followed by a single / is output as a self-closing XHTML tag: + [f0/,hr] + will generate <hr/>. + """ + + commandmap = FormulaConfig.hybridfunctions + + def parsebit(self, pos): + "Parse a function with [] and {} parameters" + readtemplate = self.translated[0] + writetemplate = self.translated[1] + self.readparams(readtemplate, pos) + self.contents = self.writeparams(writetemplate) + self.computehybridsize() + + def writeparams(self, writetemplate): + "Write all params according to the template" + return self.writepos(TextPosition(writetemplate)) + + def writepos(self, pos): + "Write all params as read in the parse position." + result = [] + while not pos.finished(): + if pos.checkskip('$'): + param = self.writeparam(pos) + if param: + result.append(param) + elif pos.checkskip('f'): + function = self.writefunction(pos) + if function: + function.type = None + result.append(function) + elif pos.checkskip('('): + result.append(self.writebracket('left', '(')) + elif pos.checkskip(')'): + result.append(self.writebracket('right', ')')) + else: + result.append(FormulaConstant(pos.skipcurrent())) + return result + + def writeparam(self, pos): + "Write a single param of the form $0, $x..." + name = '$' + pos.skipcurrent() + if name not in self.params: + Trace.error('Unknown parameter ' + name) + return None + if not self.params[name]: + return None + if pos.checkskip('.'): + self.params[name].value.type = pos.globalpha() + return self.params[name].value + + def writefunction(self, pos): + "Write a single function f0,...,fn." + tag = self.readtag(pos) + if not tag: + return None + if pos.checkskip('/'): + # self-closing XHTML tag, such as <hr/> + return TaggedBit().selfcomplete(tag) + if not pos.checkskip('{'): + Trace.error('Function should be defined in {}') + return None + pos.pushending('}') + contents = self.writepos(pos) + pos.popending() + if len(contents) == 0: + return None + return TaggedBit().complete(contents, tag) + + def readtag(self, pos): + "Get the tag corresponding to the given index. Does parameter substitution." + if not pos.current().isdigit(): + Trace.error('Function should be f0,...,f9: f' + pos.current()) + return None + index = int(pos.skipcurrent()) + if 2 + index > len(self.translated): + Trace.error('Function f' + str(index) + ' is not defined') + return None + tag = self.translated[2 + index] + if '$' not in tag: + return tag + for variable in self.params: + if variable in tag: + param = self.params[variable] + if not param.literal: + Trace.error('Parameters in tag ' + tag + ' should be literal: {' + variable + '!}') + continue + if param.literalvalue: + value = param.literalvalue + else: + value = '' + tag = tag.replace(variable, value) + return tag + + def writebracket(self, direction, character): + "Return a new bracket looking at the given direction." + return self.factory.create(BracketCommand).create(direction, character) + + def computehybridsize(self): + "Compute the size of the hybrid function." + if self.command not in HybridSize.configsizes: + self.computesize() + return + self.size = HybridSize().getsize(self) + # set the size in all elements at first level + for element in self.contents: + element.size = self.size + + +class HybridSize: + "The size associated with a hybrid function." + + configsizes = FormulaConfig.hybridsizes + + def getsize(self, function): + "Read the size for a function and parse it." + sizestring = self.configsizes[function.command] + for name in function.params: + if name in sizestring: + size = function.params[name].value.computesize() + sizestring = sizestring.replace(name, str(size)) + if '$' in sizestring: + Trace.error('Unconverted variable in hybrid size: ' + sizestring) + return 1 + return eval(sizestring) + + +FormulaCommand.types += [HybridFunction] + + +def math2html(formula): + "Convert some TeX math to HTML." + factory = FormulaFactory() + whole = factory.parseformula(formula) + FormulaProcessor().process(whole) + whole.process() + return ''.join(whole.gethtml()) + + +def main(): + "Main function, called if invoked from the command line" + args = sys.argv + Options().parseoptions(args) + if len(args) != 1: + Trace.error('Usage: math2html.py escaped_string') + exit() + result = math2html(args[0]) + Trace.message(result) + + +if __name__ == '__main__': + main() diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/mathalphabet2unichar.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/mathalphabet2unichar.py new file mode 100644 index 00000000..876cea47 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/mathalphabet2unichar.py @@ -0,0 +1,892 @@ +#!/usr/bin/env python3 +# +# LaTeX math to Unicode symbols translation dictionaries for +# the content of math alphabet commands (\mathtt, \mathbf, ...). +# Generated with ``write_mathalphabet2unichar.py`` from the data in +# http://milde.users.sourceforge.net/LUCR/Math/ +# +# :Copyright: © 2024 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. +# +# __ https://opensource.org/licenses/BSD-2-Clause + +mathbb = { + '0': '\U0001d7d8', # 𝟘 MATHEMATICAL DOUBLE-STRUCK DIGIT ZERO + '1': '\U0001d7d9', # 𝟙 MATHEMATICAL DOUBLE-STRUCK DIGIT ONE + '2': '\U0001d7da', # 𝟚 MATHEMATICAL DOUBLE-STRUCK DIGIT TWO + '3': '\U0001d7db', # 𝟛 MATHEMATICAL DOUBLE-STRUCK DIGIT THREE + '4': '\U0001d7dc', # 𝟜 MATHEMATICAL DOUBLE-STRUCK DIGIT FOUR + '5': '\U0001d7dd', # 𝟝 MATHEMATICAL DOUBLE-STRUCK DIGIT FIVE + '6': '\U0001d7de', # 𝟞 MATHEMATICAL DOUBLE-STRUCK DIGIT SIX + '7': '\U0001d7df', # 𝟟 MATHEMATICAL DOUBLE-STRUCK DIGIT SEVEN + '8': '\U0001d7e0', # 𝟠 MATHEMATICAL DOUBLE-STRUCK DIGIT EIGHT + '9': '\U0001d7e1', # 𝟡 MATHEMATICAL DOUBLE-STRUCK DIGIT NINE + 'A': '\U0001d538', # 𝔸 MATHEMATICAL DOUBLE-STRUCK CAPITAL A + 'B': '\U0001d539', # 𝔹 MATHEMATICAL DOUBLE-STRUCK CAPITAL B + 'C': '\u2102', # ℂ DOUBLE-STRUCK CAPITAL C + 'D': '\U0001d53b', # 𝔻 MATHEMATICAL DOUBLE-STRUCK CAPITAL D + 'E': '\U0001d53c', # 𝔼 MATHEMATICAL DOUBLE-STRUCK CAPITAL E + 'F': '\U0001d53d', # 𝔽 MATHEMATICAL DOUBLE-STRUCK CAPITAL F + 'G': '\U0001d53e', # 𝔾 MATHEMATICAL DOUBLE-STRUCK CAPITAL G + 'H': '\u210d', # ℍ DOUBLE-STRUCK CAPITAL H + 'I': '\U0001d540', # 𝕀 MATHEMATICAL DOUBLE-STRUCK CAPITAL I + 'J': '\U0001d541', # 𝕁 MATHEMATICAL DOUBLE-STRUCK CAPITAL J + 'K': '\U0001d542', # 𝕂 MATHEMATICAL DOUBLE-STRUCK CAPITAL K + 'L': '\U0001d543', # 𝕃 MATHEMATICAL DOUBLE-STRUCK CAPITAL L + 'M': '\U0001d544', # 𝕄 MATHEMATICAL DOUBLE-STRUCK CAPITAL M + 'N': '\u2115', # ℕ DOUBLE-STRUCK CAPITAL N + 'O': '\U0001d546', # 𝕆 MATHEMATICAL DOUBLE-STRUCK CAPITAL O + 'P': '\u2119', # ℙ DOUBLE-STRUCK CAPITAL P + 'Q': '\u211a', # ℚ DOUBLE-STRUCK CAPITAL Q + 'R': '\u211d', # ℝ DOUBLE-STRUCK CAPITAL R + 'S': '\U0001d54a', # 𝕊 MATHEMATICAL DOUBLE-STRUCK CAPITAL S + 'T': '\U0001d54b', # 𝕋 MATHEMATICAL DOUBLE-STRUCK CAPITAL T + 'U': '\U0001d54c', # 𝕌 MATHEMATICAL DOUBLE-STRUCK CAPITAL U + 'V': '\U0001d54d', # 𝕍 MATHEMATICAL DOUBLE-STRUCK CAPITAL V + 'W': '\U0001d54e', # 𝕎 MATHEMATICAL DOUBLE-STRUCK CAPITAL W + 'X': '\U0001d54f', # 𝕏 MATHEMATICAL DOUBLE-STRUCK CAPITAL X + 'Y': '\U0001d550', # 𝕐 MATHEMATICAL DOUBLE-STRUCK CAPITAL Y + 'Z': '\u2124', # ℤ DOUBLE-STRUCK CAPITAL Z + 'a': '\U0001d552', # 𝕒 MATHEMATICAL DOUBLE-STRUCK SMALL A + 'b': '\U0001d553', # 𝕓 MATHEMATICAL DOUBLE-STRUCK SMALL B + 'c': '\U0001d554', # 𝕔 MATHEMATICAL DOUBLE-STRUCK SMALL C + 'd': '\U0001d555', # 𝕕 MATHEMATICAL DOUBLE-STRUCK SMALL D + 'e': '\U0001d556', # 𝕖 MATHEMATICAL DOUBLE-STRUCK SMALL E + 'f': '\U0001d557', # 𝕗 MATHEMATICAL DOUBLE-STRUCK SMALL F + 'g': '\U0001d558', # 𝕘 MATHEMATICAL DOUBLE-STRUCK SMALL G + 'h': '\U0001d559', # 𝕙 MATHEMATICAL DOUBLE-STRUCK SMALL H + 'i': '\U0001d55a', # 𝕚 MATHEMATICAL DOUBLE-STRUCK SMALL I + 'j': '\U0001d55b', # 𝕛 MATHEMATICAL DOUBLE-STRUCK SMALL J + 'k': '\U0001d55c', # 𝕜 MATHEMATICAL DOUBLE-STRUCK SMALL K + 'l': '\U0001d55d', # 𝕝 MATHEMATICAL DOUBLE-STRUCK SMALL L + 'm': '\U0001d55e', # 𝕞 MATHEMATICAL DOUBLE-STRUCK SMALL M + 'n': '\U0001d55f', # 𝕟 MATHEMATICAL DOUBLE-STRUCK SMALL N + 'o': '\U0001d560', # 𝕠 MATHEMATICAL DOUBLE-STRUCK SMALL O + 'p': '\U0001d561', # 𝕡 MATHEMATICAL DOUBLE-STRUCK SMALL P + 'q': '\U0001d562', # 𝕢 MATHEMATICAL DOUBLE-STRUCK SMALL Q + 'r': '\U0001d563', # 𝕣 MATHEMATICAL DOUBLE-STRUCK SMALL R + 's': '\U0001d564', # 𝕤 MATHEMATICAL DOUBLE-STRUCK SMALL S + 't': '\U0001d565', # 𝕥 MATHEMATICAL DOUBLE-STRUCK SMALL T + 'u': '\U0001d566', # 𝕦 MATHEMATICAL DOUBLE-STRUCK SMALL U + 'v': '\U0001d567', # 𝕧 MATHEMATICAL DOUBLE-STRUCK SMALL V + 'w': '\U0001d568', # 𝕨 MATHEMATICAL DOUBLE-STRUCK SMALL W + 'x': '\U0001d569', # 𝕩 MATHEMATICAL DOUBLE-STRUCK SMALL X + 'y': '\U0001d56a', # 𝕪 MATHEMATICAL DOUBLE-STRUCK SMALL Y + 'z': '\U0001d56b', # 𝕫 MATHEMATICAL DOUBLE-STRUCK SMALL Z + 'Γ': '\u213e', # ℾ DOUBLE-STRUCK CAPITAL GAMMA + 'Π': '\u213f', # ℿ DOUBLE-STRUCK CAPITAL PI + 'Σ': '\u2140', # ⅀ DOUBLE-STRUCK N-ARY SUMMATION + 'γ': '\u213d', # ℽ DOUBLE-STRUCK SMALL GAMMA + 'π': '\u213c', # ℼ DOUBLE-STRUCK SMALL PI + } + +mathbf = { + '0': '\U0001d7ce', # 𝟎 MATHEMATICAL BOLD DIGIT ZERO + '1': '\U0001d7cf', # 𝟏 MATHEMATICAL BOLD DIGIT ONE + '2': '\U0001d7d0', # 𝟐 MATHEMATICAL BOLD DIGIT TWO + '3': '\U0001d7d1', # 𝟑 MATHEMATICAL BOLD DIGIT THREE + '4': '\U0001d7d2', # 𝟒 MATHEMATICAL BOLD DIGIT FOUR + '5': '\U0001d7d3', # 𝟓 MATHEMATICAL BOLD DIGIT FIVE + '6': '\U0001d7d4', # 𝟔 MATHEMATICAL BOLD DIGIT SIX + '7': '\U0001d7d5', # 𝟕 MATHEMATICAL BOLD DIGIT SEVEN + '8': '\U0001d7d6', # 𝟖 MATHEMATICAL BOLD DIGIT EIGHT + '9': '\U0001d7d7', # 𝟗 MATHEMATICAL BOLD DIGIT NINE + 'A': '\U0001d400', # 𝐀 MATHEMATICAL BOLD CAPITAL A + 'B': '\U0001d401', # 𝐁 MATHEMATICAL BOLD CAPITAL B + 'C': '\U0001d402', # 𝐂 MATHEMATICAL BOLD CAPITAL C + 'D': '\U0001d403', # 𝐃 MATHEMATICAL BOLD CAPITAL D + 'E': '\U0001d404', # 𝐄 MATHEMATICAL BOLD CAPITAL E + 'F': '\U0001d405', # 𝐅 MATHEMATICAL BOLD CAPITAL F + 'G': '\U0001d406', # 𝐆 MATHEMATICAL BOLD CAPITAL G + 'H': '\U0001d407', # 𝐇 MATHEMATICAL BOLD CAPITAL H + 'I': '\U0001d408', # 𝐈 MATHEMATICAL BOLD CAPITAL I + 'J': '\U0001d409', # 𝐉 MATHEMATICAL BOLD CAPITAL J + 'K': '\U0001d40a', # 𝐊 MATHEMATICAL BOLD CAPITAL K + 'L': '\U0001d40b', # 𝐋 MATHEMATICAL BOLD CAPITAL L + 'M': '\U0001d40c', # 𝐌 MATHEMATICAL BOLD CAPITAL M + 'N': '\U0001d40d', # 𝐍 MATHEMATICAL BOLD CAPITAL N + 'O': '\U0001d40e', # 𝐎 MATHEMATICAL BOLD CAPITAL O + 'P': '\U0001d40f', # 𝐏 MATHEMATICAL BOLD CAPITAL P + 'Q': '\U0001d410', # 𝐐 MATHEMATICAL BOLD CAPITAL Q + 'R': '\U0001d411', # 𝐑 MATHEMATICAL BOLD CAPITAL R + 'S': '\U0001d412', # 𝐒 MATHEMATICAL BOLD CAPITAL S + 'T': '\U0001d413', # 𝐓 MATHEMATICAL BOLD CAPITAL T + 'U': '\U0001d414', # 𝐔 MATHEMATICAL BOLD CAPITAL U + 'V': '\U0001d415', # 𝐕 MATHEMATICAL BOLD CAPITAL V + 'W': '\U0001d416', # 𝐖 MATHEMATICAL BOLD CAPITAL W + 'X': '\U0001d417', # 𝐗 MATHEMATICAL BOLD CAPITAL X + 'Y': '\U0001d418', # 𝐘 MATHEMATICAL BOLD CAPITAL Y + 'Z': '\U0001d419', # 𝐙 MATHEMATICAL BOLD CAPITAL Z + 'a': '\U0001d41a', # 𝐚 MATHEMATICAL BOLD SMALL A + 'b': '\U0001d41b', # 𝐛 MATHEMATICAL BOLD SMALL B + 'c': '\U0001d41c', # 𝐜 MATHEMATICAL BOLD SMALL C + 'd': '\U0001d41d', # 𝐝 MATHEMATICAL BOLD SMALL D + 'e': '\U0001d41e', # 𝐞 MATHEMATICAL BOLD SMALL E + 'f': '\U0001d41f', # 𝐟 MATHEMATICAL BOLD SMALL F + 'g': '\U0001d420', # 𝐠 MATHEMATICAL BOLD SMALL G + 'h': '\U0001d421', # 𝐡 MATHEMATICAL BOLD SMALL H + 'i': '\U0001d422', # 𝐢 MATHEMATICAL BOLD SMALL I + 'j': '\U0001d423', # 𝐣 MATHEMATICAL BOLD SMALL J + 'k': '\U0001d424', # 𝐤 MATHEMATICAL BOLD SMALL K + 'l': '\U0001d425', # 𝐥 MATHEMATICAL BOLD SMALL L + 'm': '\U0001d426', # 𝐦 MATHEMATICAL BOLD SMALL M + 'n': '\U0001d427', # 𝐧 MATHEMATICAL BOLD SMALL N + 'o': '\U0001d428', # 𝐨 MATHEMATICAL BOLD SMALL O + 'p': '\U0001d429', # 𝐩 MATHEMATICAL BOLD SMALL P + 'q': '\U0001d42a', # 𝐪 MATHEMATICAL BOLD SMALL Q + 'r': '\U0001d42b', # 𝐫 MATHEMATICAL BOLD SMALL R + 's': '\U0001d42c', # 𝐬 MATHEMATICAL BOLD SMALL S + 't': '\U0001d42d', # 𝐭 MATHEMATICAL BOLD SMALL T + 'u': '\U0001d42e', # 𝐮 MATHEMATICAL BOLD SMALL U + 'v': '\U0001d42f', # 𝐯 MATHEMATICAL BOLD SMALL V + 'w': '\U0001d430', # 𝐰 MATHEMATICAL BOLD SMALL W + 'x': '\U0001d431', # 𝐱 MATHEMATICAL BOLD SMALL X + 'y': '\U0001d432', # 𝐲 MATHEMATICAL BOLD SMALL Y + 'z': '\U0001d433', # 𝐳 MATHEMATICAL BOLD SMALL Z + 'Γ': '\U0001d6aa', # 𝚪 MATHEMATICAL BOLD CAPITAL GAMMA + 'Δ': '\U0001d6ab', # 𝚫 MATHEMATICAL BOLD CAPITAL DELTA + 'Θ': '\U0001d6af', # 𝚯 MATHEMATICAL BOLD CAPITAL THETA + 'Λ': '\U0001d6b2', # 𝚲 MATHEMATICAL BOLD CAPITAL LAMDA + 'Ξ': '\U0001d6b5', # 𝚵 MATHEMATICAL BOLD CAPITAL XI + 'Π': '\U0001d6b7', # 𝚷 MATHEMATICAL BOLD CAPITAL PI + 'Σ': '\U0001d6ba', # 𝚺 MATHEMATICAL BOLD CAPITAL SIGMA + 'Υ': '\U0001d6bc', # 𝚼 MATHEMATICAL BOLD CAPITAL UPSILON + 'Φ': '\U0001d6bd', # 𝚽 MATHEMATICAL BOLD CAPITAL PHI + 'Ψ': '\U0001d6bf', # 𝚿 MATHEMATICAL BOLD CAPITAL PSI + 'Ω': '\U0001d6c0', # 𝛀 MATHEMATICAL BOLD CAPITAL OMEGA + 'α': '\U0001d6c2', # 𝛂 MATHEMATICAL BOLD SMALL ALPHA + 'β': '\U0001d6c3', # 𝛃 MATHEMATICAL BOLD SMALL BETA + 'γ': '\U0001d6c4', # 𝛄 MATHEMATICAL BOLD SMALL GAMMA + 'δ': '\U0001d6c5', # 𝛅 MATHEMATICAL BOLD SMALL DELTA + 'ε': '\U0001d6c6', # 𝛆 MATHEMATICAL BOLD SMALL EPSILON + 'ζ': '\U0001d6c7', # 𝛇 MATHEMATICAL BOLD SMALL ZETA + 'η': '\U0001d6c8', # 𝛈 MATHEMATICAL BOLD SMALL ETA + 'θ': '\U0001d6c9', # 𝛉 MATHEMATICAL BOLD SMALL THETA + 'ι': '\U0001d6ca', # 𝛊 MATHEMATICAL BOLD SMALL IOTA + 'κ': '\U0001d6cb', # 𝛋 MATHEMATICAL BOLD SMALL KAPPA + 'λ': '\U0001d6cc', # 𝛌 MATHEMATICAL BOLD SMALL LAMDA + 'μ': '\U0001d6cd', # 𝛍 MATHEMATICAL BOLD SMALL MU + 'ν': '\U0001d6ce', # 𝛎 MATHEMATICAL BOLD SMALL NU + 'ξ': '\U0001d6cf', # 𝛏 MATHEMATICAL BOLD SMALL XI + 'π': '\U0001d6d1', # 𝛑 MATHEMATICAL BOLD SMALL PI + 'ρ': '\U0001d6d2', # 𝛒 MATHEMATICAL BOLD SMALL RHO + 'ς': '\U0001d6d3', # 𝛓 MATHEMATICAL BOLD SMALL FINAL SIGMA + 'σ': '\U0001d6d4', # 𝛔 MATHEMATICAL BOLD SMALL SIGMA + 'τ': '\U0001d6d5', # 𝛕 MATHEMATICAL BOLD SMALL TAU + 'υ': '\U0001d6d6', # 𝛖 MATHEMATICAL BOLD SMALL UPSILON + 'φ': '\U0001d6d7', # 𝛗 MATHEMATICAL BOLD SMALL PHI + 'χ': '\U0001d6d8', # 𝛘 MATHEMATICAL BOLD SMALL CHI + 'ψ': '\U0001d6d9', # 𝛙 MATHEMATICAL BOLD SMALL PSI + 'ω': '\U0001d6da', # 𝛚 MATHEMATICAL BOLD SMALL OMEGA + 'ϑ': '\U0001d6dd', # 𝛝 MATHEMATICAL BOLD THETA SYMBOL + 'ϕ': '\U0001d6df', # 𝛟 MATHEMATICAL BOLD PHI SYMBOL + 'ϖ': '\U0001d6e1', # 𝛡 MATHEMATICAL BOLD PI SYMBOL + 'Ϝ': '\U0001d7ca', # 𝟊 MATHEMATICAL BOLD CAPITAL DIGAMMA + 'ϝ': '\U0001d7cb', # 𝟋 MATHEMATICAL BOLD SMALL DIGAMMA + 'ϰ': '\U0001d6de', # 𝛞 MATHEMATICAL BOLD KAPPA SYMBOL + 'ϱ': '\U0001d6e0', # 𝛠 MATHEMATICAL BOLD RHO SYMBOL + 'ϵ': '\U0001d6dc', # 𝛜 MATHEMATICAL BOLD EPSILON SYMBOL + '∂': '\U0001d6db', # 𝛛 MATHEMATICAL BOLD PARTIAL DIFFERENTIAL + '∇': '\U0001d6c1', # 𝛁 MATHEMATICAL BOLD NABLA + } + +mathbfit = { + 'A': '\U0001d468', # 𝑨 MATHEMATICAL BOLD ITALIC CAPITAL A + 'B': '\U0001d469', # 𝑩 MATHEMATICAL BOLD ITALIC CAPITAL B + 'C': '\U0001d46a', # 𝑪 MATHEMATICAL BOLD ITALIC CAPITAL C + 'D': '\U0001d46b', # 𝑫 MATHEMATICAL BOLD ITALIC CAPITAL D + 'E': '\U0001d46c', # 𝑬 MATHEMATICAL BOLD ITALIC CAPITAL E + 'F': '\U0001d46d', # 𝑭 MATHEMATICAL BOLD ITALIC CAPITAL F + 'G': '\U0001d46e', # 𝑮 MATHEMATICAL BOLD ITALIC CAPITAL G + 'H': '\U0001d46f', # 𝑯 MATHEMATICAL BOLD ITALIC CAPITAL H + 'I': '\U0001d470', # 𝑰 MATHEMATICAL BOLD ITALIC CAPITAL I + 'J': '\U0001d471', # 𝑱 MATHEMATICAL BOLD ITALIC CAPITAL J + 'K': '\U0001d472', # 𝑲 MATHEMATICAL BOLD ITALIC CAPITAL K + 'L': '\U0001d473', # 𝑳 MATHEMATICAL BOLD ITALIC CAPITAL L + 'M': '\U0001d474', # 𝑴 MATHEMATICAL BOLD ITALIC CAPITAL M + 'N': '\U0001d475', # 𝑵 MATHEMATICAL BOLD ITALIC CAPITAL N + 'O': '\U0001d476', # 𝑶 MATHEMATICAL BOLD ITALIC CAPITAL O + 'P': '\U0001d477', # 𝑷 MATHEMATICAL BOLD ITALIC CAPITAL P + 'Q': '\U0001d478', # 𝑸 MATHEMATICAL BOLD ITALIC CAPITAL Q + 'R': '\U0001d479', # 𝑹 MATHEMATICAL BOLD ITALIC CAPITAL R + 'S': '\U0001d47a', # 𝑺 MATHEMATICAL BOLD ITALIC CAPITAL S + 'T': '\U0001d47b', # 𝑻 MATHEMATICAL BOLD ITALIC CAPITAL T + 'U': '\U0001d47c', # 𝑼 MATHEMATICAL BOLD ITALIC CAPITAL U + 'V': '\U0001d47d', # 𝑽 MATHEMATICAL BOLD ITALIC CAPITAL V + 'W': '\U0001d47e', # 𝑾 MATHEMATICAL BOLD ITALIC CAPITAL W + 'X': '\U0001d47f', # 𝑿 MATHEMATICAL BOLD ITALIC CAPITAL X + 'Y': '\U0001d480', # 𝒀 MATHEMATICAL BOLD ITALIC CAPITAL Y + 'Z': '\U0001d481', # 𝒁 MATHEMATICAL BOLD ITALIC CAPITAL Z + 'a': '\U0001d482', # 𝒂 MATHEMATICAL BOLD ITALIC SMALL A + 'b': '\U0001d483', # 𝒃 MATHEMATICAL BOLD ITALIC SMALL B + 'c': '\U0001d484', # 𝒄 MATHEMATICAL BOLD ITALIC SMALL C + 'd': '\U0001d485', # 𝒅 MATHEMATICAL BOLD ITALIC SMALL D + 'e': '\U0001d486', # 𝒆 MATHEMATICAL BOLD ITALIC SMALL E + 'f': '\U0001d487', # 𝒇 MATHEMATICAL BOLD ITALIC SMALL F + 'g': '\U0001d488', # 𝒈 MATHEMATICAL BOLD ITALIC SMALL G + 'h': '\U0001d489', # 𝒉 MATHEMATICAL BOLD ITALIC SMALL H + 'i': '\U0001d48a', # 𝒊 MATHEMATICAL BOLD ITALIC SMALL I + 'j': '\U0001d48b', # 𝒋 MATHEMATICAL BOLD ITALIC SMALL J + 'k': '\U0001d48c', # 𝒌 MATHEMATICAL BOLD ITALIC SMALL K + 'l': '\U0001d48d', # 𝒍 MATHEMATICAL BOLD ITALIC SMALL L + 'm': '\U0001d48e', # 𝒎 MATHEMATICAL BOLD ITALIC SMALL M + 'n': '\U0001d48f', # 𝒏 MATHEMATICAL BOLD ITALIC SMALL N + 'o': '\U0001d490', # 𝒐 MATHEMATICAL BOLD ITALIC SMALL O + 'p': '\U0001d491', # 𝒑 MATHEMATICAL BOLD ITALIC SMALL P + 'q': '\U0001d492', # 𝒒 MATHEMATICAL BOLD ITALIC SMALL Q + 'r': '\U0001d493', # 𝒓 MATHEMATICAL BOLD ITALIC SMALL R + 's': '\U0001d494', # 𝒔 MATHEMATICAL BOLD ITALIC SMALL S + 't': '\U0001d495', # 𝒕 MATHEMATICAL BOLD ITALIC SMALL T + 'u': '\U0001d496', # 𝒖 MATHEMATICAL BOLD ITALIC SMALL U + 'v': '\U0001d497', # 𝒗 MATHEMATICAL BOLD ITALIC SMALL V + 'w': '\U0001d498', # 𝒘 MATHEMATICAL BOLD ITALIC SMALL W + 'x': '\U0001d499', # 𝒙 MATHEMATICAL BOLD ITALIC SMALL X + 'y': '\U0001d49a', # 𝒚 MATHEMATICAL BOLD ITALIC SMALL Y + 'z': '\U0001d49b', # 𝒛 MATHEMATICAL BOLD ITALIC SMALL Z + 'Γ': '\U0001d71e', # 𝜞 MATHEMATICAL BOLD ITALIC CAPITAL GAMMA + 'Δ': '\U0001d71f', # 𝜟 MATHEMATICAL BOLD ITALIC CAPITAL DELTA + 'Θ': '\U0001d723', # 𝜣 MATHEMATICAL BOLD ITALIC CAPITAL THETA + 'Λ': '\U0001d726', # 𝜦 MATHEMATICAL BOLD ITALIC CAPITAL LAMDA + 'Ξ': '\U0001d729', # 𝜩 MATHEMATICAL BOLD ITALIC CAPITAL XI + 'Π': '\U0001d72b', # 𝜫 MATHEMATICAL BOLD ITALIC CAPITAL PI + 'Σ': '\U0001d72e', # 𝜮 MATHEMATICAL BOLD ITALIC CAPITAL SIGMA + 'Υ': '\U0001d730', # 𝜰 MATHEMATICAL BOLD ITALIC CAPITAL UPSILON + 'Φ': '\U0001d731', # 𝜱 MATHEMATICAL BOLD ITALIC CAPITAL PHI + 'Ψ': '\U0001d733', # 𝜳 MATHEMATICAL BOLD ITALIC CAPITAL PSI + 'Ω': '\U0001d734', # 𝜴 MATHEMATICAL BOLD ITALIC CAPITAL OMEGA + 'α': '\U0001d736', # 𝜶 MATHEMATICAL BOLD ITALIC SMALL ALPHA + 'β': '\U0001d737', # 𝜷 MATHEMATICAL BOLD ITALIC SMALL BETA + 'γ': '\U0001d738', # 𝜸 MATHEMATICAL BOLD ITALIC SMALL GAMMA + 'δ': '\U0001d739', # 𝜹 MATHEMATICAL BOLD ITALIC SMALL DELTA + 'ε': '\U0001d73a', # 𝜺 MATHEMATICAL BOLD ITALIC SMALL EPSILON + 'ζ': '\U0001d73b', # 𝜻 MATHEMATICAL BOLD ITALIC SMALL ZETA + 'η': '\U0001d73c', # 𝜼 MATHEMATICAL BOLD ITALIC SMALL ETA + 'θ': '\U0001d73d', # 𝜽 MATHEMATICAL BOLD ITALIC SMALL THETA + 'ι': '\U0001d73e', # 𝜾 MATHEMATICAL BOLD ITALIC SMALL IOTA + 'κ': '\U0001d73f', # 𝜿 MATHEMATICAL BOLD ITALIC SMALL KAPPA + 'λ': '\U0001d740', # 𝝀 MATHEMATICAL BOLD ITALIC SMALL LAMDA + 'μ': '\U0001d741', # 𝝁 MATHEMATICAL BOLD ITALIC SMALL MU + 'ν': '\U0001d742', # 𝝂 MATHEMATICAL BOLD ITALIC SMALL NU + 'ξ': '\U0001d743', # 𝝃 MATHEMATICAL BOLD ITALIC SMALL XI + 'π': '\U0001d745', # 𝝅 MATHEMATICAL BOLD ITALIC SMALL PI + 'ρ': '\U0001d746', # 𝝆 MATHEMATICAL BOLD ITALIC SMALL RHO + 'ς': '\U0001d747', # 𝝇 MATHEMATICAL BOLD ITALIC SMALL FINAL SIGMA + 'σ': '\U0001d748', # 𝝈 MATHEMATICAL BOLD ITALIC SMALL SIGMA + 'τ': '\U0001d749', # 𝝉 MATHEMATICAL BOLD ITALIC SMALL TAU + 'υ': '\U0001d74a', # 𝝊 MATHEMATICAL BOLD ITALIC SMALL UPSILON + 'φ': '\U0001d74b', # 𝝋 MATHEMATICAL BOLD ITALIC SMALL PHI + 'χ': '\U0001d74c', # 𝝌 MATHEMATICAL BOLD ITALIC SMALL CHI + 'ψ': '\U0001d74d', # 𝝍 MATHEMATICAL BOLD ITALIC SMALL PSI + 'ω': '\U0001d74e', # 𝝎 MATHEMATICAL BOLD ITALIC SMALL OMEGA + 'ϑ': '\U0001d751', # 𝝑 MATHEMATICAL BOLD ITALIC THETA SYMBOL + 'ϕ': '\U0001d753', # 𝝓 MATHEMATICAL BOLD ITALIC PHI SYMBOL + 'ϖ': '\U0001d755', # 𝝕 MATHEMATICAL BOLD ITALIC PI SYMBOL + 'ϰ': '\U0001d752', # 𝝒 MATHEMATICAL BOLD ITALIC KAPPA SYMBOL + 'ϱ': '\U0001d754', # 𝝔 MATHEMATICAL BOLD ITALIC RHO SYMBOL + 'ϵ': '\U0001d750', # 𝝐 MATHEMATICAL BOLD ITALIC EPSILON SYMBOL + '∂': '\U0001d74f', # 𝝏 MATHEMATICAL BOLD ITALIC PARTIAL DIFFERENTIAL + '∇': '\U0001d735', # 𝜵 MATHEMATICAL BOLD ITALIC NABLA + } + +mathcal = { + 'A': '\U0001d49c', # 𝒜 MATHEMATICAL SCRIPT CAPITAL A + 'B': '\u212c', # ℬ SCRIPT CAPITAL B + 'C': '\U0001d49e', # 𝒞 MATHEMATICAL SCRIPT CAPITAL C + 'D': '\U0001d49f', # 𝒟 MATHEMATICAL SCRIPT CAPITAL D + 'E': '\u2130', # ℰ SCRIPT CAPITAL E + 'F': '\u2131', # ℱ SCRIPT CAPITAL F + 'G': '\U0001d4a2', # 𝒢 MATHEMATICAL SCRIPT CAPITAL G + 'H': '\u210b', # ℋ SCRIPT CAPITAL H + 'I': '\u2110', # ℐ SCRIPT CAPITAL I + 'J': '\U0001d4a5', # 𝒥 MATHEMATICAL SCRIPT CAPITAL J + 'K': '\U0001d4a6', # 𝒦 MATHEMATICAL SCRIPT CAPITAL K + 'L': '\u2112', # ℒ SCRIPT CAPITAL L + 'M': '\u2133', # ℳ SCRIPT CAPITAL M + 'N': '\U0001d4a9', # 𝒩 MATHEMATICAL SCRIPT CAPITAL N + 'O': '\U0001d4aa', # 𝒪 MATHEMATICAL SCRIPT CAPITAL O + 'P': '\U0001d4ab', # 𝒫 MATHEMATICAL SCRIPT CAPITAL P + 'Q': '\U0001d4ac', # 𝒬 MATHEMATICAL SCRIPT CAPITAL Q + 'R': '\u211b', # ℛ SCRIPT CAPITAL R + 'S': '\U0001d4ae', # 𝒮 MATHEMATICAL SCRIPT CAPITAL S + 'T': '\U0001d4af', # 𝒯 MATHEMATICAL SCRIPT CAPITAL T + 'U': '\U0001d4b0', # 𝒰 MATHEMATICAL SCRIPT CAPITAL U + 'V': '\U0001d4b1', # 𝒱 MATHEMATICAL SCRIPT CAPITAL V + 'W': '\U0001d4b2', # 𝒲 MATHEMATICAL SCRIPT CAPITAL W + 'X': '\U0001d4b3', # 𝒳 MATHEMATICAL SCRIPT CAPITAL X + 'Y': '\U0001d4b4', # 𝒴 MATHEMATICAL SCRIPT CAPITAL Y + 'Z': '\U0001d4b5', # 𝒵 MATHEMATICAL SCRIPT CAPITAL Z + 'a': '\U0001d4b6', # 𝒶 MATHEMATICAL SCRIPT SMALL A + 'b': '\U0001d4b7', # 𝒷 MATHEMATICAL SCRIPT SMALL B + 'c': '\U0001d4b8', # 𝒸 MATHEMATICAL SCRIPT SMALL C + 'd': '\U0001d4b9', # 𝒹 MATHEMATICAL SCRIPT SMALL D + 'e': '\u212f', # ℯ SCRIPT SMALL E + 'f': '\U0001d4bb', # 𝒻 MATHEMATICAL SCRIPT SMALL F + 'g': '\u210a', # ℊ SCRIPT SMALL G + 'h': '\U0001d4bd', # 𝒽 MATHEMATICAL SCRIPT SMALL H + 'i': '\U0001d4be', # 𝒾 MATHEMATICAL SCRIPT SMALL I + 'j': '\U0001d4bf', # 𝒿 MATHEMATICAL SCRIPT SMALL J + 'k': '\U0001d4c0', # 𝓀 MATHEMATICAL SCRIPT SMALL K + 'l': '\U0001d4c1', # 𝓁 MATHEMATICAL SCRIPT SMALL L + 'm': '\U0001d4c2', # 𝓂 MATHEMATICAL SCRIPT SMALL M + 'n': '\U0001d4c3', # 𝓃 MATHEMATICAL SCRIPT SMALL N + 'o': '\u2134', # ℴ SCRIPT SMALL O + 'p': '\U0001d4c5', # 𝓅 MATHEMATICAL SCRIPT SMALL P + 'q': '\U0001d4c6', # 𝓆 MATHEMATICAL SCRIPT SMALL Q + 'r': '\U0001d4c7', # 𝓇 MATHEMATICAL SCRIPT SMALL R + 's': '\U0001d4c8', # 𝓈 MATHEMATICAL SCRIPT SMALL S + 't': '\U0001d4c9', # 𝓉 MATHEMATICAL SCRIPT SMALL T + 'u': '\U0001d4ca', # 𝓊 MATHEMATICAL SCRIPT SMALL U + 'v': '\U0001d4cb', # 𝓋 MATHEMATICAL SCRIPT SMALL V + 'w': '\U0001d4cc', # 𝓌 MATHEMATICAL SCRIPT SMALL W + 'x': '\U0001d4cd', # 𝓍 MATHEMATICAL SCRIPT SMALL X + 'y': '\U0001d4ce', # 𝓎 MATHEMATICAL SCRIPT SMALL Y + 'z': '\U0001d4cf', # 𝓏 MATHEMATICAL SCRIPT SMALL Z + } + +mathfrak = { + 'A': '\U0001d504', # 𝔄 MATHEMATICAL FRAKTUR CAPITAL A + 'B': '\U0001d505', # 𝔅 MATHEMATICAL FRAKTUR CAPITAL B + 'C': '\u212d', # ℭ BLACK-LETTER CAPITAL C + 'D': '\U0001d507', # 𝔇 MATHEMATICAL FRAKTUR CAPITAL D + 'E': '\U0001d508', # 𝔈 MATHEMATICAL FRAKTUR CAPITAL E + 'F': '\U0001d509', # 𝔉 MATHEMATICAL FRAKTUR CAPITAL F + 'G': '\U0001d50a', # 𝔊 MATHEMATICAL FRAKTUR CAPITAL G + 'H': '\u210c', # ℌ BLACK-LETTER CAPITAL H + 'I': '\u2111', # ℑ BLACK-LETTER CAPITAL I + 'J': '\U0001d50d', # 𝔍 MATHEMATICAL FRAKTUR CAPITAL J + 'K': '\U0001d50e', # 𝔎 MATHEMATICAL FRAKTUR CAPITAL K + 'L': '\U0001d50f', # 𝔏 MATHEMATICAL FRAKTUR CAPITAL L + 'M': '\U0001d510', # 𝔐 MATHEMATICAL FRAKTUR CAPITAL M + 'N': '\U0001d511', # 𝔑 MATHEMATICAL FRAKTUR CAPITAL N + 'O': '\U0001d512', # 𝔒 MATHEMATICAL FRAKTUR CAPITAL O + 'P': '\U0001d513', # 𝔓 MATHEMATICAL FRAKTUR CAPITAL P + 'Q': '\U0001d514', # 𝔔 MATHEMATICAL FRAKTUR CAPITAL Q + 'R': '\u211c', # ℜ BLACK-LETTER CAPITAL R + 'S': '\U0001d516', # 𝔖 MATHEMATICAL FRAKTUR CAPITAL S + 'T': '\U0001d517', # 𝔗 MATHEMATICAL FRAKTUR CAPITAL T + 'U': '\U0001d518', # 𝔘 MATHEMATICAL FRAKTUR CAPITAL U + 'V': '\U0001d519', # 𝔙 MATHEMATICAL FRAKTUR CAPITAL V + 'W': '\U0001d51a', # 𝔚 MATHEMATICAL FRAKTUR CAPITAL W + 'X': '\U0001d51b', # 𝔛 MATHEMATICAL FRAKTUR CAPITAL X + 'Y': '\U0001d51c', # 𝔜 MATHEMATICAL FRAKTUR CAPITAL Y + 'Z': '\u2128', # ℨ BLACK-LETTER CAPITAL Z + 'a': '\U0001d51e', # 𝔞 MATHEMATICAL FRAKTUR SMALL A + 'b': '\U0001d51f', # 𝔟 MATHEMATICAL FRAKTUR SMALL B + 'c': '\U0001d520', # 𝔠 MATHEMATICAL FRAKTUR SMALL C + 'd': '\U0001d521', # 𝔡 MATHEMATICAL FRAKTUR SMALL D + 'e': '\U0001d522', # 𝔢 MATHEMATICAL FRAKTUR SMALL E + 'f': '\U0001d523', # 𝔣 MATHEMATICAL FRAKTUR SMALL F + 'g': '\U0001d524', # 𝔤 MATHEMATICAL FRAKTUR SMALL G + 'h': '\U0001d525', # 𝔥 MATHEMATICAL FRAKTUR SMALL H + 'i': '\U0001d526', # 𝔦 MATHEMATICAL FRAKTUR SMALL I + 'j': '\U0001d527', # 𝔧 MATHEMATICAL FRAKTUR SMALL J + 'k': '\U0001d528', # 𝔨 MATHEMATICAL FRAKTUR SMALL K + 'l': '\U0001d529', # 𝔩 MATHEMATICAL FRAKTUR SMALL L + 'm': '\U0001d52a', # 𝔪 MATHEMATICAL FRAKTUR SMALL M + 'n': '\U0001d52b', # 𝔫 MATHEMATICAL FRAKTUR SMALL N + 'o': '\U0001d52c', # 𝔬 MATHEMATICAL FRAKTUR SMALL O + 'p': '\U0001d52d', # 𝔭 MATHEMATICAL FRAKTUR SMALL P + 'q': '\U0001d52e', # 𝔮 MATHEMATICAL FRAKTUR SMALL Q + 'r': '\U0001d52f', # 𝔯 MATHEMATICAL FRAKTUR SMALL R + 's': '\U0001d530', # 𝔰 MATHEMATICAL FRAKTUR SMALL S + 't': '\U0001d531', # 𝔱 MATHEMATICAL FRAKTUR SMALL T + 'u': '\U0001d532', # 𝔲 MATHEMATICAL FRAKTUR SMALL U + 'v': '\U0001d533', # 𝔳 MATHEMATICAL FRAKTUR SMALL V + 'w': '\U0001d534', # 𝔴 MATHEMATICAL FRAKTUR SMALL W + 'x': '\U0001d535', # 𝔵 MATHEMATICAL FRAKTUR SMALL X + 'y': '\U0001d536', # 𝔶 MATHEMATICAL FRAKTUR SMALL Y + 'z': '\U0001d537', # 𝔷 MATHEMATICAL FRAKTUR SMALL Z + } + +mathit = { + 'A': '\U0001d434', # 𝐴 MATHEMATICAL ITALIC CAPITAL A + 'B': '\U0001d435', # 𝐵 MATHEMATICAL ITALIC CAPITAL B + 'C': '\U0001d436', # 𝐶 MATHEMATICAL ITALIC CAPITAL C + 'D': '\U0001d437', # 𝐷 MATHEMATICAL ITALIC CAPITAL D + 'E': '\U0001d438', # 𝐸 MATHEMATICAL ITALIC CAPITAL E + 'F': '\U0001d439', # 𝐹 MATHEMATICAL ITALIC CAPITAL F + 'G': '\U0001d43a', # 𝐺 MATHEMATICAL ITALIC CAPITAL G + 'H': '\U0001d43b', # 𝐻 MATHEMATICAL ITALIC CAPITAL H + 'I': '\U0001d43c', # 𝐼 MATHEMATICAL ITALIC CAPITAL I + 'J': '\U0001d43d', # 𝐽 MATHEMATICAL ITALIC CAPITAL J + 'K': '\U0001d43e', # 𝐾 MATHEMATICAL ITALIC CAPITAL K + 'L': '\U0001d43f', # 𝐿 MATHEMATICAL ITALIC CAPITAL L + 'M': '\U0001d440', # 𝑀 MATHEMATICAL ITALIC CAPITAL M + 'N': '\U0001d441', # 𝑁 MATHEMATICAL ITALIC CAPITAL N + 'O': '\U0001d442', # 𝑂 MATHEMATICAL ITALIC CAPITAL O + 'P': '\U0001d443', # 𝑃 MATHEMATICAL ITALIC CAPITAL P + 'Q': '\U0001d444', # 𝑄 MATHEMATICAL ITALIC CAPITAL Q + 'R': '\U0001d445', # 𝑅 MATHEMATICAL ITALIC CAPITAL R + 'S': '\U0001d446', # 𝑆 MATHEMATICAL ITALIC CAPITAL S + 'T': '\U0001d447', # 𝑇 MATHEMATICAL ITALIC CAPITAL T + 'U': '\U0001d448', # 𝑈 MATHEMATICAL ITALIC CAPITAL U + 'V': '\U0001d449', # 𝑉 MATHEMATICAL ITALIC CAPITAL V + 'W': '\U0001d44a', # 𝑊 MATHEMATICAL ITALIC CAPITAL W + 'X': '\U0001d44b', # 𝑋 MATHEMATICAL ITALIC CAPITAL X + 'Y': '\U0001d44c', # 𝑌 MATHEMATICAL ITALIC CAPITAL Y + 'Z': '\U0001d44d', # 𝑍 MATHEMATICAL ITALIC CAPITAL Z + 'a': '\U0001d44e', # 𝑎 MATHEMATICAL ITALIC SMALL A + 'b': '\U0001d44f', # 𝑏 MATHEMATICAL ITALIC SMALL B + 'c': '\U0001d450', # 𝑐 MATHEMATICAL ITALIC SMALL C + 'd': '\U0001d451', # 𝑑 MATHEMATICAL ITALIC SMALL D + 'e': '\U0001d452', # 𝑒 MATHEMATICAL ITALIC SMALL E + 'f': '\U0001d453', # 𝑓 MATHEMATICAL ITALIC SMALL F + 'g': '\U0001d454', # 𝑔 MATHEMATICAL ITALIC SMALL G + 'h': '\u210e', # ℎ PLANCK CONSTANT + 'i': '\U0001d456', # 𝑖 MATHEMATICAL ITALIC SMALL I + 'j': '\U0001d457', # 𝑗 MATHEMATICAL ITALIC SMALL J + 'k': '\U0001d458', # 𝑘 MATHEMATICAL ITALIC SMALL K + 'l': '\U0001d459', # 𝑙 MATHEMATICAL ITALIC SMALL L + 'm': '\U0001d45a', # 𝑚 MATHEMATICAL ITALIC SMALL M + 'n': '\U0001d45b', # 𝑛 MATHEMATICAL ITALIC SMALL N + 'o': '\U0001d45c', # 𝑜 MATHEMATICAL ITALIC SMALL O + 'p': '\U0001d45d', # 𝑝 MATHEMATICAL ITALIC SMALL P + 'q': '\U0001d45e', # 𝑞 MATHEMATICAL ITALIC SMALL Q + 'r': '\U0001d45f', # 𝑟 MATHEMATICAL ITALIC SMALL R + 's': '\U0001d460', # 𝑠 MATHEMATICAL ITALIC SMALL S + 't': '\U0001d461', # 𝑡 MATHEMATICAL ITALIC SMALL T + 'u': '\U0001d462', # 𝑢 MATHEMATICAL ITALIC SMALL U + 'v': '\U0001d463', # 𝑣 MATHEMATICAL ITALIC SMALL V + 'w': '\U0001d464', # 𝑤 MATHEMATICAL ITALIC SMALL W + 'x': '\U0001d465', # 𝑥 MATHEMATICAL ITALIC SMALL X + 'y': '\U0001d466', # 𝑦 MATHEMATICAL ITALIC SMALL Y + 'z': '\U0001d467', # 𝑧 MATHEMATICAL ITALIC SMALL Z + 'ı': '\U0001d6a4', # 𝚤 MATHEMATICAL ITALIC SMALL DOTLESS I + 'ȷ': '\U0001d6a5', # 𝚥 MATHEMATICAL ITALIC SMALL DOTLESS J + 'Γ': '\U0001d6e4', # 𝛤 MATHEMATICAL ITALIC CAPITAL GAMMA + 'Δ': '\U0001d6e5', # 𝛥 MATHEMATICAL ITALIC CAPITAL DELTA + 'Θ': '\U0001d6e9', # 𝛩 MATHEMATICAL ITALIC CAPITAL THETA + 'Λ': '\U0001d6ec', # 𝛬 MATHEMATICAL ITALIC CAPITAL LAMDA + 'Ξ': '\U0001d6ef', # 𝛯 MATHEMATICAL ITALIC CAPITAL XI + 'Π': '\U0001d6f1', # 𝛱 MATHEMATICAL ITALIC CAPITAL PI + 'Σ': '\U0001d6f4', # 𝛴 MATHEMATICAL ITALIC CAPITAL SIGMA + 'Υ': '\U0001d6f6', # 𝛶 MATHEMATICAL ITALIC CAPITAL UPSILON + 'Φ': '\U0001d6f7', # 𝛷 MATHEMATICAL ITALIC CAPITAL PHI + 'Ψ': '\U0001d6f9', # 𝛹 MATHEMATICAL ITALIC CAPITAL PSI + 'Ω': '\U0001d6fa', # 𝛺 MATHEMATICAL ITALIC CAPITAL OMEGA + 'α': '\U0001d6fc', # 𝛼 MATHEMATICAL ITALIC SMALL ALPHA + 'β': '\U0001d6fd', # 𝛽 MATHEMATICAL ITALIC SMALL BETA + 'γ': '\U0001d6fe', # 𝛾 MATHEMATICAL ITALIC SMALL GAMMA + 'δ': '\U0001d6ff', # 𝛿 MATHEMATICAL ITALIC SMALL DELTA + 'ε': '\U0001d700', # 𝜀 MATHEMATICAL ITALIC SMALL EPSILON + 'ζ': '\U0001d701', # 𝜁 MATHEMATICAL ITALIC SMALL ZETA + 'η': '\U0001d702', # 𝜂 MATHEMATICAL ITALIC SMALL ETA + 'θ': '\U0001d703', # 𝜃 MATHEMATICAL ITALIC SMALL THETA + 'ι': '\U0001d704', # 𝜄 MATHEMATICAL ITALIC SMALL IOTA + 'κ': '\U0001d705', # 𝜅 MATHEMATICAL ITALIC SMALL KAPPA + 'λ': '\U0001d706', # 𝜆 MATHEMATICAL ITALIC SMALL LAMDA + 'μ': '\U0001d707', # 𝜇 MATHEMATICAL ITALIC SMALL MU + 'ν': '\U0001d708', # 𝜈 MATHEMATICAL ITALIC SMALL NU + 'ξ': '\U0001d709', # 𝜉 MATHEMATICAL ITALIC SMALL XI + 'π': '\U0001d70b', # 𝜋 MATHEMATICAL ITALIC SMALL PI + 'ρ': '\U0001d70c', # 𝜌 MATHEMATICAL ITALIC SMALL RHO + 'ς': '\U0001d70d', # 𝜍 MATHEMATICAL ITALIC SMALL FINAL SIGMA + 'σ': '\U0001d70e', # 𝜎 MATHEMATICAL ITALIC SMALL SIGMA + 'τ': '\U0001d70f', # 𝜏 MATHEMATICAL ITALIC SMALL TAU + 'υ': '\U0001d710', # 𝜐 MATHEMATICAL ITALIC SMALL UPSILON + 'φ': '\U0001d711', # 𝜑 MATHEMATICAL ITALIC SMALL PHI + 'χ': '\U0001d712', # 𝜒 MATHEMATICAL ITALIC SMALL CHI + 'ψ': '\U0001d713', # 𝜓 MATHEMATICAL ITALIC SMALL PSI + 'ω': '\U0001d714', # 𝜔 MATHEMATICAL ITALIC SMALL OMEGA + 'ϑ': '\U0001d717', # 𝜗 MATHEMATICAL ITALIC THETA SYMBOL + 'ϕ': '\U0001d719', # 𝜙 MATHEMATICAL ITALIC PHI SYMBOL + 'ϖ': '\U0001d71b', # 𝜛 MATHEMATICAL ITALIC PI SYMBOL + 'ϱ': '\U0001d71a', # 𝜚 MATHEMATICAL ITALIC RHO SYMBOL + 'ϵ': '\U0001d716', # 𝜖 MATHEMATICAL ITALIC EPSILON SYMBOL + '∂': '\U0001d715', # 𝜕 MATHEMATICAL ITALIC PARTIAL DIFFERENTIAL + '∇': '\U0001d6fb', # 𝛻 MATHEMATICAL ITALIC NABLA + } + +mathsf = { + '0': '\U0001d7e2', # 𝟢 MATHEMATICAL SANS-SERIF DIGIT ZERO + '1': '\U0001d7e3', # 𝟣 MATHEMATICAL SANS-SERIF DIGIT ONE + '2': '\U0001d7e4', # 𝟤 MATHEMATICAL SANS-SERIF DIGIT TWO + '3': '\U0001d7e5', # 𝟥 MATHEMATICAL SANS-SERIF DIGIT THREE + '4': '\U0001d7e6', # 𝟦 MATHEMATICAL SANS-SERIF DIGIT FOUR + '5': '\U0001d7e7', # 𝟧 MATHEMATICAL SANS-SERIF DIGIT FIVE + '6': '\U0001d7e8', # 𝟨 MATHEMATICAL SANS-SERIF DIGIT SIX + '7': '\U0001d7e9', # 𝟩 MATHEMATICAL SANS-SERIF DIGIT SEVEN + '8': '\U0001d7ea', # 𝟪 MATHEMATICAL SANS-SERIF DIGIT EIGHT + '9': '\U0001d7eb', # 𝟫 MATHEMATICAL SANS-SERIF DIGIT NINE + 'A': '\U0001d5a0', # 𝖠 MATHEMATICAL SANS-SERIF CAPITAL A + 'B': '\U0001d5a1', # 𝖡 MATHEMATICAL SANS-SERIF CAPITAL B + 'C': '\U0001d5a2', # 𝖢 MATHEMATICAL SANS-SERIF CAPITAL C + 'D': '\U0001d5a3', # 𝖣 MATHEMATICAL SANS-SERIF CAPITAL D + 'E': '\U0001d5a4', # 𝖤 MATHEMATICAL SANS-SERIF CAPITAL E + 'F': '\U0001d5a5', # 𝖥 MATHEMATICAL SANS-SERIF CAPITAL F + 'G': '\U0001d5a6', # 𝖦 MATHEMATICAL SANS-SERIF CAPITAL G + 'H': '\U0001d5a7', # 𝖧 MATHEMATICAL SANS-SERIF CAPITAL H + 'I': '\U0001d5a8', # 𝖨 MATHEMATICAL SANS-SERIF CAPITAL I + 'J': '\U0001d5a9', # 𝖩 MATHEMATICAL SANS-SERIF CAPITAL J + 'K': '\U0001d5aa', # 𝖪 MATHEMATICAL SANS-SERIF CAPITAL K + 'L': '\U0001d5ab', # 𝖫 MATHEMATICAL SANS-SERIF CAPITAL L + 'M': '\U0001d5ac', # 𝖬 MATHEMATICAL SANS-SERIF CAPITAL M + 'N': '\U0001d5ad', # 𝖭 MATHEMATICAL SANS-SERIF CAPITAL N + 'O': '\U0001d5ae', # 𝖮 MATHEMATICAL SANS-SERIF CAPITAL O + 'P': '\U0001d5af', # 𝖯 MATHEMATICAL SANS-SERIF CAPITAL P + 'Q': '\U0001d5b0', # 𝖰 MATHEMATICAL SANS-SERIF CAPITAL Q + 'R': '\U0001d5b1', # 𝖱 MATHEMATICAL SANS-SERIF CAPITAL R + 'S': '\U0001d5b2', # 𝖲 MATHEMATICAL SANS-SERIF CAPITAL S + 'T': '\U0001d5b3', # 𝖳 MATHEMATICAL SANS-SERIF CAPITAL T + 'U': '\U0001d5b4', # 𝖴 MATHEMATICAL SANS-SERIF CAPITAL U + 'V': '\U0001d5b5', # 𝖵 MATHEMATICAL SANS-SERIF CAPITAL V + 'W': '\U0001d5b6', # 𝖶 MATHEMATICAL SANS-SERIF CAPITAL W + 'X': '\U0001d5b7', # 𝖷 MATHEMATICAL SANS-SERIF CAPITAL X + 'Y': '\U0001d5b8', # 𝖸 MATHEMATICAL SANS-SERIF CAPITAL Y + 'Z': '\U0001d5b9', # 𝖹 MATHEMATICAL SANS-SERIF CAPITAL Z + 'a': '\U0001d5ba', # 𝖺 MATHEMATICAL SANS-SERIF SMALL A + 'b': '\U0001d5bb', # 𝖻 MATHEMATICAL SANS-SERIF SMALL B + 'c': '\U0001d5bc', # 𝖼 MATHEMATICAL SANS-SERIF SMALL C + 'd': '\U0001d5bd', # 𝖽 MATHEMATICAL SANS-SERIF SMALL D + 'e': '\U0001d5be', # 𝖾 MATHEMATICAL SANS-SERIF SMALL E + 'f': '\U0001d5bf', # 𝖿 MATHEMATICAL SANS-SERIF SMALL F + 'g': '\U0001d5c0', # 𝗀 MATHEMATICAL SANS-SERIF SMALL G + 'h': '\U0001d5c1', # 𝗁 MATHEMATICAL SANS-SERIF SMALL H + 'i': '\U0001d5c2', # 𝗂 MATHEMATICAL SANS-SERIF SMALL I + 'j': '\U0001d5c3', # 𝗃 MATHEMATICAL SANS-SERIF SMALL J + 'k': '\U0001d5c4', # 𝗄 MATHEMATICAL SANS-SERIF SMALL K + 'l': '\U0001d5c5', # 𝗅 MATHEMATICAL SANS-SERIF SMALL L + 'm': '\U0001d5c6', # 𝗆 MATHEMATICAL SANS-SERIF SMALL M + 'n': '\U0001d5c7', # 𝗇 MATHEMATICAL SANS-SERIF SMALL N + 'o': '\U0001d5c8', # 𝗈 MATHEMATICAL SANS-SERIF SMALL O + 'p': '\U0001d5c9', # 𝗉 MATHEMATICAL SANS-SERIF SMALL P + 'q': '\U0001d5ca', # 𝗊 MATHEMATICAL SANS-SERIF SMALL Q + 'r': '\U0001d5cb', # 𝗋 MATHEMATICAL SANS-SERIF SMALL R + 's': '\U0001d5cc', # 𝗌 MATHEMATICAL SANS-SERIF SMALL S + 't': '\U0001d5cd', # 𝗍 MATHEMATICAL SANS-SERIF SMALL T + 'u': '\U0001d5ce', # 𝗎 MATHEMATICAL SANS-SERIF SMALL U + 'v': '\U0001d5cf', # 𝗏 MATHEMATICAL SANS-SERIF SMALL V + 'w': '\U0001d5d0', # 𝗐 MATHEMATICAL SANS-SERIF SMALL W + 'x': '\U0001d5d1', # 𝗑 MATHEMATICAL SANS-SERIF SMALL X + 'y': '\U0001d5d2', # 𝗒 MATHEMATICAL SANS-SERIF SMALL Y + 'z': '\U0001d5d3', # 𝗓 MATHEMATICAL SANS-SERIF SMALL Z + } + +mathsfbf = { + '0': '\U0001d7ec', # 𝟬 MATHEMATICAL SANS-SERIF BOLD DIGIT ZERO + '1': '\U0001d7ed', # 𝟭 MATHEMATICAL SANS-SERIF BOLD DIGIT ONE + '2': '\U0001d7ee', # 𝟮 MATHEMATICAL SANS-SERIF BOLD DIGIT TWO + '3': '\U0001d7ef', # 𝟯 MATHEMATICAL SANS-SERIF BOLD DIGIT THREE + '4': '\U0001d7f0', # 𝟰 MATHEMATICAL SANS-SERIF BOLD DIGIT FOUR + '5': '\U0001d7f1', # 𝟱 MATHEMATICAL SANS-SERIF BOLD DIGIT FIVE + '6': '\U0001d7f2', # 𝟲 MATHEMATICAL SANS-SERIF BOLD DIGIT SIX + '7': '\U0001d7f3', # 𝟳 MATHEMATICAL SANS-SERIF BOLD DIGIT SEVEN + '8': '\U0001d7f4', # 𝟴 MATHEMATICAL SANS-SERIF BOLD DIGIT EIGHT + '9': '\U0001d7f5', # 𝟵 MATHEMATICAL SANS-SERIF BOLD DIGIT NINE + 'A': '\U0001d5d4', # 𝗔 MATHEMATICAL SANS-SERIF BOLD CAPITAL A + 'B': '\U0001d5d5', # 𝗕 MATHEMATICAL SANS-SERIF BOLD CAPITAL B + 'C': '\U0001d5d6', # 𝗖 MATHEMATICAL SANS-SERIF BOLD CAPITAL C + 'D': '\U0001d5d7', # 𝗗 MATHEMATICAL SANS-SERIF BOLD CAPITAL D + 'E': '\U0001d5d8', # 𝗘 MATHEMATICAL SANS-SERIF BOLD CAPITAL E + 'F': '\U0001d5d9', # 𝗙 MATHEMATICAL SANS-SERIF BOLD CAPITAL F + 'G': '\U0001d5da', # 𝗚 MATHEMATICAL SANS-SERIF BOLD CAPITAL G + 'H': '\U0001d5db', # 𝗛 MATHEMATICAL SANS-SERIF BOLD CAPITAL H + 'I': '\U0001d5dc', # 𝗜 MATHEMATICAL SANS-SERIF BOLD CAPITAL I + 'J': '\U0001d5dd', # 𝗝 MATHEMATICAL SANS-SERIF BOLD CAPITAL J + 'K': '\U0001d5de', # 𝗞 MATHEMATICAL SANS-SERIF BOLD CAPITAL K + 'L': '\U0001d5df', # 𝗟 MATHEMATICAL SANS-SERIF BOLD CAPITAL L + 'M': '\U0001d5e0', # 𝗠 MATHEMATICAL SANS-SERIF BOLD CAPITAL M + 'N': '\U0001d5e1', # 𝗡 MATHEMATICAL SANS-SERIF BOLD CAPITAL N + 'O': '\U0001d5e2', # 𝗢 MATHEMATICAL SANS-SERIF BOLD CAPITAL O + 'P': '\U0001d5e3', # 𝗣 MATHEMATICAL SANS-SERIF BOLD CAPITAL P + 'Q': '\U0001d5e4', # 𝗤 MATHEMATICAL SANS-SERIF BOLD CAPITAL Q + 'R': '\U0001d5e5', # 𝗥 MATHEMATICAL SANS-SERIF BOLD CAPITAL R + 'S': '\U0001d5e6', # 𝗦 MATHEMATICAL SANS-SERIF BOLD CAPITAL S + 'T': '\U0001d5e7', # 𝗧 MATHEMATICAL SANS-SERIF BOLD CAPITAL T + 'U': '\U0001d5e8', # 𝗨 MATHEMATICAL SANS-SERIF BOLD CAPITAL U + 'V': '\U0001d5e9', # 𝗩 MATHEMATICAL SANS-SERIF BOLD CAPITAL V + 'W': '\U0001d5ea', # 𝗪 MATHEMATICAL SANS-SERIF BOLD CAPITAL W + 'X': '\U0001d5eb', # 𝗫 MATHEMATICAL SANS-SERIF BOLD CAPITAL X + 'Y': '\U0001d5ec', # 𝗬 MATHEMATICAL SANS-SERIF BOLD CAPITAL Y + 'Z': '\U0001d5ed', # 𝗭 MATHEMATICAL SANS-SERIF BOLD CAPITAL Z + 'a': '\U0001d5ee', # 𝗮 MATHEMATICAL SANS-SERIF BOLD SMALL A + 'b': '\U0001d5ef', # 𝗯 MATHEMATICAL SANS-SERIF BOLD SMALL B + 'c': '\U0001d5f0', # 𝗰 MATHEMATICAL SANS-SERIF BOLD SMALL C + 'd': '\U0001d5f1', # 𝗱 MATHEMATICAL SANS-SERIF BOLD SMALL D + 'e': '\U0001d5f2', # 𝗲 MATHEMATICAL SANS-SERIF BOLD SMALL E + 'f': '\U0001d5f3', # 𝗳 MATHEMATICAL SANS-SERIF BOLD SMALL F + 'g': '\U0001d5f4', # 𝗴 MATHEMATICAL SANS-SERIF BOLD SMALL G + 'h': '\U0001d5f5', # 𝗵 MATHEMATICAL SANS-SERIF BOLD SMALL H + 'i': '\U0001d5f6', # 𝗶 MATHEMATICAL SANS-SERIF BOLD SMALL I + 'j': '\U0001d5f7', # 𝗷 MATHEMATICAL SANS-SERIF BOLD SMALL J + 'k': '\U0001d5f8', # 𝗸 MATHEMATICAL SANS-SERIF BOLD SMALL K + 'l': '\U0001d5f9', # 𝗹 MATHEMATICAL SANS-SERIF BOLD SMALL L + 'm': '\U0001d5fa', # 𝗺 MATHEMATICAL SANS-SERIF BOLD SMALL M + 'n': '\U0001d5fb', # 𝗻 MATHEMATICAL SANS-SERIF BOLD SMALL N + 'o': '\U0001d5fc', # 𝗼 MATHEMATICAL SANS-SERIF BOLD SMALL O + 'p': '\U0001d5fd', # 𝗽 MATHEMATICAL SANS-SERIF BOLD SMALL P + 'q': '\U0001d5fe', # 𝗾 MATHEMATICAL SANS-SERIF BOLD SMALL Q + 'r': '\U0001d5ff', # 𝗿 MATHEMATICAL SANS-SERIF BOLD SMALL R + 's': '\U0001d600', # 𝘀 MATHEMATICAL SANS-SERIF BOLD SMALL S + 't': '\U0001d601', # 𝘁 MATHEMATICAL SANS-SERIF BOLD SMALL T + 'u': '\U0001d602', # 𝘂 MATHEMATICAL SANS-SERIF BOLD SMALL U + 'v': '\U0001d603', # 𝘃 MATHEMATICAL SANS-SERIF BOLD SMALL V + 'w': '\U0001d604', # 𝘄 MATHEMATICAL SANS-SERIF BOLD SMALL W + 'x': '\U0001d605', # 𝘅 MATHEMATICAL SANS-SERIF BOLD SMALL X + 'y': '\U0001d606', # 𝘆 MATHEMATICAL SANS-SERIF BOLD SMALL Y + 'z': '\U0001d607', # 𝘇 MATHEMATICAL SANS-SERIF BOLD SMALL Z + 'Γ': '\U0001d758', # 𝝘 MATHEMATICAL SANS-SERIF BOLD CAPITAL GAMMA + 'Δ': '\U0001d759', # 𝝙 MATHEMATICAL SANS-SERIF BOLD CAPITAL DELTA + 'Θ': '\U0001d75d', # 𝝝 MATHEMATICAL SANS-SERIF BOLD CAPITAL THETA + 'Λ': '\U0001d760', # 𝝠 MATHEMATICAL SANS-SERIF BOLD CAPITAL LAMDA + 'Ξ': '\U0001d763', # 𝝣 MATHEMATICAL SANS-SERIF BOLD CAPITAL XI + 'Π': '\U0001d765', # 𝝥 MATHEMATICAL SANS-SERIF BOLD CAPITAL PI + 'Σ': '\U0001d768', # 𝝨 MATHEMATICAL SANS-SERIF BOLD CAPITAL SIGMA + 'Υ': '\U0001d76a', # 𝝪 MATHEMATICAL SANS-SERIF BOLD CAPITAL UPSILON + 'Φ': '\U0001d76b', # 𝝫 MATHEMATICAL SANS-SERIF BOLD CAPITAL PHI + 'Ψ': '\U0001d76d', # 𝝭 MATHEMATICAL SANS-SERIF BOLD CAPITAL PSI + 'Ω': '\U0001d76e', # 𝝮 MATHEMATICAL SANS-SERIF BOLD CAPITAL OMEGA + 'α': '\U0001d770', # 𝝰 MATHEMATICAL SANS-SERIF BOLD SMALL ALPHA + 'β': '\U0001d771', # 𝝱 MATHEMATICAL SANS-SERIF BOLD SMALL BETA + 'γ': '\U0001d772', # 𝝲 MATHEMATICAL SANS-SERIF BOLD SMALL GAMMA + 'δ': '\U0001d773', # 𝝳 MATHEMATICAL SANS-SERIF BOLD SMALL DELTA + 'ε': '\U0001d774', # 𝝴 MATHEMATICAL SANS-SERIF BOLD SMALL EPSILON + 'ζ': '\U0001d775', # 𝝵 MATHEMATICAL SANS-SERIF BOLD SMALL ZETA + 'η': '\U0001d776', # 𝝶 MATHEMATICAL SANS-SERIF BOLD SMALL ETA + 'θ': '\U0001d777', # 𝝷 MATHEMATICAL SANS-SERIF BOLD SMALL THETA + 'ι': '\U0001d778', # 𝝸 MATHEMATICAL SANS-SERIF BOLD SMALL IOTA + 'κ': '\U0001d779', # 𝝹 MATHEMATICAL SANS-SERIF BOLD SMALL KAPPA + 'λ': '\U0001d77a', # 𝝺 MATHEMATICAL SANS-SERIF BOLD SMALL LAMDA + 'μ': '\U0001d77b', # 𝝻 MATHEMATICAL SANS-SERIF BOLD SMALL MU + 'ν': '\U0001d77c', # 𝝼 MATHEMATICAL SANS-SERIF BOLD SMALL NU + 'ξ': '\U0001d77d', # 𝝽 MATHEMATICAL SANS-SERIF BOLD SMALL XI + 'π': '\U0001d77f', # 𝝿 MATHEMATICAL SANS-SERIF BOLD SMALL PI + 'ρ': '\U0001d780', # 𝞀 MATHEMATICAL SANS-SERIF BOLD SMALL RHO + 'ς': '\U0001d781', # 𝞁 MATHEMATICAL SANS-SERIF BOLD SMALL FINAL SIGMA + 'σ': '\U0001d782', # 𝞂 MATHEMATICAL SANS-SERIF BOLD SMALL SIGMA + 'τ': '\U0001d783', # 𝞃 MATHEMATICAL SANS-SERIF BOLD SMALL TAU + 'υ': '\U0001d784', # 𝞄 MATHEMATICAL SANS-SERIF BOLD SMALL UPSILON + 'φ': '\U0001d785', # 𝞅 MATHEMATICAL SANS-SERIF BOLD SMALL PHI + 'χ': '\U0001d786', # 𝞆 MATHEMATICAL SANS-SERIF BOLD SMALL CHI + 'ψ': '\U0001d787', # 𝞇 MATHEMATICAL SANS-SERIF BOLD SMALL PSI + 'ω': '\U0001d788', # 𝞈 MATHEMATICAL SANS-SERIF BOLD SMALL OMEGA + 'ϑ': '\U0001d78b', # 𝞋 MATHEMATICAL SANS-SERIF BOLD THETA SYMBOL + 'ϕ': '\U0001d78d', # 𝞍 MATHEMATICAL SANS-SERIF BOLD PHI SYMBOL + 'ϖ': '\U0001d78f', # 𝞏 MATHEMATICAL SANS-SERIF BOLD PI SYMBOL + 'ϱ': '\U0001d78e', # 𝞎 MATHEMATICAL SANS-SERIF BOLD RHO SYMBOL + 'ϵ': '\U0001d78a', # 𝞊 MATHEMATICAL SANS-SERIF BOLD EPSILON SYMBOL + '∇': '\U0001d76f', # 𝝯 MATHEMATICAL SANS-SERIF BOLD NABLA + } + +mathsfbfit = { + 'A': '\U0001d63c', # 𝘼 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL A + 'B': '\U0001d63d', # 𝘽 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL B + 'C': '\U0001d63e', # 𝘾 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL C + 'D': '\U0001d63f', # 𝘿 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL D + 'E': '\U0001d640', # 𝙀 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL E + 'F': '\U0001d641', # 𝙁 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL F + 'G': '\U0001d642', # 𝙂 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL G + 'H': '\U0001d643', # 𝙃 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL H + 'I': '\U0001d644', # 𝙄 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL I + 'J': '\U0001d645', # 𝙅 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL J + 'K': '\U0001d646', # 𝙆 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL K + 'L': '\U0001d647', # 𝙇 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL L + 'M': '\U0001d648', # 𝙈 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL M + 'N': '\U0001d649', # 𝙉 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL N + 'O': '\U0001d64a', # 𝙊 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL O + 'P': '\U0001d64b', # 𝙋 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL P + 'Q': '\U0001d64c', # 𝙌 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Q + 'R': '\U0001d64d', # 𝙍 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL R + 'S': '\U0001d64e', # 𝙎 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL S + 'T': '\U0001d64f', # 𝙏 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL T + 'U': '\U0001d650', # 𝙐 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL U + 'V': '\U0001d651', # 𝙑 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL V + 'W': '\U0001d652', # 𝙒 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL W + 'X': '\U0001d653', # 𝙓 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL X + 'Y': '\U0001d654', # 𝙔 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Y + 'Z': '\U0001d655', # 𝙕 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL Z + 'a': '\U0001d656', # 𝙖 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL A + 'b': '\U0001d657', # 𝙗 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL B + 'c': '\U0001d658', # 𝙘 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL C + 'd': '\U0001d659', # 𝙙 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL D + 'e': '\U0001d65a', # 𝙚 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL E + 'f': '\U0001d65b', # 𝙛 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL F + 'g': '\U0001d65c', # 𝙜 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL G + 'h': '\U0001d65d', # 𝙝 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL H + 'i': '\U0001d65e', # 𝙞 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL I + 'j': '\U0001d65f', # 𝙟 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL J + 'k': '\U0001d660', # 𝙠 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL K + 'l': '\U0001d661', # 𝙡 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL L + 'm': '\U0001d662', # 𝙢 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL M + 'n': '\U0001d663', # 𝙣 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL N + 'o': '\U0001d664', # 𝙤 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL O + 'p': '\U0001d665', # 𝙥 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL P + 'q': '\U0001d666', # 𝙦 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Q + 'r': '\U0001d667', # 𝙧 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL R + 's': '\U0001d668', # 𝙨 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL S + 't': '\U0001d669', # 𝙩 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL T + 'u': '\U0001d66a', # 𝙪 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL U + 'v': '\U0001d66b', # 𝙫 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL V + 'w': '\U0001d66c', # 𝙬 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL W + 'x': '\U0001d66d', # 𝙭 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL X + 'y': '\U0001d66e', # 𝙮 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Y + 'z': '\U0001d66f', # 𝙯 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL Z + 'Γ': '\U0001d792', # 𝞒 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL GAMMA + 'Δ': '\U0001d793', # 𝞓 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL DELTA + 'Θ': '\U0001d797', # 𝞗 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL THETA + 'Λ': '\U0001d79a', # 𝞚 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL LAMDA + 'Ξ': '\U0001d79d', # 𝞝 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL XI + 'Π': '\U0001d79f', # 𝞟 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PI + 'Σ': '\U0001d7a2', # 𝞢 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL SIGMA + 'Υ': '\U0001d7a4', # 𝞤 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL UPSILON + 'Φ': '\U0001d7a5', # 𝞥 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PHI + 'Ψ': '\U0001d7a7', # 𝞧 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL PSI + 'Ω': '\U0001d7a8', # 𝞨 MATHEMATICAL SANS-SERIF BOLD ITALIC CAPITAL OMEGA + 'α': '\U0001d7aa', # 𝞪 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ALPHA + 'β': '\U0001d7ab', # 𝞫 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL BETA + 'γ': '\U0001d7ac', # 𝞬 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL GAMMA + 'δ': '\U0001d7ad', # 𝞭 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL DELTA + 'ε': '\U0001d7ae', # 𝞮 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL EPSILON + 'ζ': '\U0001d7af', # 𝞯 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ZETA + 'η': '\U0001d7b0', # 𝞰 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL ETA + 'θ': '\U0001d7b1', # 𝞱 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL THETA + 'ι': '\U0001d7b2', # 𝞲 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL IOTA + 'κ': '\U0001d7b3', # 𝞳 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL KAPPA + 'λ': '\U0001d7b4', # 𝞴 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL LAMDA + 'μ': '\U0001d7b5', # 𝞵 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL MU + 'ν': '\U0001d7b6', # 𝞶 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL NU + 'ξ': '\U0001d7b7', # 𝞷 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL XI + 'π': '\U0001d7b9', # 𝞹 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PI + 'ρ': '\U0001d7ba', # 𝞺 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL RHO + 'ς': '\U0001d7bb', # 𝞻 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL FINAL SIGMA + 'σ': '\U0001d7bc', # 𝞼 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL SIGMA + 'τ': '\U0001d7bd', # 𝞽 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL TAU + 'υ': '\U0001d7be', # 𝞾 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL UPSILON + 'φ': '\U0001d7bf', # 𝞿 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PHI + 'χ': '\U0001d7c0', # 𝟀 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL CHI + 'ψ': '\U0001d7c1', # 𝟁 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL PSI + 'ω': '\U0001d7c2', # 𝟂 MATHEMATICAL SANS-SERIF BOLD ITALIC SMALL OMEGA + 'ϑ': '\U0001d7c5', # 𝟅 MATHEMATICAL SANS-SERIF BOLD ITALIC THETA SYMBOL + 'ϕ': '\U0001d7c7', # 𝟇 MATHEMATICAL SANS-SERIF BOLD ITALIC PHI SYMBOL + 'ϖ': '\U0001d7c9', # 𝟉 MATHEMATICAL SANS-SERIF BOLD ITALIC PI SYMBOL + 'ϰ': '\U0001d7c6', # 𝟆 MATHEMATICAL SANS-SERIF BOLD ITALIC KAPPA SYMBOL + 'ϱ': '\U0001d7c8', # 𝟈 MATHEMATICAL SANS-SERIF BOLD ITALIC RHO SYMBOL + 'ϵ': '\U0001d7c4', # 𝟄 MATHEMATICAL SANS-SERIF BOLD ITALIC EPSILON SYMBOL + '∂': '\U0001d7c3', # 𝟃 MATHEMATICAL SANS-SERIF BOLD ITALIC PARTIAL DIFFERENTIAL + '∇': '\U0001d7a9', # 𝞩 MATHEMATICAL SANS-SERIF BOLD ITALIC NABLA + } + +mathsfit = { + 'A': '\U0001d608', # 𝘈 MATHEMATICAL SANS-SERIF ITALIC CAPITAL A + 'B': '\U0001d609', # 𝘉 MATHEMATICAL SANS-SERIF ITALIC CAPITAL B + 'C': '\U0001d60a', # 𝘊 MATHEMATICAL SANS-SERIF ITALIC CAPITAL C + 'D': '\U0001d60b', # 𝘋 MATHEMATICAL SANS-SERIF ITALIC CAPITAL D + 'E': '\U0001d60c', # 𝘌 MATHEMATICAL SANS-SERIF ITALIC CAPITAL E + 'F': '\U0001d60d', # 𝘍 MATHEMATICAL SANS-SERIF ITALIC CAPITAL F + 'G': '\U0001d60e', # 𝘎 MATHEMATICAL SANS-SERIF ITALIC CAPITAL G + 'H': '\U0001d60f', # 𝘏 MATHEMATICAL SANS-SERIF ITALIC CAPITAL H + 'I': '\U0001d610', # 𝘐 MATHEMATICAL SANS-SERIF ITALIC CAPITAL I + 'J': '\U0001d611', # 𝘑 MATHEMATICAL SANS-SERIF ITALIC CAPITAL J + 'K': '\U0001d612', # 𝘒 MATHEMATICAL SANS-SERIF ITALIC CAPITAL K + 'L': '\U0001d613', # 𝘓 MATHEMATICAL SANS-SERIF ITALIC CAPITAL L + 'M': '\U0001d614', # 𝘔 MATHEMATICAL SANS-SERIF ITALIC CAPITAL M + 'N': '\U0001d615', # 𝘕 MATHEMATICAL SANS-SERIF ITALIC CAPITAL N + 'O': '\U0001d616', # 𝘖 MATHEMATICAL SANS-SERIF ITALIC CAPITAL O + 'P': '\U0001d617', # 𝘗 MATHEMATICAL SANS-SERIF ITALIC CAPITAL P + 'Q': '\U0001d618', # 𝘘 MATHEMATICAL SANS-SERIF ITALIC CAPITAL Q + 'R': '\U0001d619', # 𝘙 MATHEMATICAL SANS-SERIF ITALIC CAPITAL R + 'S': '\U0001d61a', # 𝘚 MATHEMATICAL SANS-SERIF ITALIC CAPITAL S + 'T': '\U0001d61b', # 𝘛 MATHEMATICAL SANS-SERIF ITALIC CAPITAL T + 'U': '\U0001d61c', # 𝘜 MATHEMATICAL SANS-SERIF ITALIC CAPITAL U + 'V': '\U0001d61d', # 𝘝 MATHEMATICAL SANS-SERIF ITALIC CAPITAL V + 'W': '\U0001d61e', # 𝘞 MATHEMATICAL SANS-SERIF ITALIC CAPITAL W + 'X': '\U0001d61f', # 𝘟 MATHEMATICAL SANS-SERIF ITALIC CAPITAL X + 'Y': '\U0001d620', # 𝘠 MATHEMATICAL SANS-SERIF ITALIC CAPITAL Y + 'Z': '\U0001d621', # 𝘡 MATHEMATICAL SANS-SERIF ITALIC CAPITAL Z + 'a': '\U0001d622', # 𝘢 MATHEMATICAL SANS-SERIF ITALIC SMALL A + 'b': '\U0001d623', # 𝘣 MATHEMATICAL SANS-SERIF ITALIC SMALL B + 'c': '\U0001d624', # 𝘤 MATHEMATICAL SANS-SERIF ITALIC SMALL C + 'd': '\U0001d625', # 𝘥 MATHEMATICAL SANS-SERIF ITALIC SMALL D + 'e': '\U0001d626', # 𝘦 MATHEMATICAL SANS-SERIF ITALIC SMALL E + 'f': '\U0001d627', # 𝘧 MATHEMATICAL SANS-SERIF ITALIC SMALL F + 'g': '\U0001d628', # 𝘨 MATHEMATICAL SANS-SERIF ITALIC SMALL G + 'h': '\U0001d629', # 𝘩 MATHEMATICAL SANS-SERIF ITALIC SMALL H + 'i': '\U0001d62a', # 𝘪 MATHEMATICAL SANS-SERIF ITALIC SMALL I + 'j': '\U0001d62b', # 𝘫 MATHEMATICAL SANS-SERIF ITALIC SMALL J + 'k': '\U0001d62c', # 𝘬 MATHEMATICAL SANS-SERIF ITALIC SMALL K + 'l': '\U0001d62d', # 𝘭 MATHEMATICAL SANS-SERIF ITALIC SMALL L + 'm': '\U0001d62e', # 𝘮 MATHEMATICAL SANS-SERIF ITALIC SMALL M + 'n': '\U0001d62f', # 𝘯 MATHEMATICAL SANS-SERIF ITALIC SMALL N + 'o': '\U0001d630', # 𝘰 MATHEMATICAL SANS-SERIF ITALIC SMALL O + 'p': '\U0001d631', # 𝘱 MATHEMATICAL SANS-SERIF ITALIC SMALL P + 'q': '\U0001d632', # 𝘲 MATHEMATICAL SANS-SERIF ITALIC SMALL Q + 'r': '\U0001d633', # 𝘳 MATHEMATICAL SANS-SERIF ITALIC SMALL R + 's': '\U0001d634', # 𝘴 MATHEMATICAL SANS-SERIF ITALIC SMALL S + 't': '\U0001d635', # 𝘵 MATHEMATICAL SANS-SERIF ITALIC SMALL T + 'u': '\U0001d636', # 𝘶 MATHEMATICAL SANS-SERIF ITALIC SMALL U + 'v': '\U0001d637', # 𝘷 MATHEMATICAL SANS-SERIF ITALIC SMALL V + 'w': '\U0001d638', # 𝘸 MATHEMATICAL SANS-SERIF ITALIC SMALL W + 'x': '\U0001d639', # 𝘹 MATHEMATICAL SANS-SERIF ITALIC SMALL X + 'y': '\U0001d63a', # 𝘺 MATHEMATICAL SANS-SERIF ITALIC SMALL Y + 'z': '\U0001d63b', # 𝘻 MATHEMATICAL SANS-SERIF ITALIC SMALL Z + } + +mathtt = { + '0': '\U0001d7f6', # 𝟶 MATHEMATICAL MONOSPACE DIGIT ZERO + '1': '\U0001d7f7', # 𝟷 MATHEMATICAL MONOSPACE DIGIT ONE + '2': '\U0001d7f8', # 𝟸 MATHEMATICAL MONOSPACE DIGIT TWO + '3': '\U0001d7f9', # 𝟹 MATHEMATICAL MONOSPACE DIGIT THREE + '4': '\U0001d7fa', # 𝟺 MATHEMATICAL MONOSPACE DIGIT FOUR + '5': '\U0001d7fb', # 𝟻 MATHEMATICAL MONOSPACE DIGIT FIVE + '6': '\U0001d7fc', # 𝟼 MATHEMATICAL MONOSPACE DIGIT SIX + '7': '\U0001d7fd', # 𝟽 MATHEMATICAL MONOSPACE DIGIT SEVEN + '8': '\U0001d7fe', # 𝟾 MATHEMATICAL MONOSPACE DIGIT EIGHT + '9': '\U0001d7ff', # 𝟿 MATHEMATICAL MONOSPACE DIGIT NINE + 'A': '\U0001d670', # 𝙰 MATHEMATICAL MONOSPACE CAPITAL A + 'B': '\U0001d671', # 𝙱 MATHEMATICAL MONOSPACE CAPITAL B + 'C': '\U0001d672', # 𝙲 MATHEMATICAL MONOSPACE CAPITAL C + 'D': '\U0001d673', # 𝙳 MATHEMATICAL MONOSPACE CAPITAL D + 'E': '\U0001d674', # 𝙴 MATHEMATICAL MONOSPACE CAPITAL E + 'F': '\U0001d675', # 𝙵 MATHEMATICAL MONOSPACE CAPITAL F + 'G': '\U0001d676', # 𝙶 MATHEMATICAL MONOSPACE CAPITAL G + 'H': '\U0001d677', # 𝙷 MATHEMATICAL MONOSPACE CAPITAL H + 'I': '\U0001d678', # 𝙸 MATHEMATICAL MONOSPACE CAPITAL I + 'J': '\U0001d679', # 𝙹 MATHEMATICAL MONOSPACE CAPITAL J + 'K': '\U0001d67a', # 𝙺 MATHEMATICAL MONOSPACE CAPITAL K + 'L': '\U0001d67b', # 𝙻 MATHEMATICAL MONOSPACE CAPITAL L + 'M': '\U0001d67c', # 𝙼 MATHEMATICAL MONOSPACE CAPITAL M + 'N': '\U0001d67d', # 𝙽 MATHEMATICAL MONOSPACE CAPITAL N + 'O': '\U0001d67e', # 𝙾 MATHEMATICAL MONOSPACE CAPITAL O + 'P': '\U0001d67f', # 𝙿 MATHEMATICAL MONOSPACE CAPITAL P + 'Q': '\U0001d680', # 𝚀 MATHEMATICAL MONOSPACE CAPITAL Q + 'R': '\U0001d681', # 𝚁 MATHEMATICAL MONOSPACE CAPITAL R + 'S': '\U0001d682', # 𝚂 MATHEMATICAL MONOSPACE CAPITAL S + 'T': '\U0001d683', # 𝚃 MATHEMATICAL MONOSPACE CAPITAL T + 'U': '\U0001d684', # 𝚄 MATHEMATICAL MONOSPACE CAPITAL U + 'V': '\U0001d685', # 𝚅 MATHEMATICAL MONOSPACE CAPITAL V + 'W': '\U0001d686', # 𝚆 MATHEMATICAL MONOSPACE CAPITAL W + 'X': '\U0001d687', # 𝚇 MATHEMATICAL MONOSPACE CAPITAL X + 'Y': '\U0001d688', # 𝚈 MATHEMATICAL MONOSPACE CAPITAL Y + 'Z': '\U0001d689', # 𝚉 MATHEMATICAL MONOSPACE CAPITAL Z + 'a': '\U0001d68a', # 𝚊 MATHEMATICAL MONOSPACE SMALL A + 'b': '\U0001d68b', # 𝚋 MATHEMATICAL MONOSPACE SMALL B + 'c': '\U0001d68c', # 𝚌 MATHEMATICAL MONOSPACE SMALL C + 'd': '\U0001d68d', # 𝚍 MATHEMATICAL MONOSPACE SMALL D + 'e': '\U0001d68e', # 𝚎 MATHEMATICAL MONOSPACE SMALL E + 'f': '\U0001d68f', # 𝚏 MATHEMATICAL MONOSPACE SMALL F + 'g': '\U0001d690', # 𝚐 MATHEMATICAL MONOSPACE SMALL G + 'h': '\U0001d691', # 𝚑 MATHEMATICAL MONOSPACE SMALL H + 'i': '\U0001d692', # 𝚒 MATHEMATICAL MONOSPACE SMALL I + 'j': '\U0001d693', # 𝚓 MATHEMATICAL MONOSPACE SMALL J + 'k': '\U0001d694', # 𝚔 MATHEMATICAL MONOSPACE SMALL K + 'l': '\U0001d695', # 𝚕 MATHEMATICAL MONOSPACE SMALL L + 'm': '\U0001d696', # 𝚖 MATHEMATICAL MONOSPACE SMALL M + 'n': '\U0001d697', # 𝚗 MATHEMATICAL MONOSPACE SMALL N + 'o': '\U0001d698', # 𝚘 MATHEMATICAL MONOSPACE SMALL O + 'p': '\U0001d699', # 𝚙 MATHEMATICAL MONOSPACE SMALL P + 'q': '\U0001d69a', # 𝚚 MATHEMATICAL MONOSPACE SMALL Q + 'r': '\U0001d69b', # 𝚛 MATHEMATICAL MONOSPACE SMALL R + 's': '\U0001d69c', # 𝚜 MATHEMATICAL MONOSPACE SMALL S + 't': '\U0001d69d', # 𝚝 MATHEMATICAL MONOSPACE SMALL T + 'u': '\U0001d69e', # 𝚞 MATHEMATICAL MONOSPACE SMALL U + 'v': '\U0001d69f', # 𝚟 MATHEMATICAL MONOSPACE SMALL V + 'w': '\U0001d6a0', # 𝚠 MATHEMATICAL MONOSPACE SMALL W + 'x': '\U0001d6a1', # 𝚡 MATHEMATICAL MONOSPACE SMALL X + 'y': '\U0001d6a2', # 𝚢 MATHEMATICAL MONOSPACE SMALL Y + 'z': '\U0001d6a3', # 𝚣 MATHEMATICAL MONOSPACE SMALL Z + } diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py new file mode 100644 index 00000000..f2059c9f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/mathml_elements.py @@ -0,0 +1,478 @@ +# :Id: $Id: mathml_elements.py 9561 2024-03-14 16:34:48Z milde $ +# :Copyright: 2024 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 + +"""MathML element classes based on `xml.etree`. + +The module is intended for programmatic generation of MathML +and covers the part of `MathML Core`_ that is required by +Docutil's *TeX math to MathML* converter. + +This module is PROVISIONAL: +the API is not settled and may change with any minor Docutils version. + +.. _MathML Core: https://www.w3.org/TR/mathml-core/ +""" + +# Usage: +# +# >>> from mathml_elements import * + +import numbers +import xml.etree.ElementTree as ET + + +GLOBAL_ATTRIBUTES = ( + 'class', # space-separated list of element classes + # 'data-*', # custom data attributes (see HTML) + 'dir', # directionality ('ltr', 'rtl') + 'displaystyle', # True: normal, False: compact + 'id', # unique identifier + # 'mathbackground', # color definition, deprecated + # 'mathcolor', # color definition, deprecated + # 'mathsize', # font-size, deprecated + 'nonce', # cryptographic nonce ("number used once") + 'scriptlevel', # math-depth for the element + 'style', # CSS styling declarations + 'tabindex', # indicate if the element takes input focus + ) +"""Global MathML attributes + +https://w3c.github.io/mathml-core/#global-attributes +""" + + +# Base classes +# ------------ + +class MathElement(ET.Element): + """Base class for MathML elements.""" + + nchildren = None + """Expected number of children or None""" + # cf. https://www.w3.org/TR/MathML3/chapter3.html#id.3.1.3.2 + parent = None + """Parent node in MathML element tree.""" + + def __init__(self, *children, **attributes): + """Set up node with `children` and `attributes`. + + Attribute names are normalised to lowercase. + You may use "CLASS" to set a "class" attribute. + Attribute values are converted to strings + (with True -> "true" and False -> "false"). + + >>> math(CLASS='test', level=3, split=True) + math(class='test', level='3', split='true') + >>> math(CLASS='test', level=3, split=True).toxml() + '<math class="test" level="3" split="true"></math>' + + """ + attrib = {k.lower(): self.a_str(v) for k, v in attributes.items()} + super().__init__(self.__class__.__name__, **attrib) + self.extend(children) + + @staticmethod + def a_str(v): + # Return string representation for attribute value `v`. + if isinstance(v, bool): + return str(v).lower() + return str(v) + + def __repr__(self): + """Return full string representation.""" + args = [repr(child) for child in self] + if self.text: + args.append(repr(self.text)) + if self.nchildren != self.__class__.nchildren: + args.append(f'nchildren={self.nchildren}') + if getattr(self, 'switch', None): + args.append('switch=True') + args += [f'{k}={v!r}' for k, v in self.items() if v is not None] + return f'{self.tag}({", ".join(args)})' + + def __str__(self): + """Return concise, informal string representation.""" + if self.text: + args = repr(self.text) + else: + args = ', '.join(f'{child}' for child in self) + return f'{self.tag}({args})' + + def set(self, key, value): + super().set(key, self.a_str(value)) + + def __setitem__(self, key, value): + if self.nchildren == 0: + raise TypeError(f'Element "{self}" does not take children.') + if isinstance(value, MathElement): + value.parent = self + else: # value may be an iterable + if self.nchildren and len(self) + len(value) > self.nchildren: + raise TypeError(f'Element "{self}" takes only {self.nchildren}' + ' children') + for e in value: + e.parent = self + super().__setitem__(key, value) + + def is_full(self): + """Return boolean indicating whether children may be appended.""" + return self.nchildren is not None and len(self) >= self.nchildren + + def close(self): + """Close element and return first non-full anchestor or None.""" + self.nchildren = len(self) # mark node as full + parent = self.parent + while parent is not None and parent.is_full(): + parent = parent.parent + return parent + + def append(self, element): + """Append `element` and return new "current node" (insertion point). + + Append as child element and set the internal `parent` attribute. + + If self is already full, raise TypeError. + + If self is full after appending, call `self.close()` + (returns first non-full anchestor or None) else return `self`. + """ + if self.is_full(): + if self.nchildren: + status = f'takes only {self.nchildren} children' + else: + status = 'does not take children' + raise TypeError(f'Element "{self}" {status}.') + super().append(element) + element.parent = self + if self.is_full(): + return self.close() + return self + + def extend(self, elements): + """Sequentially append `elements`. Return new "current node". + + Raise TypeError if overfull. + """ + current_node = self + for element in elements: + current_node = self.append(element) + return current_node + + def pop(self, index=-1): + element = self[index] + del self[index] + return element + + def in_block(self): + """Return True, if `self` or an ancestor has ``display='block'``. + + Used to find out whether we are in inline vs. displayed maths. + """ + if self.get('display') is None: + try: + return self.parent.in_block() + except AttributeError: + return False + return self.get('display') == 'block' + + # XML output: + + def indent_xml(self, space=' ', level=0): + """Format XML output with indents. + + Use with care: + Formatting whitespace is permanently added to the + `text` and `tail` attributes of `self` and anchestors! + """ + ET.indent(self, space, level) + + def unindent_xml(self): + """Strip whitespace at the end of `text` and `tail` attributes... + + to revert changes made by the `indent_xml()` method. + Use with care, trailing whitespace from the original may be lost. + """ + for e in self.iter(): + if not isinstance(e, MathToken) and e.text: + e.text = e.text.rstrip() + if e.tail: + e.tail = e.tail.rstrip() + + def toxml(self, encoding=None): + """Return an XML representation of the element. + + By default, the return value is a `str` instance. With an explicit + `encoding` argument, the result is a `bytes` instance in the + specified encoding. The XML default encoding is UTF-8, any other + encoding must be specified in an XML document header. + + Name and encoding handling match `xml.dom.minidom.Node.toxml()` + while `etree.Element.tostring()` returns `bytes` by default. + """ + xml = ET.tostring(self, encoding or 'unicode', + short_empty_elements=False) + # Visible representation for "Apply Function" character: + try: + xml = xml.replace('\u2061', '⁡') + except TypeError: + xml = xml.replace('\u2061'.encode(encoding), b'⁡') + return xml + + +# Group sub-expressions in a horizontal row +# +# The elements <msqrt>, <mstyle>, <merror>, <mpadded>, <mphantom>, +# <menclose>, <mtd>, <mscarry>, and <math> treat their contents +# as a single inferred mrow formed from all their children. +# (https://www.w3.org/TR/mathml4/#presm_inferredmrow) +# +# MathML Core uses the term "anonymous mrow element". + +class MathRow(MathElement): + """Base class for elements treating content as a single mrow.""" + + +# 2d Schemata + +class MathSchema(MathElement): + """Base class for schemata expecting 2 or more children. + + The special attribute `switch` indicates that the last two child + elements are in reversed order and must be switched before XML-export. + See `msub` for an example. + """ + nchildren = 2 + + def __init__(self, *children, **kwargs): + self.switch = kwargs.pop('switch', False) + super().__init__(*children, **kwargs) + + def append(self, element): + """Append element. Normalize order and close if full.""" + current_node = super().append(element) + if self.switch and self.is_full(): + self[-1], self[-2] = self[-2], self[-1] + self.switch = False + return current_node + + +# Token elements represent the smallest units of mathematical notation which +# carry meaning. + +class MathToken(MathElement): + """Token Element: contains textual data instead of children. + + Expect text data on initialisation. + """ + nchildren = 0 + + def __init__(self, text, **attributes): + super().__init__(**attributes) + if not isinstance(text, (str, numbers.Number)): + raise ValueError('MathToken element expects `str` or number,' + f' not "{text}".') + self.text = str(text) + + +# MathML element classes +# ---------------------- + +class math(MathRow): + """Top-level MathML element, a single mathematical formula.""" + + +# Token elements +# ~~~~~~~~~~~~~~ + +class mtext(MathToken): + """Arbitrary text with no notational meaning.""" + + +class mi(MathToken): + """Identifier, such as a function name, variable or symbolic constant.""" + + +class mn(MathToken): + """Numeric literal. + + >>> mn(3.41).toxml() + '<mn>3.41</mn>' + + Normally a sequence of digits with a possible separator (a dot or a comma). + (Values with comma must be specified as `str`.) + """ + + +class mo(MathToken): + """Operator, Fence, Separator, or Accent. + + >>> mo('<').toxml() + '<mo><</mo>' + + Besides operators in strict mathematical meaning, this element also + includes "operators" like parentheses, separators like comma and + semicolon, or "absolute value" bars. + """ + + +class mspace(MathElement): + """Blank space, whose size is set by its attributes. + + Takes additional attributes `depth`, `height`, `width`. + Takes no children and no text. + + See also `mphantom`. + """ + nchildren = 0 + + +# General Layout Schemata +# ~~~~~~~~~~~~~~~~~~~~~~~ + +class mrow(MathRow): + """Generic element to group children as a horizontal row. + + Removed on closing if not required (see `mrow.close()`). + """ + + def transfer_attributes(self, other): + """Transfer attributes from self to other. + + "List values" (class, style) are appended to existing values, + other values replace existing values. + """ + delimiters = {'class': ' ', 'style': '; '} + for k, v in self.items(): + if k in ('class', 'style') and v: + if other.get(k): + v = delimiters[k].join( + (other.get(k).rstrip(delimiters[k]), v)) + other.set(k, v) + + def close(self): + """Close element and return first non-full anchestor or None. + + Remove <mrow> if it has only one child element. + """ + parent = self.parent + # replace `self` with single child + if parent is not None and len(self) == 1: + child = self[0] + try: + parent[list(parent).index(self)] = child + child.parent = parent + except (AttributeError, ValueError): + return None + self.transfer_attributes(child) + return super().close() + + +class mfrac(MathSchema): + """Fractions or fraction-like objects such as binomial coefficients.""" + + +class msqrt(MathRow): + """Square root. See also `mroot`.""" + nchildren = 1 # \sqrt expects one argument or a group + + +class mroot(MathSchema): + """Roots with an explicit index. See also `msqrt`.""" + + +class mstyle(MathRow): + """Style Change. + + In modern browsers, <mstyle> is equivalent to an <mrow> element. + However, <mstyle> may still be relevant for compatibility with + MathML implementations outside browsers. + """ + + +class merror(MathRow): + """Display contents as error messages.""" + + +class menclose(MathRow): + """Renders content inside an enclosing notation... + + ... specified by the notation attribute. + + Non-standard but still required by Firefox for boxed expressions. + """ + nchildren = 1 # \boxed expects one argument or a group + + +class mpadded(MathRow): + """Adjust space around content.""" + # nchildren = 1 # currently not used by latex2mathml + + +class mphantom(MathRow): + """Placeholder: Rendered invisibly but dimensions are kept.""" + nchildren = 1 # \phantom expects one argument or a group + + +# Script and Limit Schemata +# ~~~~~~~~~~~~~~~~~~~~~~~~~ + +class msub(MathSchema): + """Attach a subscript to an expression.""" + + +class msup(MathSchema): + """Attach a superscript to an expression.""" + + +class msubsup(MathSchema): + """Attach both a subscript and a superscript to an expression.""" + nchildren = 3 + +# Examples: +# +# The `switch` attribute reverses the order of the last two children: +# >>> msub(mn(1), mn(2)).toxml() +# '<msub><mn>1</mn><mn>2</mn></msub>' +# >>> msub(mn(1), mn(2), switch=True).toxml() +# '<msub><mn>2</mn><mn>1</mn></msub>' +# +# >>> msubsup(mi('base'), mn(1), mn(2)).toxml() +# '<msubsup><mi>base</mi><mn>1</mn><mn>2</mn></msubsup>' +# >>> msubsup(mi('base'), mn(1), mn(2), switch=True).toxml() +# '<msubsup><mi>base</mi><mn>2</mn><mn>1</mn></msubsup>' + + +class munder(msub): + """Attach an accent or a limit under an expression.""" + + +class mover(msup): + """Attach an accent or a limit over an expression.""" + + +class munderover(msubsup): + """Attach accents or limits both under and over an expression.""" + + +# Tabular Math +# ~~~~~~~~~~~~ + +class mtable(MathElement): + """Table or matrix element.""" + + +class mtr(MathRow): + """Row in a table or a matrix.""" + + +class mtd(MathRow): + """Cell in a table or a matrix""" diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/tex2mathml_extern.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/tex2mathml_extern.py new file mode 100644 index 00000000..11f9ab3e --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/tex2mathml_extern.py @@ -0,0 +1,261 @@ +# :Id: $Id: tex2mathml_extern.py 9536 2024-02-01 13:04:22Z 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. +# +# __ https://opensource.org/licenses/BSD-2-Clause + +"""Wrappers for TeX->MathML conversion by external tools + +This module is provisional: +the API is not settled and may change with any minor Docutils version. +""" + +import subprocess + +from docutils import nodes +from docutils.utils.math import MathError, wrap_math_code + +# `latexml` expects a complete document: +document_template = r"""\documentclass{article} +\begin{document} +%s +\end{document} +""" + + +def _check_result(result, details=[]): + # raise MathError if the conversion went wrong + # :details: list of doctree nodes with additional info + msg = '' + if not details and result.stderr: + details = [nodes.paragraph('', result.stderr, classes=['pre-wrap'])] + if details: + msg = f'TeX to MathML converter `{result.args[0]}` failed:' + elif result.returncode: + msg = (f'TeX to MathMl converter `{result.args[0]}` ' + f'exited with Errno {result.returncode}.') + elif not result.stdout: + msg = f'TeX to MathML converter `{result.args[0]}` returned no MathML.' + if msg: + raise MathError(msg, details=details) + + +def blahtexml(math_code, as_block=False): + """Convert LaTeX math code to MathML with blahtexml__. + + __ http://gva.noekeon.org/blahtexml/ + """ + args = ['blahtexml', + '--mathml', + '--indented', + '--spacing', 'moderate', + '--mathml-encoding', 'raw', + '--other-encoding', 'raw', + '--doctype-xhtml+mathml', + '--annotate-TeX', + ] + # "blahtexml" expects LaTeX code without math-mode-switch. + # We still need to tell it about displayed equation(s). + mathml_args = ' display="block"' if as_block else '' + _wrapped = wrap_math_code(math_code, as_block) + if '{align*}' in _wrapped: + math_code = _wrapped.replace('{align*}', '{aligned}') + + result = subprocess.run(args, input=math_code, + capture_output=True, text=True) + + # blahtexml writes <error> messages to stdout + if '<error>' in result.stdout: + result.stderr = result.stdout[result.stdout.find('<message>')+9: + result.stdout.find('</message>')] + else: + result.stdout = result.stdout[result.stdout.find('<markup>')+9: + result.stdout.find('</markup>')] + _check_result(result) + return (f'<math xmlns="http://www.w3.org/1998/Math/MathML"{mathml_args}>' + f'\n{result.stdout}</math>') + + +def latexml(math_code, as_block=False): + """Convert LaTeX math code to MathML with LaTeXML__. + + Comprehensive macro support but **very** slow. + + __ http://dlmf.nist.gov/LaTeXML/ + """ + + # LaTeXML works in 2 stages, expects complete documents. + # + # The `latexmlmath`__ convenience wrapper does not support block-level + # (displayed) equations. + # + # __ https://metacpan.org/dist/LaTeXML/view/bin/latexmlmath + args1 = ['latexml', + '-', # read from stdin + '--preload=amsmath', + '--preload=amssymb', # also loads amsfonts + '--inputencoding=utf8', + '--', + ] + math_code = document_template % wrap_math_code(math_code, as_block) + + result1 = subprocess.run(args1, input=math_code, + capture_output=True, text=True) + if result1.stderr: + result1.stderr = '\n'.join(line for line in result1.stderr.splitlines() + if line.startswith('Error:') + or line.startswith('Warning:') + or line.startswith('Fatal:')) + _check_result(result1) + + args2 = ['latexmlpost', + '-', + '--nonumbersections', + '--format=html5', # maths included as MathML + '--omitdoctype', # Make it simple, we only need the maths. + '--noscan', # ... + '--nocrossref', + '--nographicimages', + '--nopictureimages', + '--nodefaultresources', # do not copy *.css files to output dir + '--' + ] + result2 = subprocess.run(args2, input=result1.stdout, + capture_output=True, text=True) + # Extract MathML from HTML document: + # <table> with <math> in cells for "align", <math> element else. + start = result2.stdout.find('<table class="ltx_equationgroup') + if start != -1: + stop = result2.stdout.find('</table>', start)+8 + result2.stdout = result2.stdout[start:stop].replace( + 'ltx_equationgroup', 'borderless align-center') + else: + result2.stdout = result2.stdout[result2.stdout.find('<math'): + result2.stdout.find('</math>')+7] + # Search for error messages + if result2.stdout: + _msg_source = result2.stdout # latexmlpost reports errors in output + else: + _msg_source = result2.stderr # just in case + result2.stderr = '\n'.join(line for line in _msg_source.splitlines() + if line.startswith('Error:') + or line.startswith('Warning:') + or line.startswith('Fatal:')) + _check_result(result2) + return result2.stdout + + +def pandoc(math_code, as_block=False): + """Convert LaTeX math code to MathML with pandoc__. + + __ https://pandoc.org/ + """ + args = ['pandoc', + '--mathml', + '--from=latex', + ] + result = subprocess.run(args, input=wrap_math_code(math_code, as_block), + capture_output=True, text=True) + + result.stdout = result.stdout[result.stdout.find('<math'): + result.stdout.find('</math>')+7] + # Pandoc (2.9.2.1) messages are pre-formatted for the terminal: + # 1. summary + # 2. math source (part) + # 3. error spot indicator '^' (works only in a literal block) + # 4. assumed problem + # 5. assumed solution (may be wrong or confusing) + # Construct a "details" list: + details = [] + if result.stderr: + lines = result.stderr.splitlines() + details.append(nodes.paragraph('', lines[0])) + details.append(nodes.literal_block('', '\n'.join(lines[1:3]))) + details.append(nodes.paragraph('', '\n'.join(lines[3:]), + classes=['pre-wrap'])) + _check_result(result, details=details) + return result.stdout + + +def ttm(math_code, as_block=False): + """Convert LaTeX math code to MathML with TtM__. + + Aged, limited, but fast. + + __ http://silas.psfc.mit.edu/tth/mml/ + """ + args = ['ttm', + '-L', # source is LaTeX snippet + '-r'] # output MathML snippet + math_code = wrap_math_code(math_code, as_block) + + # "ttm" does not support UTF-8 input. (Docutils converts most math + # characters to LaTeX commands before calling this function.) + try: + result = subprocess.run(args, input=math_code, + capture_output=True, text=True, + encoding='ISO-8859-1') + except UnicodeEncodeError as err: + raise MathError(err) + + result.stdout = result.stdout[result.stdout.find('<math'): + result.stdout.find('</math>')+7] + if as_block: + result.stdout = result.stdout.replace('<math xmlns=', + '<math display="block" xmlns=') + result.stderr = '\n'.join(line[5:] + '.' + for line in result.stderr.splitlines() + if line.startswith('**** ')) + _check_result(result) + return result.stdout + + +# self-test + +if __name__ == "__main__": + example = (r'\frac{\partial \sin^2(\alpha)}{\partial \vec r}' + r'\varpi \mathbb{R} \, \text{Grüße}') + + print("""<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> +<title>test external mathml converters</title> +</head> +<body> +<p>Test external converters</p> +<p> +""") + print(f'latexml: {latexml(example)},') + print(f'ttm: {ttm(example.replace("mathbb", "mathbf"))},') + print(f'blahtexml: {blahtexml(example)},') + print(f'pandoc: {pandoc(example)}.') + print('</p>') + + print('<p>latexml:</p>') + print(latexml(example, as_block=True)) + print('<p>ttm:</p>') + print(ttm(example.replace('mathbb', 'mathbf'), as_block=True)) + print('<p>blahtexml:</p>') + print(blahtexml(example, as_block=True)) + print('<p>pandoc:</p>') + print(pandoc(example, as_block=True)) + + print('</main>\n</body>\n</html>') + + buggy = r'\sinc \phy' + # buggy = '\sqrt[e]' + try: + # print(blahtexml(buggy)) + # print(latexml(f'${buggy}$')) + print(pandoc(f'${buggy}$')) + # print(ttm(f'${buggy}$')) + except MathError as err: + print(err) + print(err.details) + for node in err.details: + print(node.astext()) diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/tex2unichar.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/tex2unichar.py new file mode 100644 index 00000000..c84e8a6f --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/tex2unichar.py @@ -0,0 +1,730 @@ +#!/usr/bin/env python3 + +# LaTeX math to Unicode symbols translation dictionaries. +# Generated with ``write_tex2unichar.py`` from the data in +# http://milde.users.sourceforge.net/LUCR/Math/ + +# Includes commands from: +# standard LaTeX +# amssymb +# amsmath +# amsxtra +# bbold +# esint +# mathabx +# mathdots +# txfonts +# stmaryrd +# wasysym + +mathaccent = { + 'acute': '\u0301', # ́ COMBINING ACUTE ACCENT + 'bar': '\u0304', # ̄ COMBINING MACRON + 'breve': '\u0306', # ̆ COMBINING BREVE + 'check': '\u030c', # ̌ COMBINING CARON + 'ddddot': '\u20dc', # ⃜ COMBINING FOUR DOTS ABOVE + 'dddot': '\u20db', # ⃛ COMBINING THREE DOTS ABOVE + 'ddot': '\u0308', # ̈ COMBINING DIAERESIS + 'dot': '\u0307', # ̇ COMBINING DOT ABOVE + 'grave': '\u0300', # ̀ COMBINING GRAVE ACCENT + 'hat': '\u0302', # ̂ COMBINING CIRCUMFLEX ACCENT + 'mathring': '\u030a', # ̊ COMBINING RING ABOVE + 'not': '\u0338', # ̸ COMBINING LONG SOLIDUS OVERLAY + 'overleftrightarrow': '\u20e1', # ⃡ COMBINING LEFT RIGHT ARROW ABOVE + 'overline': '\u0305', # ̅ COMBINING OVERLINE + 'tilde': '\u0303', # ̃ COMBINING TILDE + 'underbar': '\u0331', # ̱ COMBINING MACRON BELOW + 'underleftarrow': '\u20ee', # ⃮ COMBINING LEFT ARROW BELOW + 'underline': '\u0332', # ̲ COMBINING LOW LINE + 'underrightarrow': '\u20ef', # ⃯ COMBINING RIGHT ARROW BELOW + 'vec': '\u20d7', # ⃗ COMBINING RIGHT ARROW ABOVE + } + +mathalpha = { + 'Bbbk': '\U0001d55c', # 𝕜 MATHEMATICAL DOUBLE-STRUCK SMALL K + 'Delta': '\u0394', # Δ GREEK CAPITAL LETTER DELTA + 'Gamma': '\u0393', # Γ GREEK CAPITAL LETTER GAMMA + 'Im': '\u2111', # ℑ BLACK-LETTER CAPITAL I + 'Lambda': '\u039b', # Λ GREEK CAPITAL LETTER LAMDA + 'Omega': '\u03a9', # Ω GREEK CAPITAL LETTER OMEGA + 'Phi': '\u03a6', # Φ GREEK CAPITAL LETTER PHI + 'Pi': '\u03a0', # Π GREEK CAPITAL LETTER PI + 'Psi': '\u03a8', # Ψ GREEK CAPITAL LETTER PSI + 'Re': '\u211c', # ℜ BLACK-LETTER CAPITAL R + 'Sigma': '\u03a3', # Σ GREEK CAPITAL LETTER SIGMA + 'Theta': '\u0398', # Θ GREEK CAPITAL LETTER THETA + 'Upsilon': '\u03a5', # Υ GREEK CAPITAL LETTER UPSILON + 'Xi': '\u039e', # Ξ GREEK CAPITAL LETTER XI + 'aleph': '\u2135', # ℵ ALEF SYMBOL + 'alpha': '\u03b1', # α GREEK SMALL LETTER ALPHA + 'beta': '\u03b2', # β GREEK SMALL LETTER BETA + 'beth': '\u2136', # ℶ BET SYMBOL + 'chi': '\u03c7', # χ GREEK SMALL LETTER CHI + 'daleth': '\u2138', # ℸ DALET SYMBOL + 'delta': '\u03b4', # δ GREEK SMALL LETTER DELTA + 'digamma': '\u03dd', # ϝ GREEK SMALL LETTER DIGAMMA + 'ell': '\u2113', # ℓ SCRIPT SMALL L + 'epsilon': '\u03f5', # ϵ GREEK LUNATE EPSILON SYMBOL + 'eta': '\u03b7', # η GREEK SMALL LETTER ETA + 'eth': '\xf0', # ð LATIN SMALL LETTER ETH + 'gamma': '\u03b3', # γ GREEK SMALL LETTER GAMMA + 'gimel': '\u2137', # ℷ GIMEL SYMBOL + 'imath': '\u0131', # ı LATIN SMALL LETTER DOTLESS I + 'iota': '\u03b9', # ι GREEK SMALL LETTER IOTA + 'jmath': '\u0237', # ȷ LATIN SMALL LETTER DOTLESS J + 'kappa': '\u03ba', # κ GREEK SMALL LETTER KAPPA + 'lambda': '\u03bb', # λ GREEK SMALL LETTER LAMDA + 'mu': '\u03bc', # μ GREEK SMALL LETTER MU + 'nu': '\u03bd', # ν GREEK SMALL LETTER NU + 'omega': '\u03c9', # ω GREEK SMALL LETTER OMEGA + 'phi': '\u03d5', # ϕ GREEK PHI SYMBOL + 'pi': '\u03c0', # π GREEK SMALL LETTER PI + 'psi': '\u03c8', # ψ GREEK SMALL LETTER PSI + 'rho': '\u03c1', # ρ GREEK SMALL LETTER RHO + 'sigma': '\u03c3', # σ GREEK SMALL LETTER SIGMA + 'tau': '\u03c4', # τ GREEK SMALL LETTER TAU + 'theta': '\u03b8', # θ GREEK SMALL LETTER THETA + 'upsilon': '\u03c5', # υ GREEK SMALL LETTER UPSILON + 'varDelta': '\U0001d6e5', # 𝛥 MATHEMATICAL ITALIC CAPITAL DELTA + 'varGamma': '\U0001d6e4', # 𝛤 MATHEMATICAL ITALIC CAPITAL GAMMA + 'varLambda': '\U0001d6ec', # 𝛬 MATHEMATICAL ITALIC CAPITAL LAMDA + 'varOmega': '\U0001d6fa', # 𝛺 MATHEMATICAL ITALIC CAPITAL OMEGA + 'varPhi': '\U0001d6f7', # 𝛷 MATHEMATICAL ITALIC CAPITAL PHI + 'varPi': '\U0001d6f1', # 𝛱 MATHEMATICAL ITALIC CAPITAL PI + 'varPsi': '\U0001d6f9', # 𝛹 MATHEMATICAL ITALIC CAPITAL PSI + 'varSigma': '\U0001d6f4', # 𝛴 MATHEMATICAL ITALIC CAPITAL SIGMA + 'varTheta': '\U0001d6e9', # 𝛩 MATHEMATICAL ITALIC CAPITAL THETA + 'varUpsilon': '\U0001d6f6', # 𝛶 MATHEMATICAL ITALIC CAPITAL UPSILON + 'varXi': '\U0001d6ef', # 𝛯 MATHEMATICAL ITALIC CAPITAL XI + 'varepsilon': '\u03b5', # ε GREEK SMALL LETTER EPSILON + 'varkappa': '\u03f0', # ϰ GREEK KAPPA SYMBOL + 'varphi': '\u03c6', # φ GREEK SMALL LETTER PHI + 'varpi': '\u03d6', # ϖ GREEK PI SYMBOL + 'varrho': '\u03f1', # ϱ GREEK RHO SYMBOL + 'varsigma': '\u03c2', # ς GREEK SMALL LETTER FINAL SIGMA + 'vartheta': '\u03d1', # ϑ GREEK THETA SYMBOL + 'wp': '\u2118', # ℘ SCRIPT CAPITAL P + 'xi': '\u03be', # ξ GREEK SMALL LETTER XI + 'zeta': '\u03b6', # ζ GREEK SMALL LETTER ZETA + } + +mathbin = { + 'Cap': '\u22d2', # ⋒ DOUBLE INTERSECTION + 'Circle': '\u25cb', # ○ WHITE CIRCLE + 'Cup': '\u22d3', # ⋓ DOUBLE UNION + 'LHD': '\u25c0', # ◀ BLACK LEFT-POINTING TRIANGLE + 'RHD': '\u25b6', # ▶ BLACK RIGHT-POINTING TRIANGLE + 'amalg': '\u2a3f', # ⨿ AMALGAMATION OR COPRODUCT + 'ast': '\u2217', # ∗ ASTERISK OPERATOR + 'barwedge': '\u22bc', # ⊼ NAND + 'bigcirc': '\u25ef', # ◯ LARGE CIRCLE + 'bigtriangledown': '\u25bd', # ▽ WHITE DOWN-POINTING TRIANGLE + 'bigtriangleup': '\u25b3', # △ WHITE UP-POINTING TRIANGLE + 'bindnasrepma': '\u214b', # ⅋ TURNED AMPERSAND + 'blacklozenge': '\u29eb', # ⧫ BLACK LOZENGE + 'boxast': '\u29c6', # ⧆ SQUARED ASTERISK + 'boxbar': '\u25eb', # ◫ WHITE SQUARE WITH VERTICAL BISECTING LINE + 'boxbox': '\u29c8', # ⧈ SQUARED SQUARE + 'boxbslash': '\u29c5', # ⧅ SQUARED FALLING DIAGONAL SLASH + 'boxcircle': '\u29c7', # ⧇ SQUARED SMALL CIRCLE + 'boxdot': '\u22a1', # ⊡ SQUARED DOT OPERATOR + 'boxminus': '\u229f', # ⊟ SQUARED MINUS + 'boxplus': '\u229e', # ⊞ SQUARED PLUS + 'boxslash': '\u29c4', # ⧄ SQUARED RISING DIAGONAL SLASH + 'boxtimes': '\u22a0', # ⊠ SQUARED TIMES + 'bullet': '\u2022', # • BULLET + 'cap': '\u2229', # ∩ INTERSECTION + 'cdot': '\u22c5', # ⋅ DOT OPERATOR + 'circ': '\u2218', # ∘ RING OPERATOR + 'circledast': '\u229b', # ⊛ CIRCLED ASTERISK OPERATOR + 'circledbslash': '\u29b8', # ⦸ CIRCLED REVERSE SOLIDUS + 'circledcirc': '\u229a', # ⊚ CIRCLED RING OPERATOR + 'circleddash': '\u229d', # ⊝ CIRCLED DASH + 'circledgtr': '\u29c1', # ⧁ CIRCLED GREATER-THAN + 'circledless': '\u29c0', # ⧀ CIRCLED LESS-THAN + 'cup': '\u222a', # ∪ UNION + 'curlyvee': '\u22ce', # ⋎ CURLY LOGICAL OR + 'curlywedge': '\u22cf', # ⋏ CURLY LOGICAL AND + 'dagger': '\u2020', # † DAGGER + 'ddagger': '\u2021', # ‡ DOUBLE DAGGER + 'diamond': '\u22c4', # ⋄ DIAMOND OPERATOR + 'div': '\xf7', # ÷ DIVISION SIGN + 'divideontimes': '\u22c7', # ⋇ DIVISION TIMES + 'dotplus': '\u2214', # ∔ DOT PLUS + 'doublebarwedge': '\u2a5e', # ⩞ LOGICAL AND WITH DOUBLE OVERBAR + 'gtrdot': '\u22d7', # ⋗ GREATER-THAN WITH DOT + 'intercal': '\u22ba', # ⊺ INTERCALATE + 'interleave': '\u2af4', # ⫴ TRIPLE VERTICAL BAR BINARY RELATION + 'invamp': '\u214b', # ⅋ TURNED AMPERSAND + 'land': '\u2227', # ∧ LOGICAL AND + 'leftthreetimes': '\u22cb', # ⋋ LEFT SEMIDIRECT PRODUCT + 'lessdot': '\u22d6', # ⋖ LESS-THAN WITH DOT + 'lor': '\u2228', # ∨ LOGICAL OR + 'ltimes': '\u22c9', # ⋉ LEFT NORMAL FACTOR SEMIDIRECT PRODUCT + 'mp': '\u2213', # ∓ MINUS-OR-PLUS SIGN + 'odot': '\u2299', # ⊙ CIRCLED DOT OPERATOR + 'ominus': '\u2296', # ⊖ CIRCLED MINUS + 'oplus': '\u2295', # ⊕ CIRCLED PLUS + 'oslash': '\u2298', # ⊘ CIRCLED DIVISION SLASH + 'otimes': '\u2297', # ⊗ CIRCLED TIMES + 'pm': '\xb1', # ± PLUS-MINUS SIGN + 'rightthreetimes': '\u22cc', # ⋌ RIGHT SEMIDIRECT PRODUCT + 'rtimes': '\u22ca', # ⋊ RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT + 'setminus': '\u29f5', # ⧵ REVERSE SOLIDUS OPERATOR + 'slash': '\u2215', # ∕ DIVISION SLASH + 'smallsetminus': '\u2216', # ∖ SET MINUS + 'smalltriangledown': '\u25bf', # ▿ WHITE DOWN-POINTING SMALL TRIANGLE + 'smalltriangleleft': '\u25c3', # ◃ WHITE LEFT-POINTING SMALL TRIANGLE + 'smalltriangleright': '\u25b9', # ▹ WHITE RIGHT-POINTING SMALL TRIANGLE + 'sqcap': '\u2293', # ⊓ SQUARE CAP + 'sqcup': '\u2294', # ⊔ SQUARE CUP + 'sslash': '\u2afd', # ⫽ DOUBLE SOLIDUS OPERATOR + 'star': '\u22c6', # ⋆ STAR OPERATOR + 'talloblong': '\u2afe', # ⫾ WHITE VERTICAL BAR + 'times': '\xd7', # × MULTIPLICATION SIGN + 'triangleleft': '\u25c3', # ◃ WHITE LEFT-POINTING SMALL TRIANGLE + 'triangleright': '\u25b9', # ▹ WHITE RIGHT-POINTING SMALL TRIANGLE + 'uplus': '\u228e', # ⊎ MULTISET UNION + 'vee': '\u2228', # ∨ LOGICAL OR + 'veebar': '\u22bb', # ⊻ XOR + 'wedge': '\u2227', # ∧ LOGICAL AND + 'wr': '\u2240', # ≀ WREATH PRODUCT + } + +mathclose = { + 'Rbag': '\u27c6', # ⟆ RIGHT S-SHAPED BAG DELIMITER + 'lrcorner': '\u231f', # ⌟ BOTTOM RIGHT CORNER + 'rangle': '\u27e9', # ⟩ MATHEMATICAL RIGHT ANGLE BRACKET + 'rbag': '\u27c6', # ⟆ RIGHT S-SHAPED BAG DELIMITER + 'rbrace': '}', # } RIGHT CURLY BRACKET + 'rbrack': ']', # ] RIGHT SQUARE BRACKET + 'rceil': '\u2309', # ⌉ RIGHT CEILING + 'rfloor': '\u230b', # ⌋ RIGHT FLOOR + 'rgroup': '\u27ef', # ⟯ MATHEMATICAL RIGHT FLATTENED PARENTHESIS + 'rrbracket': '\u27e7', # ⟧ MATHEMATICAL RIGHT WHITE SQUARE BRACKET + 'rrparenthesis': '\u2988', # ⦈ Z NOTATION RIGHT IMAGE BRACKET + 'urcorner': '\u231d', # ⌝ TOP RIGHT CORNER + '}': '}', # } RIGHT CURLY BRACKET + } + +mathfence = { + 'Vert': '\u2016', # ‖ DOUBLE VERTICAL LINE + 'vert': '|', # | VERTICAL LINE + '|': '\u2016', # ‖ DOUBLE VERTICAL LINE + } + +mathop = { + 'bigcap': '\u22c2', # ⋂ N-ARY INTERSECTION + 'bigcup': '\u22c3', # ⋃ N-ARY UNION + 'biginterleave': '\u2afc', # ⫼ LARGE TRIPLE VERTICAL BAR OPERATOR + 'bigodot': '\u2a00', # ⨀ N-ARY CIRCLED DOT OPERATOR + 'bigoplus': '\u2a01', # ⨁ N-ARY CIRCLED PLUS OPERATOR + 'bigotimes': '\u2a02', # ⨂ N-ARY CIRCLED TIMES OPERATOR + 'bigsqcap': '\u2a05', # ⨅ N-ARY SQUARE INTERSECTION OPERATOR + 'bigsqcup': '\u2a06', # ⨆ N-ARY SQUARE UNION OPERATOR + 'biguplus': '\u2a04', # ⨄ N-ARY UNION OPERATOR WITH PLUS + 'bigvee': '\u22c1', # ⋁ N-ARY LOGICAL OR + 'bigwedge': '\u22c0', # ⋀ N-ARY LOGICAL AND + 'coprod': '\u2210', # ∐ N-ARY COPRODUCT + 'fatsemi': '\u2a1f', # ⨟ Z NOTATION SCHEMA COMPOSITION + 'fint': '\u2a0f', # ⨏ INTEGRAL AVERAGE WITH SLASH + 'iiiint': '\u2a0c', # ⨌ QUADRUPLE INTEGRAL OPERATOR + 'iiint': '\u222d', # ∭ TRIPLE INTEGRAL + 'iint': '\u222c', # ∬ DOUBLE INTEGRAL + 'int': '\u222b', # ∫ INTEGRAL + 'intop': '\u222b', # ∫ INTEGRAL + 'oiiint': '\u2230', # ∰ VOLUME INTEGRAL + 'oiint': '\u222f', # ∯ SURFACE INTEGRAL + 'oint': '\u222e', # ∮ CONTOUR INTEGRAL + 'ointctrclockwise': '\u2233', # ∳ ANTICLOCKWISE CONTOUR INTEGRAL + 'ointop': '\u222e', # ∮ CONTOUR INTEGRAL + 'prod': '\u220f', # ∏ N-ARY PRODUCT + 'sqint': '\u2a16', # ⨖ QUATERNION INTEGRAL OPERATOR + 'sum': '\u2211', # ∑ N-ARY SUMMATION + 'varointclockwise': '\u2232', # ∲ CLOCKWISE CONTOUR INTEGRAL + 'varprod': '\u2a09', # ⨉ N-ARY TIMES OPERATOR + } + +mathopen = { + 'Lbag': '\u27c5', # ⟅ LEFT S-SHAPED BAG DELIMITER + 'langle': '\u27e8', # ⟨ MATHEMATICAL LEFT ANGLE BRACKET + 'lbag': '\u27c5', # ⟅ LEFT S-SHAPED BAG DELIMITER + 'lbrace': '{', # { LEFT CURLY BRACKET + 'lbrack': '[', # [ LEFT SQUARE BRACKET + 'lceil': '\u2308', # ⌈ LEFT CEILING + 'lfloor': '\u230a', # ⌊ LEFT FLOOR + 'lgroup': '\u27ee', # ⟮ MATHEMATICAL LEFT FLATTENED PARENTHESIS + 'llbracket': '\u27e6', # ⟦ MATHEMATICAL LEFT WHITE SQUARE BRACKET + 'llcorner': '\u231e', # ⌞ BOTTOM LEFT CORNER + 'llparenthesis': '\u2987', # ⦇ Z NOTATION LEFT IMAGE BRACKET + 'ulcorner': '\u231c', # ⌜ TOP LEFT CORNER + '{': '{', # { LEFT CURLY BRACKET + } + +mathord = { + '#': '#', # # NUMBER SIGN + '$': '$', # $ DOLLAR SIGN + '%': '%', # % PERCENT SIGN + '&': '&', # & AMPERSAND + 'AC': '\u223f', # ∿ SINE WAVE + 'APLcomment': '\u235d', # ⍝ APL FUNCTIONAL SYMBOL UP SHOE JOT + 'APLdownarrowbox': '\u2357', # ⍗ APL FUNCTIONAL SYMBOL QUAD DOWNWARDS ARROW + 'APLinput': '\u235e', # ⍞ APL FUNCTIONAL SYMBOL QUOTE QUAD + 'APLinv': '\u2339', # ⌹ APL FUNCTIONAL SYMBOL QUAD DIVIDE + 'APLleftarrowbox': '\u2347', # ⍇ APL FUNCTIONAL SYMBOL QUAD LEFTWARDS ARROW + 'APLlog': '\u235f', # ⍟ APL FUNCTIONAL SYMBOL CIRCLE STAR + 'APLrightarrowbox': '\u2348', # ⍈ APL FUNCTIONAL SYMBOL QUAD RIGHTWARDS ARROW + 'APLuparrowbox': '\u2350', # ⍐ APL FUNCTIONAL SYMBOL QUAD UPWARDS ARROW + 'Aries': '\u2648', # ♈ ARIES + 'Box': '\u2b1c', # ⬜ WHITE LARGE SQUARE + 'CIRCLE': '\u25cf', # ● BLACK CIRCLE + 'CheckedBox': '\u2611', # ☑ BALLOT BOX WITH CHECK + 'Diamond': '\u25c7', # ◇ WHITE DIAMOND + 'Diamondblack': '\u25c6', # ◆ BLACK DIAMOND + 'Diamonddot': '\u27d0', # ⟐ WHITE DIAMOND WITH CENTRED DOT + 'Finv': '\u2132', # Ⅎ TURNED CAPITAL F + 'Game': '\u2141', # ⅁ TURNED SANS-SERIF CAPITAL G + 'Gemini': '\u264a', # ♊ GEMINI + 'Jupiter': '\u2643', # ♃ JUPITER + 'LEFTCIRCLE': '\u25d6', # ◖ LEFT HALF BLACK CIRCLE + 'LEFTcircle': '\u25d0', # ◐ CIRCLE WITH LEFT HALF BLACK + 'Leo': '\u264c', # ♌ LEO + 'Libra': '\u264e', # ♎ LIBRA + 'Mars': '\u2642', # ♂ MALE SIGN + 'Mercury': '\u263f', # ☿ MERCURY + 'Neptune': '\u2646', # ♆ NEPTUNE + 'P': '\xb6', # ¶ PILCROW SIGN + 'Pluto': '\u2647', # ♇ PLUTO + 'RIGHTCIRCLE': '\u25d7', # ◗ RIGHT HALF BLACK CIRCLE + 'RIGHTcircle': '\u25d1', # ◑ CIRCLE WITH RIGHT HALF BLACK + 'S': '\xa7', # § SECTION SIGN + 'Saturn': '\u2644', # ♄ SATURN + 'Scorpio': '\u264f', # ♏ SCORPIUS + 'Square': '\u2610', # ☐ BALLOT BOX + 'Sun': '\u2609', # ☉ SUN + 'Taurus': '\u2649', # ♉ TAURUS + 'Uranus': '\u2645', # ♅ URANUS + 'Venus': '\u2640', # ♀ FEMALE SIGN + 'XBox': '\u2612', # ☒ BALLOT BOX WITH X + 'Yup': '\u2144', # ⅄ TURNED SANS-SERIF CAPITAL Y + '_': '_', # _ LOW LINE + 'angle': '\u2220', # ∠ ANGLE + 'aquarius': '\u2652', # ♒ AQUARIUS + 'aries': '\u2648', # ♈ ARIES + 'arrowvert': '\u23d0', # ⏐ VERTICAL LINE EXTENSION + 'backprime': '\u2035', # ‵ REVERSED PRIME + 'backslash': '\\', # \ REVERSE SOLIDUS + 'bigstar': '\u2605', # ★ BLACK STAR + 'blacksmiley': '\u263b', # ☻ BLACK SMILING FACE + 'blacksquare': '\u25fc', # ◼ BLACK MEDIUM SQUARE + 'blacktriangle': '\u25b4', # ▴ BLACK UP-POINTING SMALL TRIANGLE + 'blacktriangledown': '\u25be', # ▾ BLACK DOWN-POINTING SMALL TRIANGLE + 'blacktriangleup': '\u25b4', # ▴ BLACK UP-POINTING SMALL TRIANGLE + 'bot': '\u22a5', # ⊥ UP TACK + 'boy': '\u2642', # ♂ MALE SIGN + 'bracevert': '\u23aa', # ⎪ CURLY BRACKET EXTENSION + 'cancer': '\u264b', # ♋ CANCER + 'capricornus': '\u2651', # ♑ CAPRICORN + 'cdots': '\u22ef', # ⋯ MIDLINE HORIZONTAL ELLIPSIS + 'cent': '\xa2', # ¢ CENT SIGN + 'checkmark': '\u2713', # ✓ CHECK MARK + 'circledR': '\u24c7', # Ⓡ CIRCLED LATIN CAPITAL LETTER R + 'circledS': '\u24c8', # Ⓢ CIRCLED LATIN CAPITAL LETTER S + 'clubsuit': '\u2663', # ♣ BLACK CLUB SUIT + 'complement': '\u2201', # ∁ COMPLEMENT + 'diagdown': '\u27cd', # ⟍ MATHEMATICAL FALLING DIAGONAL + 'diagup': '\u27cb', # ⟋ MATHEMATICAL RISING DIAGONAL + 'diameter': '\u2300', # ⌀ DIAMETER SIGN + 'diamondsuit': '\u2662', # ♢ WHITE DIAMOND SUIT + 'earth': '\u2641', # ♁ EARTH + 'emptyset': '\u2205', # ∅ EMPTY SET + 'exists': '\u2203', # ∃ THERE EXISTS + 'female': '\u2640', # ♀ FEMALE SIGN + 'flat': '\u266d', # ♭ MUSIC FLAT SIGN + 'forall': '\u2200', # ∀ FOR ALL + 'fourth': '\u2057', # ⁗ QUADRUPLE PRIME + 'frownie': '\u2639', # ☹ WHITE FROWNING FACE + 'gemini': '\u264a', # ♊ GEMINI + 'girl': '\u2640', # ♀ FEMALE SIGN + 'heartsuit': '\u2661', # ♡ WHITE HEART SUIT + 'hslash': '\u210f', # ℏ PLANCK CONSTANT OVER TWO PI + 'infty': '\u221e', # ∞ INFINITY + 'invdiameter': '\u2349', # ⍉ APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH + 'invneg': '\u2310', # ⌐ REVERSED NOT SIGN + 'jupiter': '\u2643', # ♃ JUPITER + 'ldots': '\u2026', # … HORIZONTAL ELLIPSIS + 'leftmoon': '\u263e', # ☾ LAST QUARTER MOON + 'leo': '\u264c', # ♌ LEO + 'libra': '\u264e', # ♎ LIBRA + 'lmoustache': '\u23b0', # ⎰ UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION + 'lnot': '\xac', # ¬ NOT SIGN + 'lozenge': '\u25ca', # ◊ LOZENGE + 'male': '\u2642', # ♂ MALE SIGN + 'maltese': '\u2720', # ✠ MALTESE CROSS + 'mathcent': '\xa2', # ¢ CENT SIGN + 'mathdollar': '$', # $ DOLLAR SIGN + 'mathsterling': '\xa3', # £ POUND SIGN + 'measuredangle': '\u2221', # ∡ MEASURED ANGLE + 'medbullet': '\u26ab', # ⚫ MEDIUM BLACK CIRCLE + 'medcirc': '\u26aa', # ⚪ MEDIUM WHITE CIRCLE + 'mercury': '\u263f', # ☿ MERCURY + 'mho': '\u2127', # ℧ INVERTED OHM SIGN + 'nabla': '\u2207', # ∇ NABLA + 'natural': '\u266e', # ♮ MUSIC NATURAL SIGN + 'neg': '\xac', # ¬ NOT SIGN + 'neptune': '\u2646', # ♆ NEPTUNE + 'nexists': '\u2204', # ∄ THERE DOES NOT EXIST + 'notbackslash': '\u2340', # ⍀ APL FUNCTIONAL SYMBOL BACKSLASH BAR + 'partial': '\u2202', # ∂ PARTIAL DIFFERENTIAL + 'pisces': '\u2653', # ♓ PISCES + 'pluto': '\u2647', # ♇ PLUTO + 'pounds': '\xa3', # £ POUND SIGN + 'prime': '\u2032', # ′ PRIME + 'quarternote': '\u2669', # ♩ QUARTER NOTE + 'rightmoon': '\u263d', # ☽ FIRST QUARTER MOON + 'rmoustache': '\u23b1', # ⎱ UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION + 'sagittarius': '\u2650', # ♐ SAGITTARIUS + 'saturn': '\u2644', # ♄ SATURN + 'scorpio': '\u264f', # ♏ SCORPIUS + 'second': '\u2033', # ″ DOUBLE PRIME + 'sharp': '\u266f', # ♯ MUSIC SHARP SIGN + 'smiley': '\u263a', # ☺ WHITE SMILING FACE + 'spadesuit': '\u2660', # ♠ BLACK SPADE SUIT + 'spddot': '\xa8', # ¨ DIAERESIS + 'sphat': '^', # ^ CIRCUMFLEX ACCENT + 'sphericalangle': '\u2222', # ∢ SPHERICAL ANGLE + 'sptilde': '~', # ~ TILDE + 'square': '\u25fb', # ◻ WHITE MEDIUM SQUARE + 'sun': '\u263c', # ☼ WHITE SUN WITH RAYS + 'surd': '\u221a', # √ SQUARE ROOT + 'taurus': '\u2649', # ♉ TAURUS + 'third': '\u2034', # ‴ TRIPLE PRIME + 'top': '\u22a4', # ⊤ DOWN TACK + 'twonotes': '\u266b', # ♫ BEAMED EIGHTH NOTES + 'uranus': '\u2645', # ♅ URANUS + 'varEarth': '\u2641', # ♁ EARTH + 'varclubsuit': '\u2667', # ♧ WHITE CLUB SUIT + 'vardiamondsuit': '\u2666', # ♦ BLACK DIAMOND SUIT + 'varheartsuit': '\u2665', # ♥ BLACK HEART SUIT + 'varspadesuit': '\u2664', # ♤ WHITE SPADE SUIT + 'virgo': '\u264d', # ♍ VIRGO + 'wasylozenge': '\u2311', # ⌑ SQUARE LOZENGE + 'yen': '\xa5', # ¥ YEN SIGN + } + +mathover = { + 'overbrace': '\u23de', # ⏞ TOP CURLY BRACKET + 'wideparen': '\u23dc', # ⏜ TOP PARENTHESIS + } + +mathpunct = { + 'ddots': '\u22f1', # ⋱ DOWN RIGHT DIAGONAL ELLIPSIS + 'vdots': '\u22ee', # ⋮ VERTICAL ELLIPSIS + } + +mathradical = { + 'sqrt[3]': '\u221b', # ∛ CUBE ROOT + 'sqrt[4]': '\u221c', # ∜ FOURTH ROOT + } + +mathrel = { + 'Bot': '\u2aeb', # ⫫ DOUBLE UP TACK + 'Bumpeq': '\u224e', # ≎ GEOMETRICALLY EQUIVALENT TO + 'Coloneqq': '\u2a74', # ⩴ DOUBLE COLON EQUAL + 'Doteq': '\u2251', # ≑ GEOMETRICALLY EQUAL TO + 'Downarrow': '\u21d3', # ⇓ DOWNWARDS DOUBLE ARROW + 'Leftarrow': '\u21d0', # ⇐ LEFTWARDS DOUBLE ARROW + 'Leftrightarrow': '\u21d4', # ⇔ LEFT RIGHT DOUBLE ARROW + 'Lleftarrow': '\u21da', # ⇚ LEFTWARDS TRIPLE ARROW + 'Longleftarrow': '\u27f8', # ⟸ LONG LEFTWARDS DOUBLE ARROW + 'Longleftrightarrow': '\u27fa', # ⟺ LONG LEFT RIGHT DOUBLE ARROW + 'Longmapsfrom': '\u27fd', # ⟽ LONG LEFTWARDS DOUBLE ARROW FROM BAR + 'Longmapsto': '\u27fe', # ⟾ LONG RIGHTWARDS DOUBLE ARROW FROM BAR + 'Longrightarrow': '\u27f9', # ⟹ LONG RIGHTWARDS DOUBLE ARROW + 'Lsh': '\u21b0', # ↰ UPWARDS ARROW WITH TIP LEFTWARDS + 'Mapsfrom': '\u2906', # ⤆ LEFTWARDS DOUBLE ARROW FROM BAR + 'Mapsto': '\u2907', # ⤇ RIGHTWARDS DOUBLE ARROW FROM BAR + 'Nearrow': '\u21d7', # ⇗ NORTH EAST DOUBLE ARROW + 'Nwarrow': '\u21d6', # ⇖ NORTH WEST DOUBLE ARROW + 'Perp': '\u2aeb', # ⫫ DOUBLE UP TACK + 'Rightarrow': '\u21d2', # ⇒ RIGHTWARDS DOUBLE ARROW + 'Rrightarrow': '\u21db', # ⇛ RIGHTWARDS TRIPLE ARROW + 'Rsh': '\u21b1', # ↱ UPWARDS ARROW WITH TIP RIGHTWARDS + 'Searrow': '\u21d8', # ⇘ SOUTH EAST DOUBLE ARROW + 'Subset': '\u22d0', # ⋐ DOUBLE SUBSET + 'Supset': '\u22d1', # ⋑ DOUBLE SUPERSET + 'Swarrow': '\u21d9', # ⇙ SOUTH WEST DOUBLE ARROW + 'Top': '\u2aea', # ⫪ DOUBLE DOWN TACK + 'Uparrow': '\u21d1', # ⇑ UPWARDS DOUBLE ARROW + 'Updownarrow': '\u21d5', # ⇕ UP DOWN DOUBLE ARROW + 'VDash': '\u22ab', # ⊫ DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE + 'Vdash': '\u22a9', # ⊩ FORCES + 'Vvdash': '\u22aa', # ⊪ TRIPLE VERTICAL BAR RIGHT TURNSTILE + 'apprge': '\u2273', # ≳ GREATER-THAN OR EQUIVALENT TO + 'apprle': '\u2272', # ≲ LESS-THAN OR EQUIVALENT TO + 'approx': '\u2248', # ≈ ALMOST EQUAL TO + 'approxeq': '\u224a', # ≊ ALMOST EQUAL OR EQUAL TO + 'asymp': '\u224d', # ≍ EQUIVALENT TO + 'backepsilon': '\u220d', # ∍ SMALL CONTAINS AS MEMBER + 'backsim': '\u223d', # ∽ REVERSED TILDE + 'backsimeq': '\u22cd', # ⋍ REVERSED TILDE EQUALS + 'barin': '\u22f6', # ⋶ ELEMENT OF WITH OVERBAR + 'barleftharpoon': '\u296b', # ⥫ LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + 'barrightharpoon': '\u296d', # ⥭ RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + 'because': '\u2235', # ∵ BECAUSE + 'between': '\u226c', # ≬ BETWEEN + 'blacktriangleleft': '\u25c2', # ◂ BLACK LEFT-POINTING SMALL TRIANGLE + 'blacktriangleright': '\u25b8', # ▸ BLACK RIGHT-POINTING SMALL TRIANGLE + 'bowtie': '\u22c8', # ⋈ BOWTIE + 'bumpeq': '\u224f', # ≏ DIFFERENCE BETWEEN + 'circeq': '\u2257', # ≗ RING EQUAL TO + 'circlearrowleft': '\u21ba', # ↺ ANTICLOCKWISE OPEN CIRCLE ARROW + 'circlearrowright': '\u21bb', # ↻ CLOCKWISE OPEN CIRCLE ARROW + 'coloneq': '\u2254', # ≔ COLON EQUALS + 'coloneqq': '\u2254', # ≔ COLON EQUALS + 'cong': '\u2245', # ≅ APPROXIMATELY EQUAL TO + 'corresponds': '\u2259', # ≙ ESTIMATES + 'curlyeqprec': '\u22de', # ⋞ EQUAL TO OR PRECEDES + 'curlyeqsucc': '\u22df', # ⋟ EQUAL TO OR SUCCEEDS + 'curvearrowleft': '\u21b6', # ↶ ANTICLOCKWISE TOP SEMICIRCLE ARROW + 'curvearrowright': '\u21b7', # ↷ CLOCKWISE TOP SEMICIRCLE ARROW + 'dasharrow': '\u21e2', # ⇢ RIGHTWARDS DASHED ARROW + 'dashleftarrow': '\u21e0', # ⇠ LEFTWARDS DASHED ARROW + 'dashrightarrow': '\u21e2', # ⇢ RIGHTWARDS DASHED ARROW + 'dashv': '\u22a3', # ⊣ LEFT TACK + 'dlsh': '\u21b2', # ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS + 'doteq': '\u2250', # ≐ APPROACHES THE LIMIT + 'doteqdot': '\u2251', # ≑ GEOMETRICALLY EQUAL TO + 'downarrow': '\u2193', # ↓ DOWNWARDS ARROW + 'downdownarrows': '\u21ca', # ⇊ DOWNWARDS PAIRED ARROWS + 'downdownharpoons': '\u2965', # ⥥ DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + 'downharpoonleft': '\u21c3', # ⇃ DOWNWARDS HARPOON WITH BARB LEFTWARDS + 'downharpoonright': '\u21c2', # ⇂ DOWNWARDS HARPOON WITH BARB RIGHTWARDS + 'downuparrows': '\u21f5', # ⇵ DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW + 'downupharpoons': '\u296f', # ⥯ DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + 'drsh': '\u21b3', # ↳ DOWNWARDS ARROW WITH TIP RIGHTWARDS + 'eqcirc': '\u2256', # ≖ RING IN EQUAL TO + 'eqcolon': '\u2255', # ≕ EQUALS COLON + 'eqqcolon': '\u2255', # ≕ EQUALS COLON + 'eqsim': '\u2242', # ≂ MINUS TILDE + 'eqslantgtr': '\u2a96', # ⪖ SLANTED EQUAL TO OR GREATER-THAN + 'eqslantless': '\u2a95', # ⪕ SLANTED EQUAL TO OR LESS-THAN + 'equiv': '\u2261', # ≡ IDENTICAL TO + 'fallingdotseq': '\u2252', # ≒ APPROXIMATELY EQUAL TO OR THE IMAGE OF + 'frown': '\u2322', # ⌢ FROWN + 'ge': '\u2265', # ≥ GREATER-THAN OR EQUAL TO + 'geq': '\u2265', # ≥ GREATER-THAN OR EQUAL TO + 'geqq': '\u2267', # ≧ GREATER-THAN OVER EQUAL TO + 'geqslant': '\u2a7e', # ⩾ GREATER-THAN OR SLANTED EQUAL TO + 'gets': '\u2190', # ← LEFTWARDS ARROW + 'gg': '\u226b', # ≫ MUCH GREATER-THAN + 'ggcurly': '\u2abc', # ⪼ DOUBLE SUCCEEDS + 'ggg': '\u22d9', # ⋙ VERY MUCH GREATER-THAN + 'gggtr': '\u22d9', # ⋙ VERY MUCH GREATER-THAN + 'gnapprox': '\u2a8a', # ⪊ GREATER-THAN AND NOT APPROXIMATE + 'gneq': '\u2a88', # ⪈ GREATER-THAN AND SINGLE-LINE NOT EQUAL TO + 'gneqq': '\u2269', # ≩ GREATER-THAN BUT NOT EQUAL TO + 'gnsim': '\u22e7', # ⋧ GREATER-THAN BUT NOT EQUIVALENT TO + 'gtrapprox': '\u2a86', # ⪆ GREATER-THAN OR APPROXIMATE + 'gtreqless': '\u22db', # ⋛ GREATER-THAN EQUAL TO OR LESS-THAN + 'gtreqqless': '\u2a8c', # ⪌ GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN + 'gtrless': '\u2277', # ≷ GREATER-THAN OR LESS-THAN + 'gtrsim': '\u2273', # ≳ GREATER-THAN OR EQUIVALENT TO + 'hash': '\u22d5', # ⋕ EQUAL AND PARALLEL TO + 'hookleftarrow': '\u21a9', # ↩ LEFTWARDS ARROW WITH HOOK + 'hookrightarrow': '\u21aa', # ↪ RIGHTWARDS ARROW WITH HOOK + 'iddots': '\u22f0', # ⋰ UP RIGHT DIAGONAL ELLIPSIS + 'impliedby': '\u27f8', # ⟸ LONG LEFTWARDS DOUBLE ARROW + 'implies': '\u27f9', # ⟹ LONG RIGHTWARDS DOUBLE ARROW + 'in': '\u2208', # ∈ ELEMENT OF + 'le': '\u2264', # ≤ LESS-THAN OR EQUAL TO + 'leadsto': '\u2933', # ⤳ WAVE ARROW POINTING DIRECTLY RIGHT + 'leftarrow': '\u2190', # ← LEFTWARDS ARROW + 'leftarrowtail': '\u21a2', # ↢ LEFTWARDS ARROW WITH TAIL + 'leftarrowtriangle': '\u21fd', # ⇽ LEFTWARDS OPEN-HEADED ARROW + 'leftbarharpoon': '\u296a', # ⥪ LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH + 'leftharpoondown': '\u21bd', # ↽ LEFTWARDS HARPOON WITH BARB DOWNWARDS + 'leftharpoonup': '\u21bc', # ↼ LEFTWARDS HARPOON WITH BARB UPWARDS + 'leftleftarrows': '\u21c7', # ⇇ LEFTWARDS PAIRED ARROWS + 'leftleftharpoons': '\u2962', # ⥢ LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN + 'leftrightarrow': '\u2194', # ↔ LEFT RIGHT ARROW + 'leftrightarrows': '\u21c6', # ⇆ LEFTWARDS ARROW OVER RIGHTWARDS ARROW + 'leftrightarrowtriangle': '\u21ff', # ⇿ LEFT RIGHT OPEN-HEADED ARROW + 'leftrightharpoon': '\u294a', # ⥊ LEFT BARB UP RIGHT BARB DOWN HARPOON + 'leftrightharpoons': '\u21cb', # ⇋ LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON + 'leftrightsquigarrow': '\u21ad', # ↭ LEFT RIGHT WAVE ARROW + 'leftslice': '\u2aa6', # ⪦ LESS-THAN CLOSED BY CURVE + 'leftsquigarrow': '\u21dc', # ⇜ LEFTWARDS SQUIGGLE ARROW + 'leftturn': '\u21ba', # ↺ ANTICLOCKWISE OPEN CIRCLE ARROW + 'leq': '\u2264', # ≤ LESS-THAN OR EQUAL TO + 'leqq': '\u2266', # ≦ LESS-THAN OVER EQUAL TO + 'leqslant': '\u2a7d', # ⩽ LESS-THAN OR SLANTED EQUAL TO + 'lessapprox': '\u2a85', # ⪅ LESS-THAN OR APPROXIMATE + 'lesseqgtr': '\u22da', # ⋚ LESS-THAN EQUAL TO OR GREATER-THAN + 'lesseqqgtr': '\u2a8b', # ⪋ LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN + 'lessgtr': '\u2276', # ≶ LESS-THAN OR GREATER-THAN + 'lesssim': '\u2272', # ≲ LESS-THAN OR EQUIVALENT TO + 'lhd': '\u22b2', # ⊲ NORMAL SUBGROUP OF + 'lightning': '\u21af', # ↯ DOWNWARDS ZIGZAG ARROW + 'll': '\u226a', # ≪ MUCH LESS-THAN + 'llcurly': '\u2abb', # ⪻ DOUBLE PRECEDES + 'lll': '\u22d8', # ⋘ VERY MUCH LESS-THAN + 'llless': '\u22d8', # ⋘ VERY MUCH LESS-THAN + 'lnapprox': '\u2a89', # ⪉ LESS-THAN AND NOT APPROXIMATE + 'lneq': '\u2a87', # ⪇ LESS-THAN AND SINGLE-LINE NOT EQUAL TO + 'lneqq': '\u2268', # ≨ LESS-THAN BUT NOT EQUAL TO + 'lnsim': '\u22e6', # ⋦ LESS-THAN BUT NOT EQUIVALENT TO + 'longleftarrow': '\u27f5', # ⟵ LONG LEFTWARDS ARROW + 'longleftrightarrow': '\u27f7', # ⟷ LONG LEFT RIGHT ARROW + 'longmapsfrom': '\u27fb', # ⟻ LONG LEFTWARDS ARROW FROM BAR + 'longmapsto': '\u27fc', # ⟼ LONG RIGHTWARDS ARROW FROM BAR + 'longrightarrow': '\u27f6', # ⟶ LONG RIGHTWARDS ARROW + 'looparrowleft': '\u21ab', # ↫ LEFTWARDS ARROW WITH LOOP + 'looparrowright': '\u21ac', # ↬ RIGHTWARDS ARROW WITH LOOP + 'lrtimes': '\u22c8', # ⋈ BOWTIE + 'mapsfrom': '\u21a4', # ↤ LEFTWARDS ARROW FROM BAR + 'mapsto': '\u21a6', # ↦ RIGHTWARDS ARROW FROM BAR + 'mid': '\u2223', # ∣ DIVIDES + 'models': '\u22a7', # ⊧ MODELS + 'multimap': '\u22b8', # ⊸ MULTIMAP + 'multimapboth': '\u29df', # ⧟ DOUBLE-ENDED MULTIMAP + 'multimapdotbothA': '\u22b6', # ⊶ ORIGINAL OF + 'multimapdotbothB': '\u22b7', # ⊷ IMAGE OF + 'multimapinv': '\u27dc', # ⟜ LEFT MULTIMAP + 'nLeftarrow': '\u21cd', # ⇍ LEFTWARDS DOUBLE ARROW WITH STROKE + 'nLeftrightarrow': '\u21ce', # ⇎ LEFT RIGHT DOUBLE ARROW WITH STROKE + 'nRightarrow': '\u21cf', # ⇏ RIGHTWARDS DOUBLE ARROW WITH STROKE + 'nVDash': '\u22af', # ⊯ NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE + 'nVdash': '\u22ae', # ⊮ DOES NOT FORCE + 'ncong': '\u2247', # ≇ NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO + 'ne': '\u2260', # ≠ NOT EQUAL TO + 'nearrow': '\u2197', # ↗ NORTH EAST ARROW + 'neq': '\u2260', # ≠ NOT EQUAL TO + 'ngeq': '\u2271', # ≱ NEITHER GREATER-THAN NOR EQUAL TO + 'ngtr': '\u226f', # ≯ NOT GREATER-THAN + 'ngtrless': '\u2279', # ≹ NEITHER GREATER-THAN NOR LESS-THAN + 'ni': '\u220b', # ∋ CONTAINS AS MEMBER + 'nleftarrow': '\u219a', # ↚ LEFTWARDS ARROW WITH STROKE + 'nleftrightarrow': '\u21ae', # ↮ LEFT RIGHT ARROW WITH STROKE + 'nleq': '\u2270', # ≰ NEITHER LESS-THAN NOR EQUAL TO + 'nless': '\u226e', # ≮ NOT LESS-THAN + 'nlessgtr': '\u2278', # ≸ NEITHER LESS-THAN NOR GREATER-THAN + 'nmid': '\u2224', # ∤ DOES NOT DIVIDE + 'notasymp': '\u226d', # ≭ NOT EQUIVALENT TO + 'notin': '\u2209', # ∉ NOT AN ELEMENT OF + 'notni': '\u220c', # ∌ DOES NOT CONTAIN AS MEMBER + 'notowner': '\u220c', # ∌ DOES NOT CONTAIN AS MEMBER + 'notslash': '\u233f', # ⌿ APL FUNCTIONAL SYMBOL SLASH BAR + 'nparallel': '\u2226', # ∦ NOT PARALLEL TO + 'nprec': '\u2280', # ⊀ DOES NOT PRECEDE + 'npreceq': '\u22e0', # ⋠ DOES NOT PRECEDE OR EQUAL + 'nrightarrow': '\u219b', # ↛ RIGHTWARDS ARROW WITH STROKE + 'nsim': '\u2241', # ≁ NOT TILDE + 'nsimeq': '\u2244', # ≄ NOT ASYMPTOTICALLY EQUAL TO + 'nsubseteq': '\u2288', # ⊈ NEITHER A SUBSET OF NOR EQUAL TO + 'nsucc': '\u2281', # ⊁ DOES NOT SUCCEED + 'nsucceq': '\u22e1', # ⋡ DOES NOT SUCCEED OR EQUAL + 'nsupseteq': '\u2289', # ⊉ NEITHER A SUPERSET OF NOR EQUAL TO + 'ntriangleleft': '\u22ea', # ⋪ NOT NORMAL SUBGROUP OF + 'ntrianglelefteq': '\u22ec', # ⋬ NOT NORMAL SUBGROUP OF OR EQUAL TO + 'ntriangleright': '\u22eb', # ⋫ DOES NOT CONTAIN AS NORMAL SUBGROUP + 'ntrianglerighteq': '\u22ed', # ⋭ DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL + 'nvDash': '\u22ad', # ⊭ NOT TRUE + 'nvdash': '\u22ac', # ⊬ DOES NOT PROVE + 'nwarrow': '\u2196', # ↖ NORTH WEST ARROW + 'owns': '\u220b', # ∋ CONTAINS AS MEMBER + 'parallel': '\u2225', # ∥ PARALLEL TO + 'perp': '\u27c2', # ⟂ PERPENDICULAR + 'pitchfork': '\u22d4', # ⋔ PITCHFORK + 'prec': '\u227a', # ≺ PRECEDES + 'precapprox': '\u2ab7', # ⪷ PRECEDES ABOVE ALMOST EQUAL TO + 'preccurlyeq': '\u227c', # ≼ PRECEDES OR EQUAL TO + 'preceq': '\u2aaf', # ⪯ PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + 'preceqq': '\u2ab3', # ⪳ PRECEDES ABOVE EQUALS SIGN + 'precnapprox': '\u2ab9', # ⪹ PRECEDES ABOVE NOT ALMOST EQUAL TO + 'precneqq': '\u2ab5', # ⪵ PRECEDES ABOVE NOT EQUAL TO + 'precnsim': '\u22e8', # ⋨ PRECEDES BUT NOT EQUIVALENT TO + 'precsim': '\u227e', # ≾ PRECEDES OR EQUIVALENT TO + 'propto': '\u221d', # ∝ PROPORTIONAL TO + 'restriction': '\u21be', # ↾ UPWARDS HARPOON WITH BARB RIGHTWARDS + 'rhd': '\u22b3', # ⊳ CONTAINS AS NORMAL SUBGROUP + 'rightarrow': '\u2192', # → RIGHTWARDS ARROW + 'rightarrowtail': '\u21a3', # ↣ RIGHTWARDS ARROW WITH TAIL + 'rightarrowtriangle': '\u21fe', # ⇾ RIGHTWARDS OPEN-HEADED ARROW + 'rightbarharpoon': '\u296c', # ⥬ RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH + 'rightharpoondown': '\u21c1', # ⇁ RIGHTWARDS HARPOON WITH BARB DOWNWARDS + 'rightharpoonup': '\u21c0', # ⇀ RIGHTWARDS HARPOON WITH BARB UPWARDS + 'rightleftarrows': '\u21c4', # ⇄ RIGHTWARDS ARROW OVER LEFTWARDS ARROW + 'rightleftharpoon': '\u294b', # ⥋ LEFT BARB DOWN RIGHT BARB UP HARPOON + 'rightleftharpoons': '\u21cc', # ⇌ RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON + 'rightrightarrows': '\u21c9', # ⇉ RIGHTWARDS PAIRED ARROWS + 'rightrightharpoons': '\u2964', # ⥤ RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN + 'rightslice': '\u2aa7', # ⪧ GREATER-THAN CLOSED BY CURVE + 'rightsquigarrow': '\u21dd', # ⇝ RIGHTWARDS SQUIGGLE ARROW + 'rightturn': '\u21bb', # ↻ CLOCKWISE OPEN CIRCLE ARROW + 'risingdotseq': '\u2253', # ≓ IMAGE OF OR APPROXIMATELY EQUAL TO + 'searrow': '\u2198', # ↘ SOUTH EAST ARROW + 'sim': '\u223c', # ∼ TILDE OPERATOR + 'simeq': '\u2243', # ≃ ASYMPTOTICALLY EQUAL TO + 'smile': '\u2323', # ⌣ SMILE + 'sqsubset': '\u228f', # ⊏ SQUARE IMAGE OF + 'sqsubseteq': '\u2291', # ⊑ SQUARE IMAGE OF OR EQUAL TO + 'sqsupset': '\u2290', # ⊐ SQUARE ORIGINAL OF + 'sqsupseteq': '\u2292', # ⊒ SQUARE ORIGINAL OF OR EQUAL TO + 'strictfi': '\u297c', # ⥼ LEFT FISH TAIL + 'strictif': '\u297d', # ⥽ RIGHT FISH TAIL + 'subset': '\u2282', # ⊂ SUBSET OF + 'subseteq': '\u2286', # ⊆ SUBSET OF OR EQUAL TO + 'subseteqq': '\u2ac5', # ⫅ SUBSET OF ABOVE EQUALS SIGN + 'subsetneq': '\u228a', # ⊊ SUBSET OF WITH NOT EQUAL TO + 'subsetneqq': '\u2acb', # ⫋ SUBSET OF ABOVE NOT EQUAL TO + 'succ': '\u227b', # ≻ SUCCEEDS + 'succapprox': '\u2ab8', # ⪸ SUCCEEDS ABOVE ALMOST EQUAL TO + 'succcurlyeq': '\u227d', # ≽ SUCCEEDS OR EQUAL TO + 'succeq': '\u2ab0', # ⪰ SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + 'succeqq': '\u2ab4', # ⪴ SUCCEEDS ABOVE EQUALS SIGN + 'succnapprox': '\u2aba', # ⪺ SUCCEEDS ABOVE NOT ALMOST EQUAL TO + 'succneqq': '\u2ab6', # ⪶ SUCCEEDS ABOVE NOT EQUAL TO + 'succnsim': '\u22e9', # ⋩ SUCCEEDS BUT NOT EQUIVALENT TO + 'succsim': '\u227f', # ≿ SUCCEEDS OR EQUIVALENT TO + 'supset': '\u2283', # ⊃ SUPERSET OF + 'supseteq': '\u2287', # ⊇ SUPERSET OF OR EQUAL TO + 'supseteqq': '\u2ac6', # ⫆ SUPERSET OF ABOVE EQUALS SIGN + 'supsetneq': '\u228b', # ⊋ SUPERSET OF WITH NOT EQUAL TO + 'supsetneqq': '\u2acc', # ⫌ SUPERSET OF ABOVE NOT EQUAL TO + 'swarrow': '\u2199', # ↙ SOUTH WEST ARROW + 'therefore': '\u2234', # ∴ THEREFORE + 'to': '\u2192', # → RIGHTWARDS ARROW + 'trianglelefteq': '\u22b4', # ⊴ NORMAL SUBGROUP OF OR EQUAL TO + 'triangleq': '\u225c', # ≜ DELTA EQUAL TO + 'trianglerighteq': '\u22b5', # ⊵ CONTAINS AS NORMAL SUBGROUP OR EQUAL TO + 'twoheadleftarrow': '\u219e', # ↞ LEFTWARDS TWO HEADED ARROW + 'twoheadrightarrow': '\u21a0', # ↠ RIGHTWARDS TWO HEADED ARROW + 'uparrow': '\u2191', # ↑ UPWARDS ARROW + 'updownarrow': '\u2195', # ↕ UP DOWN ARROW + 'updownarrows': '\u21c5', # ⇅ UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW + 'updownharpoons': '\u296e', # ⥮ UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT + 'upharpoonleft': '\u21bf', # ↿ UPWARDS HARPOON WITH BARB LEFTWARDS + 'upharpoonright': '\u21be', # ↾ UPWARDS HARPOON WITH BARB RIGHTWARDS + 'upuparrows': '\u21c8', # ⇈ UPWARDS PAIRED ARROWS + 'upupharpoons': '\u2963', # ⥣ UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT + 'vDash': '\u22a8', # ⊨ TRUE + 'vartriangle': '\u25b5', # ▵ WHITE UP-POINTING SMALL TRIANGLE + 'vartriangleleft': '\u22b2', # ⊲ NORMAL SUBGROUP OF + 'vartriangleright': '\u22b3', # ⊳ CONTAINS AS NORMAL SUBGROUP + 'vdash': '\u22a2', # ⊢ RIGHT TACK + 'wasytherefore': '\u2234', # ∴ THEREFORE + } + +mathunder = { + 'underbrace': '\u23df', # ⏟ BOTTOM CURLY BRACKET + } + +space = { + ' ': ' ', # SPACE + ',': '\u2006', # SIX-PER-EM SPACE + ':': '\u205f', # MEDIUM MATHEMATICAL SPACE + 'medspace': '\u205f', # MEDIUM MATHEMATICAL SPACE + 'quad': '\u2001', # EM QUAD + 'thinspace': '\u2006', # SIX-PER-EM SPACE + } diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/math/unichar2tex.py b/.venv/lib/python3.12/site-packages/docutils/utils/math/unichar2tex.py new file mode 100644 index 00000000..da1f828a --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/math/unichar2tex.py @@ -0,0 +1,808 @@ +# LaTeX math to Unicode symbols translation table +# for use with the translate() method of unicode objects. +# Generated with ``write_unichar2tex.py`` from the data in +# http://milde.users.sourceforge.net/LUCR/Math/ + +# Includes commands from: standard LaTeX, amssymb, amsmath + +uni2tex_table = { +0xa0: '~', +0xa3: '\\pounds ', +0xa5: '\\yen ', +0xa7: '\\S ', +0xac: '\\neg ', +0xb1: '\\pm ', +0xb6: '\\P ', +0xd7: '\\times ', +0xf0: '\\eth ', +0xf7: '\\div ', +0x131: '\\imath ', +0x237: '\\jmath ', +0x393: '\\Gamma ', +0x394: '\\Delta ', +0x398: '\\Theta ', +0x39b: '\\Lambda ', +0x39e: '\\Xi ', +0x3a0: '\\Pi ', +0x3a3: '\\Sigma ', +0x3a5: '\\Upsilon ', +0x3a6: '\\Phi ', +0x3a8: '\\Psi ', +0x3a9: '\\Omega ', +0x3b1: '\\alpha ', +0x3b2: '\\beta ', +0x3b3: '\\gamma ', +0x3b4: '\\delta ', +0x3b5: '\\varepsilon ', +0x3b6: '\\zeta ', +0x3b7: '\\eta ', +0x3b8: '\\theta ', +0x3b9: '\\iota ', +0x3ba: '\\kappa ', +0x3bb: '\\lambda ', +0x3bc: '\\mu ', +0x3bd: '\\nu ', +0x3be: '\\xi ', +0x3c0: '\\pi ', +0x3c1: '\\rho ', +0x3c2: '\\varsigma ', +0x3c3: '\\sigma ', +0x3c4: '\\tau ', +0x3c5: '\\upsilon ', +0x3c6: '\\varphi ', +0x3c7: '\\chi ', +0x3c8: '\\psi ', +0x3c9: '\\omega ', +0x3d1: '\\vartheta ', +0x3d5: '\\phi ', +0x3d6: '\\varpi ', +0x3dd: '\\digamma ', +0x3f0: '\\varkappa ', +0x3f1: '\\varrho ', +0x3f5: '\\epsilon ', +0x3f6: '\\backepsilon ', +0x2001: '\\quad ', +0x2003: '\\quad ', +0x2006: '\\, ', +0x2016: '\\| ', +0x2020: '\\dagger ', +0x2021: '\\ddagger ', +0x2022: '\\bullet ', +0x2026: '\\ldots ', +0x2032: '\\prime ', +0x2035: '\\backprime ', +0x205f: '\\: ', +0x2102: '\\mathbb{C}', +0x210b: '\\mathcal{H}', +0x210c: '\\mathfrak{H}', +0x210d: '\\mathbb{H}', +0x210f: '\\hslash ', +0x2110: '\\mathcal{I}', +0x2111: '\\Im ', +0x2112: '\\mathcal{L}', +0x2113: '\\ell ', +0x2115: '\\mathbb{N}', +0x2118: '\\wp ', +0x2119: '\\mathbb{P}', +0x211a: '\\mathbb{Q}', +0x211b: '\\mathcal{R}', +0x211c: '\\Re ', +0x211d: '\\mathbb{R}', +0x2124: '\\mathbb{Z}', +0x2127: '\\mho ', +0x2128: '\\mathfrak{Z}', +0x212c: '\\mathcal{B}', +0x212d: '\\mathfrak{C}', +0x2130: '\\mathcal{E}', +0x2131: '\\mathcal{F}', +0x2132: '\\Finv ', +0x2133: '\\mathcal{M}', +0x2135: '\\aleph ', +0x2136: '\\beth ', +0x2137: '\\gimel ', +0x2138: '\\daleth ', +0x2190: '\\leftarrow ', +0x2191: '\\uparrow ', +0x2192: '\\rightarrow ', +0x2193: '\\downarrow ', +0x2194: '\\leftrightarrow ', +0x2195: '\\updownarrow ', +0x2196: '\\nwarrow ', +0x2197: '\\nearrow ', +0x2198: '\\searrow ', +0x2199: '\\swarrow ', +0x219a: '\\nleftarrow ', +0x219b: '\\nrightarrow ', +0x219e: '\\twoheadleftarrow ', +0x21a0: '\\twoheadrightarrow ', +0x21a2: '\\leftarrowtail ', +0x21a3: '\\rightarrowtail ', +0x21a6: '\\mapsto ', +0x21a9: '\\hookleftarrow ', +0x21aa: '\\hookrightarrow ', +0x21ab: '\\looparrowleft ', +0x21ac: '\\looparrowright ', +0x21ad: '\\leftrightsquigarrow ', +0x21ae: '\\nleftrightarrow ', +0x21b0: '\\Lsh ', +0x21b1: '\\Rsh ', +0x21b6: '\\curvearrowleft ', +0x21b7: '\\curvearrowright ', +0x21ba: '\\circlearrowleft ', +0x21bb: '\\circlearrowright ', +0x21bc: '\\leftharpoonup ', +0x21bd: '\\leftharpoondown ', +0x21be: '\\upharpoonright ', +0x21bf: '\\upharpoonleft ', +0x21c0: '\\rightharpoonup ', +0x21c1: '\\rightharpoondown ', +0x21c2: '\\downharpoonright ', +0x21c3: '\\downharpoonleft ', +0x21c4: '\\rightleftarrows ', +0x21c6: '\\leftrightarrows ', +0x21c7: '\\leftleftarrows ', +0x21c8: '\\upuparrows ', +0x21c9: '\\rightrightarrows ', +0x21ca: '\\downdownarrows ', +0x21cb: '\\leftrightharpoons ', +0x21cc: '\\rightleftharpoons ', +0x21cd: '\\nLeftarrow ', +0x21ce: '\\nLeftrightarrow ', +0x21cf: '\\nRightarrow ', +0x21d0: '\\Leftarrow ', +0x21d1: '\\Uparrow ', +0x21d2: '\\Rightarrow ', +0x21d3: '\\Downarrow ', +0x21d4: '\\Leftrightarrow ', +0x21d5: '\\Updownarrow ', +0x21da: '\\Lleftarrow ', +0x21db: '\\Rrightarrow ', +0x21dd: '\\rightsquigarrow ', +0x21e0: '\\dashleftarrow ', +0x21e2: '\\dashrightarrow ', +0x2200: '\\forall ', +0x2201: '\\complement ', +0x2202: '\\partial ', +0x2203: '\\exists ', +0x2204: '\\nexists ', +0x2205: '\\emptyset ', +0x2207: '\\nabla ', +0x2208: '\\in ', +0x2209: '\\notin ', +0x220b: '\\ni ', +0x220f: '\\prod ', +0x2210: '\\coprod ', +0x2211: '\\sum ', +0x2212: '-', +0x2213: '\\mp ', +0x2214: '\\dotplus ', +0x2215: '\\slash ', +0x2216: '\\smallsetminus ', +0x2217: '\\ast ', +0x2218: '\\circ ', +0x2219: '\\bullet ', +0x221a: '\\surd ', +0x221b: '\\sqrt[3] ', +0x221c: '\\sqrt[4] ', +0x221d: '\\propto ', +0x221e: '\\infty ', +0x2220: '\\angle ', +0x2221: '\\measuredangle ', +0x2222: '\\sphericalangle ', +0x2223: '\\mid ', +0x2224: '\\nmid ', +0x2225: '\\parallel ', +0x2226: '\\nparallel ', +0x2227: '\\wedge ', +0x2228: '\\vee ', +0x2229: '\\cap ', +0x222a: '\\cup ', +0x222b: '\\int ', +0x222c: '\\iint ', +0x222d: '\\iiint ', +0x222e: '\\oint ', +0x2234: '\\therefore ', +0x2235: '\\because ', +0x2236: ':', +0x223c: '\\sim ', +0x223d: '\\backsim ', +0x2240: '\\wr ', +0x2241: '\\nsim ', +0x2242: '\\eqsim ', +0x2243: '\\simeq ', +0x2245: '\\cong ', +0x2247: '\\ncong ', +0x2248: '\\approx ', +0x224a: '\\approxeq ', +0x224d: '\\asymp ', +0x224e: '\\Bumpeq ', +0x224f: '\\bumpeq ', +0x2250: '\\doteq ', +0x2251: '\\Doteq ', +0x2252: '\\fallingdotseq ', +0x2253: '\\risingdotseq ', +0x2256: '\\eqcirc ', +0x2257: '\\circeq ', +0x225c: '\\triangleq ', +0x2260: '\\neq ', +0x2261: '\\equiv ', +0x2264: '\\leq ', +0x2265: '\\geq ', +0x2266: '\\leqq ', +0x2267: '\\geqq ', +0x2268: '\\lneqq ', +0x2269: '\\gneqq ', +0x226a: '\\ll ', +0x226b: '\\gg ', +0x226c: '\\between ', +0x226e: '\\nless ', +0x226f: '\\ngtr ', +0x2270: '\\nleq ', +0x2271: '\\ngeq ', +0x2272: '\\lesssim ', +0x2273: '\\gtrsim ', +0x2276: '\\lessgtr ', +0x2277: '\\gtrless ', +0x227a: '\\prec ', +0x227b: '\\succ ', +0x227c: '\\preccurlyeq ', +0x227d: '\\succcurlyeq ', +0x227e: '\\precsim ', +0x227f: '\\succsim ', +0x2280: '\\nprec ', +0x2281: '\\nsucc ', +0x2282: '\\subset ', +0x2283: '\\supset ', +0x2286: '\\subseteq ', +0x2287: '\\supseteq ', +0x2288: '\\nsubseteq ', +0x2289: '\\nsupseteq ', +0x228a: '\\subsetneq ', +0x228b: '\\supsetneq ', +0x228e: '\\uplus ', +0x228f: '\\sqsubset ', +0x2290: '\\sqsupset ', +0x2291: '\\sqsubseteq ', +0x2292: '\\sqsupseteq ', +0x2293: '\\sqcap ', +0x2294: '\\sqcup ', +0x2295: '\\oplus ', +0x2296: '\\ominus ', +0x2297: '\\otimes ', +0x2298: '\\oslash ', +0x2299: '\\odot ', +0x229a: '\\circledcirc ', +0x229b: '\\circledast ', +0x229d: '\\circleddash ', +0x229e: '\\boxplus ', +0x229f: '\\boxminus ', +0x22a0: '\\boxtimes ', +0x22a1: '\\boxdot ', +0x22a2: '\\vdash ', +0x22a3: '\\dashv ', +0x22a4: '\\top ', +0x22a5: '\\bot ', +0x22a7: '\\models ', +0x22a8: '\\vDash ', +0x22a9: '\\Vdash ', +0x22aa: '\\Vvdash ', +0x22ac: '\\nvdash ', +0x22ad: '\\nvDash ', +0x22ae: '\\nVdash ', +0x22af: '\\nVDash ', +0x22b2: '\\vartriangleleft ', +0x22b3: '\\vartriangleright ', +0x22b4: '\\trianglelefteq ', +0x22b5: '\\trianglerighteq ', +0x22b8: '\\multimap ', +0x22ba: '\\intercal ', +0x22bb: '\\veebar ', +0x22bc: '\\barwedge ', +0x22c0: '\\bigwedge ', +0x22c1: '\\bigvee ', +0x22c2: '\\bigcap ', +0x22c3: '\\bigcup ', +0x22c4: '\\diamond ', +0x22c5: '\\cdot ', +0x22c6: '\\star ', +0x22c7: '\\divideontimes ', +0x22c8: '\\bowtie ', +0x22c9: '\\ltimes ', +0x22ca: '\\rtimes ', +0x22cb: '\\leftthreetimes ', +0x22cc: '\\rightthreetimes ', +0x22cd: '\\backsimeq ', +0x22ce: '\\curlyvee ', +0x22cf: '\\curlywedge ', +0x22d0: '\\Subset ', +0x22d1: '\\Supset ', +0x22d2: '\\Cap ', +0x22d3: '\\Cup ', +0x22d4: '\\pitchfork ', +0x22d6: '\\lessdot ', +0x22d7: '\\gtrdot ', +0x22d8: '\\lll ', +0x22d9: '\\ggg ', +0x22da: '\\lesseqgtr ', +0x22db: '\\gtreqless ', +0x22de: '\\curlyeqprec ', +0x22df: '\\curlyeqsucc ', +0x22e0: '\\npreceq ', +0x22e1: '\\nsucceq ', +0x22e6: '\\lnsim ', +0x22e7: '\\gnsim ', +0x22e8: '\\precnsim ', +0x22e9: '\\succnsim ', +0x22ea: '\\ntriangleleft ', +0x22eb: '\\ntriangleright ', +0x22ec: '\\ntrianglelefteq ', +0x22ed: '\\ntrianglerighteq ', +0x22ee: '\\vdots ', +0x22ef: '\\cdots ', +0x22f1: '\\ddots ', +0x2308: '\\lceil ', +0x2309: '\\rceil ', +0x230a: '\\lfloor ', +0x230b: '\\rfloor ', +0x231c: '\\ulcorner ', +0x231d: '\\urcorner ', +0x231e: '\\llcorner ', +0x231f: '\\lrcorner ', +0x2322: '\\frown ', +0x2323: '\\smile ', +0x23aa: '\\bracevert ', +0x23b0: '\\lmoustache ', +0x23b1: '\\rmoustache ', +0x23d0: '\\arrowvert ', +0x23de: '\\overbrace ', +0x23df: '\\underbrace ', +0x24c7: '\\circledR ', +0x24c8: '\\circledS ', +0x25b2: '\\blacktriangle ', +0x25b3: '\\bigtriangleup ', +0x25b7: '\\triangleright ', +0x25bc: '\\blacktriangledown ', +0x25bd: '\\bigtriangledown ', +0x25c1: '\\triangleleft ', +0x25c7: '\\Diamond ', +0x25ca: '\\lozenge ', +0x25ef: '\\bigcirc ', +0x25fb: '\\square ', +0x25fc: '\\blacksquare ', +0x2605: '\\bigstar ', +0x2660: '\\spadesuit ', +0x2661: '\\heartsuit ', +0x2662: '\\diamondsuit ', +0x2663: '\\clubsuit ', +0x266d: '\\flat ', +0x266e: '\\natural ', +0x266f: '\\sharp ', +0x2713: '\\checkmark ', +0x2720: '\\maltese ', +0x27c2: '\\perp ', +0x27cb: '\\diagup ', +0x27cd: '\\diagdown ', +0x27e8: '\\langle ', +0x27e9: '\\rangle ', +0x27ee: '\\lgroup ', +0x27ef: '\\rgroup ', +0x27f5: '\\longleftarrow ', +0x27f6: '\\longrightarrow ', +0x27f7: '\\longleftrightarrow ', +0x27f8: '\\Longleftarrow ', +0x27f9: '\\Longrightarrow ', +0x27fa: '\\Longleftrightarrow ', +0x27fc: '\\longmapsto ', +0x29eb: '\\blacklozenge ', +0x29f5: '\\setminus ', +0x2a00: '\\bigodot ', +0x2a01: '\\bigoplus ', +0x2a02: '\\bigotimes ', +0x2a04: '\\biguplus ', +0x2a06: '\\bigsqcup ', +0x2a0c: '\\iiiint ', +0x2a3f: '\\amalg ', +0x2a5e: '\\doublebarwedge ', +0x2a7d: '\\leqslant ', +0x2a7e: '\\geqslant ', +0x2a85: '\\lessapprox ', +0x2a86: '\\gtrapprox ', +0x2a87: '\\lneq ', +0x2a88: '\\gneq ', +0x2a89: '\\lnapprox ', +0x2a8a: '\\gnapprox ', +0x2a8b: '\\lesseqqgtr ', +0x2a8c: '\\gtreqqless ', +0x2a95: '\\eqslantless ', +0x2a96: '\\eqslantgtr ', +0x2aaf: '\\preceq ', +0x2ab0: '\\succeq ', +0x2ab5: '\\precneqq ', +0x2ab6: '\\succneqq ', +0x2ab7: '\\precapprox ', +0x2ab8: '\\succapprox ', +0x2ab9: '\\precnapprox ', +0x2aba: '\\succnapprox ', +0x2ac5: '\\subseteqq ', +0x2ac6: '\\supseteqq ', +0x2acb: '\\subsetneqq ', +0x2acc: '\\supsetneqq ', +0x2b1c: '\\Box ', +0x1d400: '\\mathbf{A}', +0x1d401: '\\mathbf{B}', +0x1d402: '\\mathbf{C}', +0x1d403: '\\mathbf{D}', +0x1d404: '\\mathbf{E}', +0x1d405: '\\mathbf{F}', +0x1d406: '\\mathbf{G}', +0x1d407: '\\mathbf{H}', +0x1d408: '\\mathbf{I}', +0x1d409: '\\mathbf{J}', +0x1d40a: '\\mathbf{K}', +0x1d40b: '\\mathbf{L}', +0x1d40c: '\\mathbf{M}', +0x1d40d: '\\mathbf{N}', +0x1d40e: '\\mathbf{O}', +0x1d40f: '\\mathbf{P}', +0x1d410: '\\mathbf{Q}', +0x1d411: '\\mathbf{R}', +0x1d412: '\\mathbf{S}', +0x1d413: '\\mathbf{T}', +0x1d414: '\\mathbf{U}', +0x1d415: '\\mathbf{V}', +0x1d416: '\\mathbf{W}', +0x1d417: '\\mathbf{X}', +0x1d418: '\\mathbf{Y}', +0x1d419: '\\mathbf{Z}', +0x1d41a: '\\mathbf{a}', +0x1d41b: '\\mathbf{b}', +0x1d41c: '\\mathbf{c}', +0x1d41d: '\\mathbf{d}', +0x1d41e: '\\mathbf{e}', +0x1d41f: '\\mathbf{f}', +0x1d420: '\\mathbf{g}', +0x1d421: '\\mathbf{h}', +0x1d422: '\\mathbf{i}', +0x1d423: '\\mathbf{j}', +0x1d424: '\\mathbf{k}', +0x1d425: '\\mathbf{l}', +0x1d426: '\\mathbf{m}', +0x1d427: '\\mathbf{n}', +0x1d428: '\\mathbf{o}', +0x1d429: '\\mathbf{p}', +0x1d42a: '\\mathbf{q}', +0x1d42b: '\\mathbf{r}', +0x1d42c: '\\mathbf{s}', +0x1d42d: '\\mathbf{t}', +0x1d42e: '\\mathbf{u}', +0x1d42f: '\\mathbf{v}', +0x1d430: '\\mathbf{w}', +0x1d431: '\\mathbf{x}', +0x1d432: '\\mathbf{y}', +0x1d433: '\\mathbf{z}', +0x1d434: 'A', +0x1d435: 'B', +0x1d436: 'C', +0x1d437: 'D', +0x1d438: 'E', +0x1d439: 'F', +0x1d43a: 'G', +0x1d43b: 'H', +0x1d43c: 'I', +0x1d43d: 'J', +0x1d43e: 'K', +0x1d43f: 'L', +0x1d440: 'M', +0x1d441: 'N', +0x1d442: 'O', +0x1d443: 'P', +0x1d444: 'Q', +0x1d445: 'R', +0x1d446: 'S', +0x1d447: 'T', +0x1d448: 'U', +0x1d449: 'V', +0x1d44a: 'W', +0x1d44b: 'X', +0x1d44c: 'Y', +0x1d44d: 'Z', +0x1d44e: 'a', +0x1d44f: 'b', +0x1d450: 'c', +0x1d451: 'd', +0x1d452: 'e', +0x1d453: 'f', +0x1d454: 'g', +0x1d456: 'i', +0x1d457: 'j', +0x1d458: 'k', +0x1d459: 'l', +0x1d45a: 'm', +0x1d45b: 'n', +0x1d45c: 'o', +0x1d45d: 'p', +0x1d45e: 'q', +0x1d45f: 'r', +0x1d460: 's', +0x1d461: 't', +0x1d462: 'u', +0x1d463: 'v', +0x1d464: 'w', +0x1d465: 'x', +0x1d466: 'y', +0x1d467: 'z', +0x1d49c: '\\mathcal{A}', +0x1d49e: '\\mathcal{C}', +0x1d49f: '\\mathcal{D}', +0x1d4a2: '\\mathcal{G}', +0x1d4a5: '\\mathcal{J}', +0x1d4a6: '\\mathcal{K}', +0x1d4a9: '\\mathcal{N}', +0x1d4aa: '\\mathcal{O}', +0x1d4ab: '\\mathcal{P}', +0x1d4ac: '\\mathcal{Q}', +0x1d4ae: '\\mathcal{S}', +0x1d4af: '\\mathcal{T}', +0x1d4b0: '\\mathcal{U}', +0x1d4b1: '\\mathcal{V}', +0x1d4b2: '\\mathcal{W}', +0x1d4b3: '\\mathcal{X}', +0x1d4b4: '\\mathcal{Y}', +0x1d4b5: '\\mathcal{Z}', +0x1d504: '\\mathfrak{A}', +0x1d505: '\\mathfrak{B}', +0x1d507: '\\mathfrak{D}', +0x1d508: '\\mathfrak{E}', +0x1d509: '\\mathfrak{F}', +0x1d50a: '\\mathfrak{G}', +0x1d50d: '\\mathfrak{J}', +0x1d50e: '\\mathfrak{K}', +0x1d50f: '\\mathfrak{L}', +0x1d510: '\\mathfrak{M}', +0x1d511: '\\mathfrak{N}', +0x1d512: '\\mathfrak{O}', +0x1d513: '\\mathfrak{P}', +0x1d514: '\\mathfrak{Q}', +0x1d516: '\\mathfrak{S}', +0x1d517: '\\mathfrak{T}', +0x1d518: '\\mathfrak{U}', +0x1d519: '\\mathfrak{V}', +0x1d51a: '\\mathfrak{W}', +0x1d51b: '\\mathfrak{X}', +0x1d51c: '\\mathfrak{Y}', +0x1d51e: '\\mathfrak{a}', +0x1d51f: '\\mathfrak{b}', +0x1d520: '\\mathfrak{c}', +0x1d521: '\\mathfrak{d}', +0x1d522: '\\mathfrak{e}', +0x1d523: '\\mathfrak{f}', +0x1d524: '\\mathfrak{g}', +0x1d525: '\\mathfrak{h}', +0x1d526: '\\mathfrak{i}', +0x1d527: '\\mathfrak{j}', +0x1d528: '\\mathfrak{k}', +0x1d529: '\\mathfrak{l}', +0x1d52a: '\\mathfrak{m}', +0x1d52b: '\\mathfrak{n}', +0x1d52c: '\\mathfrak{o}', +0x1d52d: '\\mathfrak{p}', +0x1d52e: '\\mathfrak{q}', +0x1d52f: '\\mathfrak{r}', +0x1d530: '\\mathfrak{s}', +0x1d531: '\\mathfrak{t}', +0x1d532: '\\mathfrak{u}', +0x1d533: '\\mathfrak{v}', +0x1d534: '\\mathfrak{w}', +0x1d535: '\\mathfrak{x}', +0x1d536: '\\mathfrak{y}', +0x1d537: '\\mathfrak{z}', +0x1d538: '\\mathbb{A}', +0x1d539: '\\mathbb{B}', +0x1d53b: '\\mathbb{D}', +0x1d53c: '\\mathbb{E}', +0x1d53d: '\\mathbb{F}', +0x1d53e: '\\mathbb{G}', +0x1d540: '\\mathbb{I}', +0x1d541: '\\mathbb{J}', +0x1d542: '\\mathbb{K}', +0x1d543: '\\mathbb{L}', +0x1d544: '\\mathbb{M}', +0x1d546: '\\mathbb{O}', +0x1d54a: '\\mathbb{S}', +0x1d54b: '\\mathbb{T}', +0x1d54c: '\\mathbb{U}', +0x1d54d: '\\mathbb{V}', +0x1d54e: '\\mathbb{W}', +0x1d54f: '\\mathbb{X}', +0x1d550: '\\mathbb{Y}', +0x1d55c: '\\Bbbk ', +0x1d5a0: '\\mathsf{A}', +0x1d5a1: '\\mathsf{B}', +0x1d5a2: '\\mathsf{C}', +0x1d5a3: '\\mathsf{D}', +0x1d5a4: '\\mathsf{E}', +0x1d5a5: '\\mathsf{F}', +0x1d5a6: '\\mathsf{G}', +0x1d5a7: '\\mathsf{H}', +0x1d5a8: '\\mathsf{I}', +0x1d5a9: '\\mathsf{J}', +0x1d5aa: '\\mathsf{K}', +0x1d5ab: '\\mathsf{L}', +0x1d5ac: '\\mathsf{M}', +0x1d5ad: '\\mathsf{N}', +0x1d5ae: '\\mathsf{O}', +0x1d5af: '\\mathsf{P}', +0x1d5b0: '\\mathsf{Q}', +0x1d5b1: '\\mathsf{R}', +0x1d5b2: '\\mathsf{S}', +0x1d5b3: '\\mathsf{T}', +0x1d5b4: '\\mathsf{U}', +0x1d5b5: '\\mathsf{V}', +0x1d5b6: '\\mathsf{W}', +0x1d5b7: '\\mathsf{X}', +0x1d5b8: '\\mathsf{Y}', +0x1d5b9: '\\mathsf{Z}', +0x1d5ba: '\\mathsf{a}', +0x1d5bb: '\\mathsf{b}', +0x1d5bc: '\\mathsf{c}', +0x1d5bd: '\\mathsf{d}', +0x1d5be: '\\mathsf{e}', +0x1d5bf: '\\mathsf{f}', +0x1d5c0: '\\mathsf{g}', +0x1d5c1: '\\mathsf{h}', +0x1d5c2: '\\mathsf{i}', +0x1d5c3: '\\mathsf{j}', +0x1d5c4: '\\mathsf{k}', +0x1d5c5: '\\mathsf{l}', +0x1d5c6: '\\mathsf{m}', +0x1d5c7: '\\mathsf{n}', +0x1d5c8: '\\mathsf{o}', +0x1d5c9: '\\mathsf{p}', +0x1d5ca: '\\mathsf{q}', +0x1d5cb: '\\mathsf{r}', +0x1d5cc: '\\mathsf{s}', +0x1d5cd: '\\mathsf{t}', +0x1d5ce: '\\mathsf{u}', +0x1d5cf: '\\mathsf{v}', +0x1d5d0: '\\mathsf{w}', +0x1d5d1: '\\mathsf{x}', +0x1d5d2: '\\mathsf{y}', +0x1d5d3: '\\mathsf{z}', +0x1d670: '\\mathtt{A}', +0x1d671: '\\mathtt{B}', +0x1d672: '\\mathtt{C}', +0x1d673: '\\mathtt{D}', +0x1d674: '\\mathtt{E}', +0x1d675: '\\mathtt{F}', +0x1d676: '\\mathtt{G}', +0x1d677: '\\mathtt{H}', +0x1d678: '\\mathtt{I}', +0x1d679: '\\mathtt{J}', +0x1d67a: '\\mathtt{K}', +0x1d67b: '\\mathtt{L}', +0x1d67c: '\\mathtt{M}', +0x1d67d: '\\mathtt{N}', +0x1d67e: '\\mathtt{O}', +0x1d67f: '\\mathtt{P}', +0x1d680: '\\mathtt{Q}', +0x1d681: '\\mathtt{R}', +0x1d682: '\\mathtt{S}', +0x1d683: '\\mathtt{T}', +0x1d684: '\\mathtt{U}', +0x1d685: '\\mathtt{V}', +0x1d686: '\\mathtt{W}', +0x1d687: '\\mathtt{X}', +0x1d688: '\\mathtt{Y}', +0x1d689: '\\mathtt{Z}', +0x1d68a: '\\mathtt{a}', +0x1d68b: '\\mathtt{b}', +0x1d68c: '\\mathtt{c}', +0x1d68d: '\\mathtt{d}', +0x1d68e: '\\mathtt{e}', +0x1d68f: '\\mathtt{f}', +0x1d690: '\\mathtt{g}', +0x1d691: '\\mathtt{h}', +0x1d692: '\\mathtt{i}', +0x1d693: '\\mathtt{j}', +0x1d694: '\\mathtt{k}', +0x1d695: '\\mathtt{l}', +0x1d696: '\\mathtt{m}', +0x1d697: '\\mathtt{n}', +0x1d698: '\\mathtt{o}', +0x1d699: '\\mathtt{p}', +0x1d69a: '\\mathtt{q}', +0x1d69b: '\\mathtt{r}', +0x1d69c: '\\mathtt{s}', +0x1d69d: '\\mathtt{t}', +0x1d69e: '\\mathtt{u}', +0x1d69f: '\\mathtt{v}', +0x1d6a0: '\\mathtt{w}', +0x1d6a1: '\\mathtt{x}', +0x1d6a2: '\\mathtt{y}', +0x1d6a3: '\\mathtt{z}', +0x1d6a4: '\\imath ', +0x1d6a5: '\\jmath ', +0x1d6aa: '\\mathbf{\\Gamma}', +0x1d6ab: '\\mathbf{\\Delta}', +0x1d6af: '\\mathbf{\\Theta}', +0x1d6b2: '\\mathbf{\\Lambda}', +0x1d6b5: '\\mathbf{\\Xi}', +0x1d6b7: '\\mathbf{\\Pi}', +0x1d6ba: '\\mathbf{\\Sigma}', +0x1d6bc: '\\mathbf{\\Upsilon}', +0x1d6bd: '\\mathbf{\\Phi}', +0x1d6bf: '\\mathbf{\\Psi}', +0x1d6c0: '\\mathbf{\\Omega}', +0x1d6e4: '\\mathit{\\Gamma}', +0x1d6e5: '\\mathit{\\Delta}', +0x1d6e9: '\\mathit{\\Theta}', +0x1d6ec: '\\mathit{\\Lambda}', +0x1d6ef: '\\mathit{\\Xi}', +0x1d6f1: '\\mathit{\\Pi}', +0x1d6f4: '\\mathit{\\Sigma}', +0x1d6f6: '\\mathit{\\Upsilon}', +0x1d6f7: '\\mathit{\\Phi}', +0x1d6f9: '\\mathit{\\Psi}', +0x1d6fa: '\\mathit{\\Omega}', +0x1d6fc: '\\alpha ', +0x1d6fd: '\\beta ', +0x1d6fe: '\\gamma ', +0x1d6ff: '\\delta ', +0x1d700: '\\varepsilon ', +0x1d701: '\\zeta ', +0x1d702: '\\eta ', +0x1d703: '\\theta ', +0x1d704: '\\iota ', +0x1d705: '\\kappa ', +0x1d706: '\\lambda ', +0x1d707: '\\mu ', +0x1d708: '\\nu ', +0x1d709: '\\xi ', +0x1d70b: '\\pi ', +0x1d70c: '\\rho ', +0x1d70d: '\\varsigma ', +0x1d70e: '\\sigma ', +0x1d70f: '\\tau ', +0x1d710: '\\upsilon ', +0x1d711: '\\varphi ', +0x1d712: '\\chi ', +0x1d713: '\\psi ', +0x1d714: '\\omega ', +0x1d715: '\\partial ', +0x1d716: '\\epsilon ', +0x1d717: '\\vartheta ', +0x1d718: '\\varkappa ', +0x1d719: '\\phi ', +0x1d71a: '\\varrho ', +0x1d71b: '\\varpi ', +0x1d7ce: '\\mathbf{0}', +0x1d7cf: '\\mathbf{1}', +0x1d7d0: '\\mathbf{2}', +0x1d7d1: '\\mathbf{3}', +0x1d7d2: '\\mathbf{4}', +0x1d7d3: '\\mathbf{5}', +0x1d7d4: '\\mathbf{6}', +0x1d7d5: '\\mathbf{7}', +0x1d7d6: '\\mathbf{8}', +0x1d7d7: '\\mathbf{9}', +0x1d7e2: '\\mathsf{0}', +0x1d7e3: '\\mathsf{1}', +0x1d7e4: '\\mathsf{2}', +0x1d7e5: '\\mathsf{3}', +0x1d7e6: '\\mathsf{4}', +0x1d7e7: '\\mathsf{5}', +0x1d7e8: '\\mathsf{6}', +0x1d7e9: '\\mathsf{7}', +0x1d7ea: '\\mathsf{8}', +0x1d7eb: '\\mathsf{9}', +0x1d7f6: '\\mathtt{0}', +0x1d7f7: '\\mathtt{1}', +0x1d7f8: '\\mathtt{2}', +0x1d7f9: '\\mathtt{3}', +0x1d7fa: '\\mathtt{4}', +0x1d7fb: '\\mathtt{5}', +0x1d7fc: '\\mathtt{6}', +0x1d7fd: '\\mathtt{7}', +0x1d7fe: '\\mathtt{8}', +0x1d7ff: '\\mathtt{9}', +} diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/punctuation_chars.py b/.venv/lib/python3.12/site-packages/docutils/utils/punctuation_chars.py new file mode 100644 index 00000000..9dd21404 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/punctuation_chars.py @@ -0,0 +1,123 @@ +# :Id: $Id: punctuation_chars.py 9270 2022-11-24 20:28:03Z milde $ +# :Copyright: © 2011, 2017 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 +# +# This file is generated by +# ``docutils/tools/dev/generate_punctuation_chars.py``. +# :: + +"""Docutils character category patterns. + + Patterns for the implementation of the `inline markup recognition rules`_ + in the reStructuredText parser `docutils.parsers.rst.states.py` based + on Unicode character categories. + The patterns are used inside ``[ ]`` in regular expressions. + + Rule (5) requires determination of matching open/close pairs. However, the + pairing of open/close quotes is ambiguous due to different typographic + conventions in different languages. The ``quote_pairs`` function tests + whether two characters form an open/close pair. + + The patterns are generated by + ``docutils/tools/dev/generate_punctuation_chars.py`` to prevent dependence + on the Python version and avoid the time-consuming generation with every + Docutils run. See there for motives and implementation details. + + The category of some characters changed with the development of the + Unicode standard. The current lists are generated with the help of the + "unicodedata" module of Python 2.7.13 (based on Unicode version 5.2.0). + + .. _inline markup recognition rules: + https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html + #inline-markup-recognition-rules +""" + +openers = ( + '"\'(<\\[{\u0f3a\u0f3c\u169b\u2045\u207d\u208d\u2329\u2768' + '\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea' + '\u27ec\u27ee\u2983\u2985\u2987\u2989\u298b\u298d\u298f\u2991' + '\u2993\u2995\u2997\u29d8\u29da\u29fc\u2e22\u2e24\u2e26\u2e28' + '\u3008\u300a\u300c\u300e\u3010\u3014\u3016\u3018\u301a\u301d' + '\u301d\ufd3e\ufe17\ufe35\ufe37\ufe39\ufe3b\ufe3d\ufe3f\ufe41' + '\ufe43\ufe47\ufe59\ufe5b\ufe5d\uff08\uff3b\uff5b\uff5f\uff62' + '\xab\u2018\u201c\u2039\u2e02\u2e04\u2e09\u2e0c\u2e1c\u2e20' + '\u201a\u201e\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d' + '\u2e1d\u2e21\u201b\u201f' + ) +closers = ( + '"\')>\\]}\u0f3b\u0f3d\u169c\u2046\u207e\u208e\u232a\u2769' + '\u276b\u276d\u276f\u2771\u2773\u2775\u27c6\u27e7\u27e9\u27eb' + '\u27ed\u27ef\u2984\u2986\u2988\u298a\u298c\u298e\u2990\u2992' + '\u2994\u2996\u2998\u29d9\u29db\u29fd\u2e23\u2e25\u2e27\u2e29' + '\u3009\u300b\u300d\u300f\u3011\u3015\u3017\u3019\u301b\u301e' + '\u301f\ufd3f\ufe18\ufe36\ufe38\ufe3a\ufe3c\ufe3e\ufe40\ufe42' + '\ufe44\ufe48\ufe5a\ufe5c\ufe5e\uff09\uff3d\uff5d\uff60\uff63' + '\xbb\u2019\u201d\u203a\u2e03\u2e05\u2e0a\u2e0d\u2e1d\u2e21' + '\u201b\u201f\xab\u2018\u201c\u2039\u2e02\u2e04\u2e09\u2e0c' + '\u2e1c\u2e20\u201a\u201e' + ) +delimiters = ( + '\\-/:\u058a\xa1\xb7\xbf\u037e\u0387\u055a-\u055f\u0589' + '\u05be\u05c0\u05c3\u05c6\u05f3\u05f4\u0609\u060a\u060c' + '\u060d\u061b\u061e\u061f\u066a-\u066d\u06d4\u0700-\u070d' + '\u07f7-\u07f9\u0830-\u083e\u0964\u0965\u0970\u0df4\u0e4f' + '\u0e5a\u0e5b\u0f04-\u0f12\u0f85\u0fd0-\u0fd4\u104a-\u104f' + '\u10fb\u1361-\u1368\u1400\u166d\u166e\u16eb-\u16ed\u1735' + '\u1736\u17d4-\u17d6\u17d8-\u17da\u1800-\u180a\u1944\u1945' + '\u19de\u19df\u1a1e\u1a1f\u1aa0-\u1aa6\u1aa8-\u1aad\u1b5a-' + '\u1b60\u1c3b-\u1c3f\u1c7e\u1c7f\u1cd3\u2010-\u2017\u2020-' + '\u2027\u2030-\u2038\u203b-\u203e\u2041-\u2043\u2047-' + '\u2051\u2053\u2055-\u205e\u2cf9-\u2cfc\u2cfe\u2cff\u2e00' + '\u2e01\u2e06-\u2e08\u2e0b\u2e0e-\u2e1b\u2e1e\u2e1f\u2e2a-' + '\u2e2e\u2e30\u2e31\u3001-\u3003\u301c\u3030\u303d\u30a0' + '\u30fb\ua4fe\ua4ff\ua60d-\ua60f\ua673\ua67e\ua6f2-\ua6f7' + '\ua874-\ua877\ua8ce\ua8cf\ua8f8-\ua8fa\ua92e\ua92f\ua95f' + '\ua9c1-\ua9cd\ua9de\ua9df\uaa5c-\uaa5f\uaade\uaadf\uabeb' + '\ufe10-\ufe16\ufe19\ufe30-\ufe32\ufe45\ufe46\ufe49-\ufe4c' + '\ufe50-\ufe52\ufe54-\ufe58\ufe5f-\ufe61\ufe63\ufe68\ufe6a' + '\ufe6b\uff01-\uff03\uff05-\uff07\uff0a\uff0c-\uff0f\uff1a' + '\uff1b\uff1f\uff20\uff3c\uff61\uff64\uff65' + '\U00010100\U00010101\U0001039f\U000103d0\U00010857' + '\U0001091f\U0001093f\U00010a50-\U00010a58\U00010a7f' + '\U00010b39-\U00010b3f\U000110bb\U000110bc\U000110be-' + '\U000110c1\U00012470-\U00012473' + ) +closing_delimiters = r'\\.,;!?' + + +# Matching open/close quotes +# -------------------------- + +# Matching open/close pairs are at the same position in +# `punctuation_chars.openers` and `punctuation_chars.closers`. +# Additional matches (due to different typographic conventions +# in different languages) are stored in `quote_pairs`. + +quote_pairs = { + # open char: matching closing characters # use case + '\xbb': '\xbb', # » » Swedish + '\u2018': '\u201a', # ‘ ‚ Albanian/Greek/Turkish + '\u2019': '\u2019', # ’ ’ Swedish + '\u201a': '\u2018\u2019', # ‚ ‘ German, ‚ ’ Polish + '\u201c': '\u201e', # “ „ Albanian/Greek/Turkish + '\u201e': '\u201c\u201d', # „ “ German, „ ” Polish + '\u201d': '\u201d', # ” ” Swedish + '\u203a': '\u203a', # › › Swedish + } +"""Additional open/close quote pairs.""" + + +def match_chars(c1, c2): + """Test whether `c1` and `c2` are a matching open/close character pair.""" + try: + i = openers.index(c1) + except ValueError: # c1 not in openers + return False + return c2 == closers[i] or c2 in quote_pairs.get(c1, '') diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/roman.py b/.venv/lib/python3.12/site-packages/docutils/utils/roman.py new file mode 100644 index 00000000..df0c5b33 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/roman.py @@ -0,0 +1,154 @@ +############################################################################## +# +# Copyright (c) 2001 Mark Pilgrim and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## +"""Convert to and from Roman numerals""" + +__author__ = "Mark Pilgrim (f8dy@diveintopython.org)" +__version__ = "1.4" +__date__ = "8 August 2001" +__copyright__ = """Copyright (c) 2001 Mark Pilgrim + +This program is part of "Dive Into Python", a free Python tutorial for +experienced programmers. Visit http://diveintopython.org/ for the +latest version. + +This program is free software; you can redistribute it and/or modify +it under the terms of the Python 2.1.1 license, available at +http://www.python.org/2.1.1/license.html +""" + +import argparse +import re +import sys + + +# Define exceptions +class RomanError(Exception): + pass + + +class OutOfRangeError(RomanError): + pass + + +class NotIntegerError(RomanError): + pass + + +class InvalidRomanNumeralError(RomanError): + pass + + +# Define digit mapping +romanNumeralMap = (('M', 1000), + ('CM', 900), + ('D', 500), + ('CD', 400), + ('C', 100), + ('XC', 90), + ('L', 50), + ('XL', 40), + ('X', 10), + ('IX', 9), + ('V', 5), + ('IV', 4), + ('I', 1)) + + +def toRoman(n): + """convert integer to Roman numeral""" + if not isinstance(n, int): + raise NotIntegerError("decimals can not be converted") + if not (-1 < n < 5000): + raise OutOfRangeError("number out of range (must be 0..4999)") + + # special case + if n == 0: + return 'N' + + result = "" + for numeral, integer in romanNumeralMap: + while n >= integer: + result += numeral + n -= integer + return result + + +# Define pattern to detect valid Roman numerals +romanNumeralPattern = re.compile(""" + ^ # beginning of string + M{0,4} # thousands - 0 to 4 M's + (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), + # or 500-800 (D, followed by 0 to 3 C's) + (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), + # or 50-80 (L, followed by 0 to 3 X's) + (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), + # or 5-8 (V, followed by 0 to 3 I's) + $ # end of string + """, re.VERBOSE) + + +def fromRoman(s): + """convert Roman numeral to integer""" + if not s: + raise InvalidRomanNumeralError('Input can not be blank') + + # special case + if s == 'N': + return 0 + + if not romanNumeralPattern.search(s): + raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) + + result = 0 + index = 0 + for numeral, integer in romanNumeralMap: + while s[index:index + len(numeral)] == numeral: + result += integer + index += len(numeral) + return result + + +def parse_args(): + parser = argparse.ArgumentParser( + prog='roman', + description='convert between roman and arabic numerals' + ) + parser.add_argument('number', help='the value to convert') + parser.add_argument( + '-r', '--reverse', + action='store_true', + default=False, + help='convert roman to numeral (case insensitive) [default: False]') + + args = parser.parse_args() + args.number = args.number + return args + + +def main(): + args = parse_args() + if args.reverse: + u = args.number.upper() + r = fromRoman(u) + print(r) + else: + i = int(args.number) + n = toRoman(i) + print(n) + + return 0 + + +if __name__ == "__main__": # pragma: no cover + sys.exit(main()) # pragma: no cover diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/smartquotes.py b/.venv/lib/python3.12/site-packages/docutils/utils/smartquotes.py new file mode 100755 index 00000000..b8766db2 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/smartquotes.py @@ -0,0 +1,1004 @@ +#!/usr/bin/python3 +# :Id: $Id: smartquotes.py 9481 2023-11-19 21:19:20Z milde $ +# :Copyright: © 2010-2023 Günter Milde, +# original `SmartyPants`_: © 2003 John Gruber +# smartypants.py: © 2004, 2007 Chad Miller +# :Maintainer: docutils-develop@lists.sourceforge.net +# :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 +# notices 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 + + +r""" +========================= +Smart Quotes for Docutils +========================= + +Synopsis +======== + +"SmartyPants" is a free web publishing plug-in for Movable Type, Blosxom, and +BBEdit that easily translates plain ASCII punctuation characters into "smart" +typographic punctuation characters. + +``smartquotes.py`` is an adaption of "SmartyPants" to Docutils_. + +* Using Unicode instead of HTML entities for typographic punctuation + characters, it works for any output format that supports Unicode. +* Supports `language specific quote characters`__. + +__ https://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks + + +Authors +======= + +`John Gruber`_ did all of the hard work of writing this software in Perl for +`Movable Type`_ and almost all of this useful documentation. `Chad Miller`_ +ported it to Python to use with Pyblosxom_. +Adapted to Docutils_ by Günter Milde. + +Additional Credits +================== + +Portions of the SmartyPants original work are based on Brad Choate's nifty +MTRegex plug-in. `Brad Choate`_ also contributed a few bits of source code to +this plug-in. Brad Choate is a fine hacker indeed. + +`Jeremy Hedley`_ and `Charles Wiltgen`_ deserve mention for exemplary beta +testing of the original SmartyPants. + +`Rael Dornfest`_ ported SmartyPants to Blosxom. + +.. _Brad Choate: http://bradchoate.com/ +.. _Jeremy Hedley: http://antipixel.com/ +.. _Charles Wiltgen: http://playbacktime.com/ +.. _Rael Dornfest: http://raelity.org/ + + +Copyright and License +===================== + +SmartyPants_ license (3-Clause BSD license): + + Copyright (c) 2003 John Gruber (http://daringfireball.net/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name "SmartyPants" nor the names of its contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + + This software is provided by the copyright holders and contributors + "as is" and any express or implied warranties, including, but not + limited to, the implied warranties of merchantability and fitness for + a particular purpose are disclaimed. In no event shall the copyright + owner or contributors be liable for any direct, indirect, incidental, + special, exemplary, or consequential damages (including, but not + limited to, procurement of substitute goods or services; loss of use, + data, or profits; or business interruption) however caused and on any + theory of liability, whether in contract, strict liability, or tort + (including negligence or otherwise) arising in any way out of the use + of this software, even if advised of the possibility of such damage. + +smartypants.py license (2-Clause BSD license): + + smartypants.py is a derivative work of SmartyPants. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + This software is provided by the copyright holders and contributors + "as is" and any express or implied warranties, including, but not + limited to, the implied warranties of merchantability and fitness for + a particular purpose are disclaimed. In no event shall the copyright + owner or contributors be liable for any direct, indirect, incidental, + special, exemplary, or consequential damages (including, but not + limited to, procurement of substitute goods or services; loss of use, + data, or profits; or business interruption) however caused and on any + theory of liability, whether in contract, strict liability, or tort + (including negligence or otherwise) arising in any way out of the use + of this software, even if advised of the possibility of such damage. + +.. _John Gruber: http://daringfireball.net/ +.. _Chad Miller: http://web.chad.org/ + +.. _Pyblosxom: http://pyblosxom.bluesock.org/ +.. _SmartyPants: http://daringfireball.net/projects/smartypants/ +.. _Movable Type: http://www.movabletype.org/ +.. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause +.. _Docutils: https://docutils.sourceforge.io/ + +Description +=========== + +SmartyPants can perform the following transformations: + +- Straight quotes ( " and ' ) into "curly" quote characters +- Backticks-style quotes (\`\`like this'') into "curly" quote characters +- Dashes (``--`` and ``---``) into en- and em-dash entities +- Three consecutive dots (``...`` or ``. . .``) into an ellipsis ``…``. + +This means you can write, edit, and save your posts using plain old +ASCII straight quotes, plain dashes, and plain dots, but your published +posts (and final HTML output) will appear with smart quotes, em-dashes, +and proper ellipses. + +Backslash Escapes +================= + +If you need to use literal straight quotes (or plain hyphens and periods), +`smartquotes` accepts the following backslash escape sequences to force +ASCII-punctuation. Mind, that you need two backslashes in "docstrings", as +Python expands them, too. + +======== ========= +Escape Character +======== ========= +``\\`` \\ +``\\"`` \\" +``\\'`` \\' +``\\.`` \\. +``\\-`` \\- +``\\``` \\` +======== ========= + +This is useful, for example, when you want to use straight quotes as +foot and inch marks: 6\\'2\\" tall; a 17\\" iMac. + + +Caveats +======= + +Why You Might Not Want to Use Smart Quotes in Your Weblog +--------------------------------------------------------- + +For one thing, you might not care. + +Most normal, mentally stable individuals do not take notice of proper +typographic punctuation. Many design and typography nerds, however, break +out in a nasty rash when they encounter, say, a restaurant sign that uses +a straight apostrophe to spell "Joe's". + +If you're the sort of person who just doesn't care, you might well want to +continue not caring. Using straight quotes -- and sticking to the 7-bit +ASCII character set in general -- is certainly a simpler way to live. + +Even if you *do* care about accurate typography, you still might want to +think twice before educating the quote characters in your weblog. One side +effect of publishing curly quote characters is that it makes your +weblog a bit harder for others to quote from using copy-and-paste. What +happens is that when someone copies text from your blog, the copied text +contains the 8-bit curly quote characters (as well as the 8-bit characters +for em-dashes and ellipses, if you use these options). These characters +are not standard across different text encoding methods, which is why they +need to be encoded as characters. + +People copying text from your weblog, however, may not notice that you're +using curly quotes, and they'll go ahead and paste the unencoded 8-bit +characters copied from their browser into an email message or their own +weblog. When pasted as raw "smart quotes", these characters are likely to +get mangled beyond recognition. + +That said, my own opinion is that any decent text editor or email client +makes it easy to stupefy smart quote characters into their 7-bit +equivalents, and I don't consider it my problem if you're using an +indecent text editor or email client. + + +Algorithmic Shortcomings +------------------------ + +One situation in which quotes will get curled the wrong way is when +apostrophes are used at the start of leading contractions. For example:: + + 'Twas the night before Christmas. + +In the case above, SmartyPants will turn the apostrophe into an opening +secondary quote, when in fact it should be the `RIGHT SINGLE QUOTATION MARK` +character which is also "the preferred character to use for apostrophe" +(Unicode). I don't think this problem can be solved in the general case -- +every word processor I've tried gets this wrong as well. In such cases, it's +best to inset the `RIGHT SINGLE QUOTATION MARK` (’) by hand. + +In English, the same character is used for apostrophe and closing secondary +quote (both plain and "smart" ones). For other locales (French, Italean, +Swiss, ...) "smart" secondary closing quotes differ from the curly apostrophe. + + .. class:: language-fr + + Il dit : "C'est 'super' !" + +If the apostrophe is used at the end of a word, it cannot be distinguished +from a secondary quote by the algorithm. Therefore, a text like:: + + .. class:: language-de-CH + + "Er sagt: 'Ich fass' es nicht.'" + +will get a single closing guillemet instead of an apostrophe. + +This can be prevented by use use of the `RIGHT SINGLE QUOTATION MARK` in +the source:: + + - "Er sagt: 'Ich fass' es nicht.'" + + "Er sagt: 'Ich fass’ es nicht.'" + + +Version History +=============== + +1.10 2023-11-18 + - Pre-compile regexps once, not with every call of `educateQuotes()` + (patch #206 by Chris Sewell). Simplify regexps. + +1.9 2022-03-04 + - Code cleanup. Require Python 3. + +1.8.1 2017-10-25 + - Use open quote after Unicode whitespace, ZWSP, and ZWNJ. + - Code cleanup. + +1.8: 2017-04-24 + - Command line front-end. + +1.7.1: 2017-03-19 + - Update and extend language-dependent quotes. + - Differentiate apostrophe from single quote. + +1.7: 2012-11-19 + - Internationalization: language-dependent quotes. + +1.6.1: 2012-11-06 + - Refactor code, code cleanup, + - `educate_tokens()` generator as interface for Docutils. + +1.6: 2010-08-26 + - Adaption to Docutils: + - Use Unicode instead of HTML entities, + - Remove code special to pyblosxom. + +1.5_1.6: Fri, 27 Jul 2007 07:06:40 -0400 + - Fixed bug where blocks of precious unalterable text was instead + interpreted. Thanks to Le Roux and Dirk van Oosterbosch. + +1.5_1.5: Sat, 13 Aug 2005 15:50:24 -0400 + - Fix bogus magical quotation when there is no hint that the + user wants it, e.g., in "21st century". Thanks to Nathan Hamblen. + - Be smarter about quotes before terminating numbers in an en-dash'ed + range. + +1.5_1.4: Thu, 10 Feb 2005 20:24:36 -0500 + - Fix a date-processing bug, as reported by jacob childress. + - Begin a test-suite for ensuring correct output. + - Removed import of "string", since I didn't really need it. + (This was my first every Python program. Sue me!) + +1.5_1.3: Wed, 15 Sep 2004 18:25:58 -0400 + - Abort processing if the flavour is in forbidden-list. Default of + [ "rss" ] (Idea of Wolfgang SCHNERRING.) + - Remove stray virgules from en-dashes. Patch by Wolfgang SCHNERRING. + +1.5_1.2: Mon, 24 May 2004 08:14:54 -0400 + - Some single quotes weren't replaced properly. Diff-tesuji played + by Benjamin GEIGER. + +1.5_1.1: Sun, 14 Mar 2004 14:38:28 -0500 + - Support upcoming pyblosxom 0.9 plugin verification feature. + +1.5_1.0: Tue, 09 Mar 2004 08:08:35 -0500 + - Initial release +""" + +import re +import sys + + +options = r""" +Options +======= + +Numeric values are the easiest way to configure SmartyPants' behavior: + +:0: Suppress all transformations. (Do nothing.) + +:1: Performs default SmartyPants transformations: quotes (including + \`\`backticks'' -style), em-dashes, and ellipses. "``--``" (dash dash) + is used to signify an em-dash; there is no support for en-dashes + +:2: Same as smarty_pants="1", except that it uses the old-school typewriter + shorthand for dashes: "``--``" (dash dash) for en-dashes, "``---``" + (dash dash dash) + for em-dashes. + +:3: Same as smarty_pants="2", but inverts the shorthand for dashes: + "``--``" (dash dash) for em-dashes, and "``---``" (dash dash dash) for + en-dashes. + +:-1: Stupefy mode. Reverses the SmartyPants transformation process, turning + the characters produced by SmartyPants into their ASCII equivalents. + E.g. the LEFT DOUBLE QUOTATION MARK (“) is turned into a simple + double-quote (\"), "—" is turned into two dashes, etc. + + +The following single-character attribute values can be combined to toggle +individual transformations from within the smarty_pants attribute. For +example, ``"1"`` is equivalent to ``"qBde"``. + +:q: Educates normal quote characters: (") and ('). + +:b: Educates \`\`backticks'' -style double quotes. + +:B: Educates \`\`backticks'' -style double quotes and \`single' quotes. + +:d: Educates em-dashes. + +:D: Educates em-dashes and en-dashes, using old-school typewriter + shorthand: (dash dash) for en-dashes, (dash dash dash) for em-dashes. + +:i: Educates em-dashes and en-dashes, using inverted old-school typewriter + shorthand: (dash dash) for em-dashes, (dash dash dash) for en-dashes. + +:e: Educates ellipses. + +:w: Translates any instance of ``"`` into a normal double-quote + character. This should be of no interest to most people, but + of particular interest to anyone who writes their posts using + Dreamweaver, as Dreamweaver inexplicably uses this entity to represent + a literal double-quote character. SmartyPants only educates normal + quotes, not entities (because ordinarily, entities are used for + the explicit purpose of representing the specific character they + represent). The "w" option must be used in conjunction with one (or + both) of the other quote options ("q" or "b"). Thus, if you wish to + apply all SmartyPants transformations (quotes, en- and em-dashes, and + ellipses) and also translate ``"`` entities into regular quotes + so SmartyPants can educate them, you should pass the following to the + smarty_pants attribute: +""" + + +class smartchars: + """Smart quotes and dashes""" + + endash = '–' # EN DASH + emdash = '—' # EM DASH + ellipsis = '…' # HORIZONTAL ELLIPSIS + apostrophe = '’' # RIGHT SINGLE QUOTATION MARK + + # quote characters (language-specific, set in __init__()) + # https://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks + # https://de.wikipedia.org/wiki/Anf%C3%BChrungszeichen#Andere_Sprachen + # https://fr.wikipedia.org/wiki/Guillemet + # https://typographisme.net/post/Les-espaces-typographiques-et-le-web + # https://www.btb.termiumplus.gc.ca/tpv2guides/guides/redac/index-fra.html + # https://en.wikipedia.org/wiki/Hebrew_punctuation#Quotation_marks + # [7] https://www.tustep.uni-tuebingen.de/bi/bi00/bi001t1-anfuehrung.pdf + # [8] https://www.korrekturavdelingen.no/anforselstegn.htm + # [9] Typografisk håndbok. Oslo: Spartacus. 2000. s. 67. ISBN 8243001530. + # [10] https://www.typografi.org/sitat/sitatart.html + # [11] https://mk.wikipedia.org/wiki/Правопис_и_правоговор_на_македонскиот_јазик # noqa:E501 + # [12] https://hrvatska-tipografija.com/polunavodnici/ + # [13] https://pl.wikipedia.org/wiki/Cudzys%C5%82%C3%B3w + # + # See also configuration option "smartquote-locales". + quotes = { + 'af': '“”‘’', + 'af-x-altquot': '„”‚’', + 'bg': '„“‚‘', # https://bg.wikipedia.org/wiki/Кавички + 'ca': '«»“”', + 'ca-x-altquot': '“”‘’', + 'cs': '„“‚‘', + 'cs-x-altquot': '»«›‹', + 'da': '»«›‹', + 'da-x-altquot': '„“‚‘', + # 'da-x-altquot2': '””’’', + 'de': '„“‚‘', + 'de-x-altquot': '»«›‹', + 'de-ch': '«»‹›', + 'el': '«»“”', # '«»‟”' https://hal.science/hal-02101618 + 'en': '“”‘’', + 'en-uk-x-altquot': '‘’“”', # Attention: " → ‘ and ' → “ ! + 'eo': '“”‘’', + 'es': '«»“”', + 'es-x-altquot': '“”‘’', + 'et': '„“‚‘', # no secondary quote listed in + 'et-x-altquot': '«»‹›', # the sources above (wikipedia.org) + 'eu': '«»‹›', + 'fi': '””’’', + 'fi-x-altquot': '»»››', + 'fr': ('« ', ' »', '“', '”'), # full no-break space + 'fr-x-altquot': ('« ', ' »', '“', '”'), # narrow no-break space + 'fr-ch': '«»‹›', # https://typoguide.ch/ + 'fr-ch-x-altquot': ('« ', ' »', '‹ ', ' ›'), # narrow no-break space # noqa:E501 + 'gl': '«»“”', + 'he': '”“»«', # Hebrew is RTL, test position: + 'he-x-altquot': '„”‚’', # low quotation marks are opening. + # 'he-x-altquot': '“„‘‚', # RTL: low quotation marks opening + 'hr': '„”‘’', # Croatian [12] + 'hr-x-altquot': '»«›‹', + 'hsb': '„“‚‘', + 'hsb-x-altquot': '»«›‹', + 'hu': '„”«»', + 'is': '„“‚‘', + 'it': '«»“”', + 'it-ch': '«»‹›', + 'it-x-altquot': '“”‘’', + # 'it-x-altquot2': '“„‘‚', # [7] in headlines + 'ja': '「」『』', + 'ko': '“”‘’', + 'lt': '„“‚‘', + 'lv': '„“‚‘', + 'mk': '„“‚‘', # Macedonian [11] + 'nl': '“”‘’', + 'nl-x-altquot': '„”‚’', + # 'nl-x-altquot2': '””’’', + 'nb': '«»’’', # Norsk bokmål (canonical form 'no') + 'nn': '«»’’', # Nynorsk [10] + 'nn-x-altquot': '«»‘’', # [8], [10] + # 'nn-x-altquot2': '«»«»', # [9], [10] + # 'nn-x-altquot3': '„“‚‘', # [10] + 'no': '«»’’', # Norsk bokmål [10] + 'no-x-altquot': '«»‘’', # [8], [10] + # 'no-x-altquot2': '«»«»', # [9], [10 + # 'no-x-altquot3': '„“‚‘', # [10] + 'pl': '„”«»', + 'pl-x-altquot': '«»‚’', + # 'pl-x-altquot2': '„”‚’', # [13] + 'pt': '«»“”', + 'pt-br': '“”‘’', + 'ro': '„”«»', + 'ru': '«»„“', + 'sh': '„”‚’', # Serbo-Croatian + 'sh-x-altquot': '»«›‹', + 'sk': '„“‚‘', # Slovak + 'sk-x-altquot': '»«›‹', + 'sl': '„“‚‘', # Slovenian + 'sl-x-altquot': '»«›‹', + 'sq': '«»‹›', # Albanian + 'sq-x-altquot': '“„‘‚', + 'sr': '„”’’', + 'sr-x-altquot': '»«›‹', + 'sv': '””’’', + 'sv-x-altquot': '»»››', + 'tr': '“”‘’', + 'tr-x-altquot': '«»‹›', + # 'tr-x-altquot2': '“„‘‚', # [7] antiquated? + 'uk': '«»„“', + 'uk-x-altquot': '„“‚‘', + 'zh-cn': '“”‘’', + 'zh-tw': '「」『』', + } + + def __init__(self, language='en'): + self.language = language + try: + (self.opquote, self.cpquote, + self.osquote, self.csquote) = self.quotes[language.lower()] + except KeyError: + self.opquote, self.cpquote, self.osquote, self.csquote = '""\'\'' + + +class RegularExpressions: + # character classes: + _CH_CLASSES = {'open': '[([{]', # opening braces + 'close': r'[^\s]', # everything except whitespace + 'punct': r"""[-!" #\$\%'()*+,.\/:;<=>?\@\[\\\]\^_`{|}~]""", + 'dash': r'[-–—]', + 'sep': '[\\s\u200B\u200C]', # Whitespace, ZWSP, ZWNJ + } + START_SINGLE = re.compile(r"^'(?=%s\\B)" % _CH_CLASSES['punct']) + START_DOUBLE = re.compile(r'^"(?=%s\\B)' % _CH_CLASSES['punct']) + ADJACENT_1 = re.compile('"\'(?=\\w)') + ADJACENT_2 = re.compile('\'"(?=\\w)') + OPEN_SINGLE = re.compile(r"(%(open)s|%(dash)s)'(?=%(punct)s? )" + % _CH_CLASSES) + OPEN_DOUBLE = re.compile(r'(%(open)s|%(dash)s)"(?=%(punct)s? )' + % _CH_CLASSES) + DECADE = re.compile(r"'(?=\d{2}s)") + APOSTROPHE = re.compile(r"(?<=(\w|\d))'(?=\w)") + OPENING_SECONDARY = re.compile(""" + (# ?<= # look behind fails: requires fixed-width pattern + %(sep)s | # a whitespace char, or + %(open)s | # opening brace, or + %(dash)s # em/en-dash + ) + ' # the quote + (?=\\w|%(punct)s) # word character or punctuation + """ % _CH_CLASSES, re.VERBOSE) + CLOSING_SECONDARY = re.compile(r"(?<!\s)'") + OPENING_PRIMARY = re.compile(""" + ( + %(sep)s | # a whitespace char, or + %(open)s | # zero width separating char, or + %(dash)s # em/en-dash + ) + " # the quote, followed by + (?=\\w|%(punct)s) # a word character or punctuation + """ % _CH_CLASSES, re.VERBOSE) + CLOSING_PRIMARY = re.compile(r""" + ( + (?<!\s)" | # no whitespace before + "(?=\s) # whitespace behind + ) + """, re.VERBOSE) + + +regexes = RegularExpressions() + + +default_smartypants_attr = '1' + + +def smartyPants(text, attr=default_smartypants_attr, language='en'): + """Main function for "traditional" use.""" + + return "".join(t for t in educate_tokens(tokenize(text), attr, language)) + + +def educate_tokens(text_tokens, attr=default_smartypants_attr, language='en'): + """Return iterator that "educates" the items of `text_tokens`.""" + # Parse attributes: + # 0 : do nothing + # 1 : set all + # 2 : set all, using old school en- and em- dash shortcuts + # 3 : set all, using inverted old school en and em- dash shortcuts + # + # q : quotes + # b : backtick quotes (``double'' only) + # B : backtick quotes (``double'' and `single') + # d : dashes + # D : old school dashes + # i : inverted old school dashes + # e : ellipses + # w : convert " entities to " for Dreamweaver users + + convert_quot = False # translate " entities into normal quotes? + do_dashes = False + do_backticks = False + do_quotes = False + do_ellipses = False + do_stupefy = False + + # if attr == "0": # pass tokens unchanged (see below). + if attr == '1': # Do everything, turn all options on. + do_quotes = True + do_backticks = True + do_dashes = 1 + do_ellipses = True + elif attr == '2': + # Do everything, turn all options on, use old school dash shorthand. + do_quotes = True + do_backticks = True + do_dashes = 2 + do_ellipses = True + elif attr == '3': + # Do everything, use inverted old school dash shorthand. + do_quotes = True + do_backticks = True + do_dashes = 3 + do_ellipses = True + elif attr == '-1': # Special "stupefy" mode. + do_stupefy = True + else: + if 'q' in attr: do_quotes = True # noqa: E701 + if 'b' in attr: do_backticks = True # noqa: E701 + if 'B' in attr: do_backticks = 2 # noqa: E701 + if 'd' in attr: do_dashes = 1 # noqa: E701 + if 'D' in attr: do_dashes = 2 # noqa: E701 + if 'i' in attr: do_dashes = 3 # noqa: E701 + if 'e' in attr: do_ellipses = True # noqa: E701 + if 'w' in attr: convert_quot = True # noqa: E701 + + prev_token_last_char = ' ' + # Last character of the previous text token. Used as + # context to curl leading quote characters correctly. + + for (ttype, text) in text_tokens: + + # skip HTML and/or XML tags as well as empty text tokens + # without updating the last character + if ttype == 'tag' or not text: + yield text + continue + + # skip literal text (math, literal, raw, ...) + if ttype == 'literal': + prev_token_last_char = text[-1:] + yield text + continue + + last_char = text[-1:] # Remember last char before processing. + + text = processEscapes(text) + + if convert_quot: + text = text.replace('"', '"') + + if do_dashes == 1: + text = educateDashes(text) + elif do_dashes == 2: + text = educateDashesOldSchool(text) + elif do_dashes == 3: + text = educateDashesOldSchoolInverted(text) + + if do_ellipses: + text = educateEllipses(text) + + # Note: backticks need to be processed before quotes. + if do_backticks: + text = educateBackticks(text, language) + + if do_backticks == 2: + text = educateSingleBackticks(text, language) + + if do_quotes: + # Replace plain quotes in context to prevent conversion to + # 2-character sequence in French. + context = prev_token_last_char.replace('"', ';').replace("'", ';') + text = educateQuotes(context+text, language)[1:] + + if do_stupefy: + text = stupefyEntities(text, language) + + # Remember last char as context for the next token + prev_token_last_char = last_char + + text = processEscapes(text, restore=True) + + yield text + + +def educateQuotes(text, language='en'): + """ + Parameter: - text string (unicode or bytes). + - language (`BCP 47` language tag.) + Returns: The `text`, with "educated" curly quote characters. + + Example input: "Isn't this fun?" + Example output: “Isn’t this fun?“ + """ + smart = smartchars(language) + + if not re.search('[-"\']', text): + return text + + # Special case if the very first character is a quote + # followed by punctuation at a non-word-break. Use closing quotes. + # TODO: example (when does this match?) + text = regexes.START_SINGLE.sub(smart.csquote, text) + text = regexes.START_DOUBLE.sub(smart.cpquote, text) + + # Special case for adjacent quotes + # like "'Quoted' words in a larger quote." + text = regexes.ADJACENT_1.sub(smart.opquote+smart.osquote, text) + text = regexes.ADJACENT_2.sub(smart.osquote+smart.opquote, text) + + # Special case: "opening character" followed by quote, + # optional punctuation and space like "[", '(', or '-'. + text = regexes.OPEN_SINGLE.sub(r'\1%s'%smart.csquote, text) + text = regexes.OPEN_DOUBLE.sub(r'\1%s'%smart.cpquote, text) + + # Special case for decade abbreviations (the '80s): + if language.startswith('en'): # TODO similar cases in other languages? + text = regexes.DECADE.sub(smart.apostrophe, text) + + # Get most opening secondary quotes: + text = regexes.OPENING_SECONDARY.sub(r'\1'+smart.osquote, text) + + # In many locales, secondary closing quotes are different from apostrophe: + if smart.csquote != smart.apostrophe: + text = regexes.APOSTROPHE.sub(smart.apostrophe, text) + # TODO: keep track of quoting level to recognize apostrophe in, e.g., + # "Ich fass' es nicht." + + text = regexes.CLOSING_SECONDARY.sub(smart.csquote, text) + + # Any remaining secondary quotes should be opening ones: + text = text.replace(r"'", smart.osquote) + + # Get most opening primary quotes: + text = regexes.OPENING_PRIMARY.sub(r'\1'+smart.opquote, text) + + # primary closing quotes: + text = regexes.CLOSING_PRIMARY.sub(smart.cpquote, text) + + # Any remaining quotes should be opening ones. + text = text.replace(r'"', smart.opquote) + + return text + + +def educateBackticks(text, language='en'): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with ``backticks'' -style double quotes + translated into HTML curly quote entities. + Example input: ``Isn't this fun?'' + Example output: “Isn't this fun?“ + """ + smart = smartchars(language) + + text = text.replace(r'``', smart.opquote) + text = text.replace(r"''", smart.cpquote) + return text + + +def educateSingleBackticks(text, language='en'): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with `backticks' -style single quotes + translated into HTML curly quote entities. + + Example input: `Isn't this fun?' + Example output: ‘Isn’t this fun?’ + """ + smart = smartchars(language) + + text = text.replace(r'`', smart.osquote) + text = text.replace(r"'", smart.csquote) + return text + + +def educateDashes(text): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with each instance of "--" translated to + an em-dash character. + """ + + text = text.replace(r'---', smartchars.endash) # en (yes, backwards) + text = text.replace(r'--', smartchars.emdash) # em (yes, backwards) + return text + + +def educateDashesOldSchool(text): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with each instance of "--" translated to + an en-dash character, and each "---" translated to + an em-dash character. + """ + + text = text.replace(r'---', smartchars.emdash) + text = text.replace(r'--', smartchars.endash) + return text + + +def educateDashesOldSchoolInverted(text): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with each instance of "--" translated to + an em-dash character, and each "---" translated to + an en-dash character. Two reasons why: First, unlike the + en- and em-dash syntax supported by + EducateDashesOldSchool(), it's compatible with existing + entries written before SmartyPants 1.1, back when "--" was + only used for em-dashes. Second, em-dashes are more + common than en-dashes, and so it sort of makes sense that + the shortcut should be shorter to type. (Thanks to Aaron + Swartz for the idea.) + """ + text = text.replace(r'---', smartchars.endash) # em + text = text.replace(r'--', smartchars.emdash) # en + return text + + +def educateEllipses(text): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with each instance of "..." translated to + an ellipsis character. + + Example input: Huh...? + Example output: Huh…? + """ + + text = text.replace(r'...', smartchars.ellipsis) + text = text.replace(r'. . .', smartchars.ellipsis) + return text + + +def stupefyEntities(text, language='en'): + """ + Parameter: String (unicode or bytes). + Returns: The `text`, with each SmartyPants character translated to + its ASCII counterpart. + + Example input: “Hello — world.” + Example output: "Hello -- world." + """ + smart = smartchars(language) + + text = text.replace(smart.endash, "-") + text = text.replace(smart.emdash, "--") + text = text.replace(smart.osquote, "'") # open secondary quote + text = text.replace(smart.csquote, "'") # close secondary quote + text = text.replace(smart.opquote, '"') # open primary quote + text = text.replace(smart.cpquote, '"') # close primary quote + text = text.replace(smart.ellipsis, '...') + + return text + + +def processEscapes(text, restore=False): + r""" + Parameter: String (unicode or bytes). + Returns: The `text`, with after processing the following backslash + escape sequences. This is useful if you want to force a "dumb" + quote or other character to appear. + + Escape Value + ------ ----- + \\ \ + \" " + \' ' + \. . + \- - + \` ` + """ + replacements = ((r'\\', r'\'), + (r'\"', r'"'), + (r"\'", r'''), + (r'\.', r'.'), + (r'\-', r'-'), + (r'\`', r'`')) + if restore: + for (ch, rep) in replacements: + text = text.replace(rep, ch[1]) + else: + for (ch, rep) in replacements: + text = text.replace(ch, rep) + + return text + + +def tokenize(text): + """ + Parameter: String containing HTML markup. + Returns: An iterator that yields the tokens comprising the input + string. Each token is either a tag (possibly with nested, + tags contained therein, such as <a href="<MTFoo>">, or a + run of text between tags. Each yielded element is a + two-element tuple; the first is either 'tag' or 'text'; + the second is the actual value. + + Based on the _tokenize() subroutine from Brad Choate's MTRegex plugin. + """ + tag_soup = re.compile(r'([^<]*)(<[^>]*>)') + token_match = tag_soup.search(text) + previous_end = 0 + + while token_match is not None: + if token_match.group(1): + yield 'text', token_match.group(1) + yield 'tag', token_match.group(2) + previous_end = token_match.end() + token_match = tag_soup.search(text, token_match.end()) + + if previous_end < len(text): + yield 'text', text[previous_end:] + + +if __name__ == "__main__": + + import itertools + import locale + try: + locale.setlocale(locale.LC_ALL, '') # set to user defaults + defaultlanguage = locale.getlocale()[0] + except: # noqa catchall + defaultlanguage = 'en' + + # Normalize and drop unsupported subtags: + defaultlanguage = defaultlanguage.lower().replace('-', '_') + # split (except singletons, which mark the following tag as non-standard): + defaultlanguage = re.sub(r'_([a-zA-Z0-9])_', r'_\1-', defaultlanguage) + _subtags = [subtag for subtag in defaultlanguage.split('_')] + _basetag = _subtags.pop(0) + # find all combinations of subtags + for n in range(len(_subtags), 0, -1): + for tags in itertools.combinations(_subtags, n): + _tag = '-'.join((_basetag, *tags)) + if _tag in smartchars.quotes: + defaultlanguage = _tag + break + else: + if _basetag in smartchars.quotes: + defaultlanguage = _basetag + else: + defaultlanguage = 'en' + + import argparse + parser = argparse.ArgumentParser( + description='Filter <input> making ASCII punctuation "smart".') + # TODO: require input arg or other means to print USAGE instead of waiting. + # parser.add_argument("input", help="Input stream, use '-' for stdin.") + parser.add_argument("-a", "--action", default="1", + help="what to do with the input (see --actionhelp)") + parser.add_argument("-e", "--encoding", default="utf-8", + help="text encoding") + parser.add_argument("-l", "--language", default=defaultlanguage, + help="text language (BCP47 tag), " + f"Default: {defaultlanguage}") + parser.add_argument("-q", "--alternative-quotes", action="store_true", + help="use alternative quote style") + parser.add_argument("--doc", action="store_true", + help="print documentation") + parser.add_argument("--actionhelp", action="store_true", + help="list available actions") + parser.add_argument("--stylehelp", action="store_true", + help="list available quote styles") + parser.add_argument("--test", action="store_true", + help="perform short self-test") + args = parser.parse_args() + + if args.doc: + print(__doc__) + elif args.actionhelp: + print(options) + elif args.stylehelp: + print() + print("Available styles (primary open/close, secondary open/close)") + print("language tag quotes") + print("============ ======") + for key in sorted(smartchars.quotes.keys()): + print("%-14s %s" % (key, smartchars.quotes[key])) + elif args.test: + # Unit test output goes to stderr. + import unittest + + class TestSmartypantsAllAttributes(unittest.TestCase): + # the default attribute is "1", which means "all". + def test_dates(self): + self.assertEqual(smartyPants("1440-80's"), "1440-80’s") + self.assertEqual(smartyPants("1440-'80s"), "1440-’80s") + self.assertEqual(smartyPants("1440---'80s"), "1440–’80s") + self.assertEqual(smartyPants("1960's"), "1960’s") + self.assertEqual(smartyPants("one two '60s"), "one two ’60s") + self.assertEqual(smartyPants("'60s"), "’60s") + + def test_educated_quotes(self): + self.assertEqual(smartyPants('"Isn\'t this fun?"'), + '“Isn’t this fun?”') + + def test_html_tags(self): + text = '<a src="foo">more</a>' + self.assertEqual(smartyPants(text), text) + + suite = unittest.TestLoader().loadTestsFromTestCase( + TestSmartypantsAllAttributes) + unittest.TextTestRunner().run(suite) + + else: + if args.alternative_quotes: + if '-x-altquot' in args.language: + args.language = args.language.replace('-x-altquot', '') + else: + args.language += '-x-altquot' + text = sys.stdin.read() + print(smartyPants(text, attr=args.action, language=args.language)) diff --git a/.venv/lib/python3.12/site-packages/docutils/utils/urischemes.py b/.venv/lib/python3.12/site-packages/docutils/utils/urischemes.py new file mode 100644 index 00000000..a0435c02 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/docutils/utils/urischemes.py @@ -0,0 +1,138 @@ +# $Id: urischemes.py 9315 2023-01-15 19:27:55Z milde $ +# Author: David Goodger <goodger@python.org> +# Copyright: This module has been placed in the public domain. + +""" +`schemes` is a dictionary with lowercase URI addressing schemes as +keys and descriptions as values. It was compiled from the index at +http://www.iana.org/assignments/uri-schemes (revised 2005-11-28) +and an older list at https://www.w3.org/Addressing/schemes.html. +""" + +# Many values are blank and should be filled in with useful descriptions. + +schemes = { + 'about': 'provides information on Navigator', + 'acap': 'Application Configuration Access Protocol; RFC 2244', + 'addbook': "To add vCard entries to Communicator's Address Book", + 'afp': 'Apple Filing Protocol', + 'afs': 'Andrew File System global file names', + 'aim': 'AOL Instant Messenger', + 'callto': 'for NetMeeting links', + 'castanet': 'Castanet Tuner URLs for Netcaster', + 'chttp': 'cached HTTP supported by RealPlayer', + 'cid': 'content identifier; RFC 2392', + 'crid': 'TV-Anytime Content Reference Identifier; RFC 4078', + 'data': 'allows inclusion of small data items as "immediate" data; ' + 'RFC 2397', + 'dav': 'Distributed Authoring and Versioning Protocol; RFC 2518', + 'dict': 'dictionary service protocol; RFC 2229', + 'dns': 'Domain Name System resources', + 'eid': 'External ID; non-URL data; general escape mechanism to allow ' + 'access to information for applications that are too ' + 'specialized to justify their own schemes', + 'fax': 'a connection to a terminal that can handle telefaxes ' + '(facsimiles); RFC 2806', + 'feed': 'NetNewsWire feed', + 'file': 'Host-specific file names; RFC 1738', + 'finger': '', + 'freenet': '', + 'ftp': 'File Transfer Protocol; RFC 1738', + 'go': 'go; RFC 3368', + 'gopher': 'The Gopher Protocol', + 'gsm-sms': 'Global System for Mobile Communications Short Message ' + 'Service', + 'h323': 'video (audiovisual) communication on local area networks; ' + 'RFC 3508', + 'h324': 'video and audio communications over low bitrate connections ' + 'such as POTS modem connections', + 'hdl': 'CNRI handle system', + 'hnews': 'an HTTP-tunneling variant of the NNTP news protocol', + 'http': 'Hypertext Transfer Protocol; RFC 2616', + 'https': 'HTTP over SSL; RFC 2818', + 'hydra': 'SubEthaEdit URI. ' + 'See http://www.codingmonkeys.de/subethaedit.', + 'iioploc': 'Internet Inter-ORB Protocol Location?', + 'ilu': 'Inter-Language Unification', + 'im': 'Instant Messaging; RFC 3860', + 'imap': 'Internet Message Access Protocol; RFC 2192', + 'info': 'Information Assets with Identifiers in Public Namespaces', + 'ior': 'CORBA interoperable object reference', + 'ipp': 'Internet Printing Protocol; RFC 3510', + 'irc': 'Internet Relay Chat', + 'iris.beep': 'iris.beep; RFC 3983', + 'iseek': 'See www.ambrosiasw.com; a little util for OS X.', + 'jar': 'Java archive', + 'javascript': 'JavaScript code; ' + 'evaluates the expression after the colon', + 'jdbc': 'JDBC connection URI.', + 'ldap': 'Lightweight Directory Access Protocol', + 'lifn': '', + 'livescript': '', + 'lrq': '', + 'mailbox': 'Mail folder access', + 'mailserver': 'Access to data available from mail servers', + 'mailto': 'Electronic mail address; RFC 2368', + 'md5': '', + 'mid': 'message identifier; RFC 2392', + 'mocha': '', + 'modem': 'a connection to a terminal that can handle incoming data ' + 'calls; RFC 2806', + 'mtqp': 'Message Tracking Query Protocol; RFC 3887', + 'mupdate': 'Mailbox Update (MUPDATE) Protocol; RFC 3656', + 'news': 'USENET news; RFC 1738', + 'nfs': 'Network File System protocol; RFC 2224', + 'nntp': 'USENET news using NNTP access; RFC 1738', + 'opaquelocktoken': 'RFC 2518', + 'phone': '', + 'pop': 'Post Office Protocol; RFC 2384', + 'pop3': 'Post Office Protocol v3', + 'pres': 'Presence; RFC 3859', + 'printer': '', + 'prospero': 'Prospero Directory Service; RFC 4157', + 'rdar': 'URLs found in Darwin source ' + '(http://www.opensource.apple.com/darwinsource/).', + 'res': '', + 'rtsp': 'real time streaming protocol; RFC 2326', + 'rvp': '', + 'rwhois': '', + 'rx': 'Remote Execution', + 'sdp': '', + 'service': 'service location; RFC 2609', + 'shttp': 'secure hypertext transfer protocol', + 'sip': 'Session Initiation Protocol; RFC 3261', + 'sips': 'secure session intitiaion protocol; RFC 3261', + 'smb': 'SAMBA filesystems.', + 'snews': 'For NNTP postings via SSL', + 'snmp': 'Simple Network Management Protocol; RFC 4088', + 'soap.beep': 'RFC 3288', + 'soap.beeps': 'RFC 3288', + 'ssh': 'Reference to interactive sessions via ssh.', + 't120': 'real time data conferencing (audiographics)', + 'tag': 'RFC 4151', + 'tcp': '', + 'tel': 'a connection to a terminal that handles normal voice ' + 'telephone calls, a voice mailbox or another voice messaging ' + 'system or a service that can be operated using DTMF tones; ' + 'RFC 3966.', + 'telephone': 'telephone', + 'telnet': 'Reference to interactive sessions; RFC 4248', + 'tftp': 'Trivial File Transfer Protocol; RFC 3617', + 'tip': 'Transaction Internet Protocol; RFC 2371', + 'tn3270': 'Interactive 3270 emulation sessions', + 'tv': '', + 'urn': 'Uniform Resource Name; RFC 2141', + 'uuid': '', + 'vemmi': 'versatile multimedia interface; RFC 2122', + 'videotex': '', + 'view-source': 'displays HTML code that was generated with JavaScript', + 'wais': 'Wide Area Information Servers; RFC 4156', + 'whodp': '', + 'whois++': 'Distributed directory service.', + 'x-man-page': 'Opens man page in Terminal.app on OS X ' + '(see macosxhints.com)', + 'xmlrpc.beep': 'RFC 3529', + 'xmlrpc.beeps': 'RFC 3529', + 'z39.50r': 'Z39.50 Retrieval; RFC 2056', + 'z39.50s': 'Z39.50 Session; RFC 2056', + } 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) |