This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository jrst. See https://gitlab.nuiton.org/nuiton/jrst.git commit 90542566a385d93aea25dfdbb7e0e015fe7bc7d8 Author: Eric Chatellier <chatellier@codelutin.com> Date: Fri Jul 8 14:57:04 2022 +0200 #119: Update docutils to 0.18.1 --- .../main/resources/docutils/docutils/__init__.py | 113 +- .../main/resources/docutils/docutils/_compat.py | 48 - .../src/main/resources/docutils/docutils/core.py | 47 +- .../main/resources/docutils/docutils/frontend.py | 89 +- .../src/main/resources/docutils/docutils/io.py | 136 +- .../docutils/docutils/languages/__init__.py | 87 +- .../resources/docutils/docutils/languages/ar.py | 61 + .../resources/docutils/docutils/languages/ko.py | 61 + .../src/main/resources/docutils/docutils/nodes.py | 503 ++- .../docutils/docutils/parsers/__init__.py | 48 +- .../docutils/parsers/recommonmark_wrapper.py | 163 + .../docutils/docutils/parsers/rst/__init__.py | 41 +- .../docutils/parsers/rst/directives/__init__.py | 40 +- .../docutils/parsers/rst/directives/body.py | 45 +- .../docutils/parsers/rst/directives/html.py | 87 +- .../docutils/parsers/rst/directives/images.py | 17 +- .../docutils/parsers/rst/directives/misc.py | 160 +- .../docutils/parsers/rst/directives/tables.py | 67 +- .../docutils/parsers/rst/include/README.txt | 4 +- .../docutils/parsers/rst/languages/__init__.py | 46 +- .../docutils/docutils/parsers/rst/languages/ar.py | 102 + .../docutils/docutils/parsers/rst/languages/ko.py | 111 + .../docutils/docutils/parsers/rst/roles.py | 94 +- .../docutils/docutils/parsers/rst/states.py | 192 +- .../docutils/docutils/parsers/rst/tableparser.py | 12 +- .../docutils/docutils/readers/__init__.py | 14 +- .../docutils/docutils/readers/standalone.py | 4 +- .../resources/docutils/docutils/statemachine.py | 135 +- .../docutils/docutils/transforms/__init__.py | 14 +- .../docutils/docutils/transforms/components.py | 26 +- .../docutils/docutils/transforms/frontmatter.py | 93 +- .../resources/docutils/docutils/transforms/misc.py | 4 +- .../docutils/docutils/transforms/parts.py | 20 +- .../resources/docutils/docutils/transforms/peps.py | 11 +- .../docutils/docutils/transforms/references.py | 250 +- .../docutils/docutils/transforms/universal.py | 97 +- .../docutils/docutils/transforms/writer_aux.py | 6 +- .../resources/docutils/docutils/utils/__init__.py | 118 +- .../docutils/docutils/utils/code_analyzer.py | 25 +- .../docutils/docutils/utils/error_reporting.py | 27 +- .../docutils/docutils/utils/math/__init__.py | 18 +- .../docutils/docutils/utils/math/latex2mathml.py | 1778 +++++++--- .../docutils/docutils/utils/math/math2html.py | 3558 ++++---------------- .../docutils/utils/math/tex2mathml_extern.py | 44 +- .../docutils/docutils/utils/math/tex2unichar.py | 186 +- .../docutils/docutils/utils/math/unichar2tex.py | 1578 ++++----- .../docutils/docutils/utils/punctuation_chars.py | 4 +- .../resources/docutils/docutils/utils/roman.py | 11 +- .../docutils/docutils/utils/smartquotes.py | 198 +- .../docutils/docutils/utils/urischemes.py | 8 +- .../docutils/docutils/writers/__init__.py | 28 +- .../docutils/docutils/writers/_html_base.py | 598 ++-- .../docutils/docutils/writers/docutils_xml.py | 33 +- .../docutils/writers/html4css1/__init__.py | 319 +- .../docutils/writers/html5_polyglot/__init__.py | 436 ++- .../docutils/writers/html5_polyglot/math.css | 160 +- .../docutils/writers/html5_polyglot/minimal.css | 299 +- .../docutils/writers/html5_polyglot/plain.css | 278 +- .../docutils/writers/html5_polyglot/responsive.css | 488 +++ .../docutils/writers/html5_polyglot/tuftig.css | 554 +++ .../docutils/docutils/writers/latex2e/__init__.py | 1763 +++++----- .../docutils/docutils/writers/latex2e/default.tex | 6 +- .../writers/latex2e/docutils-05-compat.sty | 738 ---- .../docutils/docutils/writers/latex2e/docutils.sty | 223 ++ .../latex2e/{default.tex => titlingpage.tex} | 10 +- .../resources/docutils/docutils/writers/manpage.py | 135 +- .../docutils/docutils/writers/odf_odt/__init__.py | 1402 ++++---- .../docutils/docutils/writers/pep_html/__init__.py | 9 +- .../docutils/docutils/writers/pep_html/pep.css | 2 +- .../docutils/docutils/writers/pseudoxml.py | 12 +- .../docutils/docutils/writers/s5_html/__init__.py | 14 +- .../docutils/writers/s5_html/themes/README.txt | 2 +- .../writers/s5_html/themes/default/blank.gif | Bin 49 -> 0 bytes .../writers/s5_html/themes/default/iepngfix.htc | 42 - .../docutils/docutils/writers/xetex/__init__.py | 39 +- 75 files changed, 9509 insertions(+), 8682 deletions(-) diff --git a/docutils/src/main/resources/docutils/docutils/__init__.py b/docutils/src/main/resources/docutils/docutils/__init__.py index f4a81e3..3262a4c 100644 --- a/docutils/src/main/resources/docutils/docutils/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 8147 2017-08-03 09:01:16Z grubert $ +# $Id: __init__.py 8900 2021-11-23 17:44:14Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -51,78 +51,88 @@ Subpackages: """ import sys +from collections import namedtuple __docformat__ = 'reStructuredText' -__version__ = '0.14' +__version__ = '0.18.1' """Docutils version identifier (complies with PEP 440):: major.minor[.micro][releaselevel[serial]][.dev] -* The major number will be bumped when the project is feature-complete, and - later if there is a major change in the design or API. -* The minor number is bumped whenever there are new features. -* The micro number is bumped for bug-fix releases. Omitted if micro=0. -* The releaselevel identifier is used for pre-releases, one of 'a' (alpha), - 'b' (beta), or 'rc' (release candidate). Omitted for final releases. -* The serial release number identifies prereleases; omitted if 0. -* The '.dev' suffix indicates active development, not a release, before the - version indicated. - -For version comparison operations, use `__version_info__` +For version comparison operations, use `__version_info__` (see, below) rather than parsing the text of `__version__`. -""" -# workaround for Python < 2.6: -__version_info__ = (0, 14, 0, 'final', 0, True) -# To add in Docutils 0.15, replacing the line above: +See 'Version Numbering' in docs/dev/policies.txt. """ -from collections import namedtuple -VersionInfo = namedtuple( - 'VersionInfo', 'major minor micro releaselevel serial release') + +# from functools import total_ordering +# @total_ordering +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__') + if serial != 0: + raise ValueError('"serial" must be 0 for final releases') + + return super(VersionInfo, cls).__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=15, - micro=0, - releaselevel='alpha', # development status: - # one of 'alpha', 'beta', 'candidate', 'final' - serial=0, # pre-release number (0 for final releases) - release=False # True for official releases and pre-releases + minor=18, + micro=1, + releaselevel='final', # one of 'alpha', 'beta', 'candidate', 'final' + # pre-release serial number (0 for final releases and active development): + serial=0, + release=True # True for official releases and pre-releases ) +"""Comprehensive version information tuple. See 'Version Numbering' in +docs/dev/policies.txt.""" -Comprehensive version information tuple. Can be used to test for a -minimally required version, e.g. :: - - if __version_info__ >= (0, 13, 0, 'candidate', 2, True) - -or in a self-documenting way like :: - - if __version_info__ >= docutils.VersionInfo( - major=0, minor=13, micro=0, - releaselevel='candidate', serial=2, release=True) -""" - -__version_details__ = '' +__version_details__ = 'release' """Optional extra version details (e.g. 'snapshot 2005-05-29, r3410'). (For development and release status see `__version_info__`.) """ -class ApplicationError(StandardError): - # Workaround: - # In Python < 2.6, unicode(<exception instance>) calls `str` on the - # arg and therefore, e.g., unicode(StandardError(u'\u234')) fails - # with UnicodeDecodeError. - if sys.version_info < (2,6): - def __unicode__(self): - return u', '.join(self.args) - +class ApplicationError(Exception): pass class DataError(ApplicationError): pass -class SettingsSpec: +class SettingsSpec(object): """ Runtime setting specification base class. @@ -207,7 +217,8 @@ class TransformSpec: """Transforms required by this class. Override in subclasses.""" if self.default_transforms != (): import warnings - warnings.warn('default_transforms attribute deprecated.\n' + warnings.warn('TransformSpec: the "default_transforms" attribute ' + 'will be removed in Docutils 1.1.\n' 'Use get_transforms() method instead.', DeprecationWarning) return list(self.default_transforms) @@ -220,7 +231,7 @@ class TransformSpec: """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.tranforms.references` are unable to find a correct target. The + `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:: diff --git a/docutils/src/main/resources/docutils/docutils/_compat.py b/docutils/src/main/resources/docutils/docutils/_compat.py deleted file mode 100644 index 08cf511..0000000 --- a/docutils/src/main/resources/docutils/docutils/_compat.py +++ /dev/null @@ -1,48 +0,0 @@ -# $Id: _compat.py 7486 2012-07-11 12:25:14Z milde $ -# Author: Georg Brandl <georg@python.org> -# Copyright: This module has been placed in the public domain. - -""" -Python 2/3 compatibility definitions. - -This module currently provides the following helper symbols: - -* bytes (name of byte string type; str in 2.x, bytes in 3.x) -* b (function converting a string literal to an ASCII byte string; - can be also used to convert a Unicode string into a byte string) -* u_prefix (unicode repr prefix: 'u' in 2.x, '' in 3.x) - (Required in docutils/test/test_publisher.py) -* BytesIO (a StringIO class that works with bytestrings) -""" - -import sys - -if sys.version_info < (3,0): - b = bytes = str - u_prefix = 'u' - from StringIO import StringIO as BytesIO -else: - import builtins - bytes = builtins.bytes - u_prefix = '' - def b(s): - if isinstance(s, str): - return s.encode('latin1') - elif isinstance(s, bytes): - return s - else: - raise TypeError("Invalid argument %r for b()" % (s,)) - # using this hack since 2to3 "fixes" the relative import - # when using ``from io import BytesIO`` - BytesIO = __import__('io').BytesIO - -if sys.version_info < (2,5): - import __builtin__ - - def __import__(name, globals={}, locals={}, fromlist=[], level=-1): - """Compatibility definition for Python 2.4. - - Silently ignore the `level` argument missing in Python < 2.5. - """ - # we need the level arg because the default changed in Python 3.3 - return __builtin__.__import__(name, globals, locals, fromlist) diff --git a/docutils/src/main/resources/docutils/docutils/core.py b/docutils/src/main/resources/docutils/docutils/core.py index 3dc12e8..940391b 100644 --- a/docutils/src/main/resources/docutils/docutils/core.py +++ b/docutils/src/main/resources/docutils/docutils/core.py @@ -1,4 +1,4 @@ -# $Id: core.py 8126 2017-06-23 09:34:28Z milde $ +# $Id: core.py 8367 2019-08-27 12:09:56Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -11,6 +11,7 @@ custom component objects first, and pass *them* to .. _The Docutils Publisher: http://docutils.sf.net/docs/api/publisher.html """ +from __future__ import print_function __docformat__ = 'reStructuredText' @@ -23,7 +24,7 @@ from docutils.transforms import Transformer from docutils.utils.error_reporting import ErrorOutput, ErrorString import docutils.readers.doctree -class Publisher: +class Publisher(object): """ A facade encapsulating the high-level logic of a Docutils system. @@ -154,7 +155,7 @@ class Publisher: if argv is None: argv = sys.argv[1:] # converting to Unicode (Python 3 does this automatically): - if sys.version_info < (3,0): + if sys.version_info < (3, 0): # TODO: make this failsafe and reversible? argv_encoding = (frontend.locale_encoding or 'ascii') argv = [a.decode(argv_encoding) for a in argv] @@ -218,10 +219,10 @@ class Publisher: self.apply_transforms() output = self.writer.write(self.document, self.destination) self.writer.assemble_parts() - except SystemExit, error: + except SystemExit as error: exit = 1 exit_status = error.code - except Exception, error: + except Exception as error: if not self.settings: # exception too early to report nicely raise if self.settings.traceback: # Propagate exceptions? @@ -243,24 +244,24 @@ class Publisher: if not self.document: return if self.settings.dump_settings: - print >>self._stderr, '\n::: Runtime settings:' - print >>self._stderr, pprint.pformat(self.settings.__dict__) + print('\n::: Runtime settings:', file=self._stderr) + print(pprint.pformat(self.settings.__dict__), file=self._stderr) if self.settings.dump_internals: - print >>self._stderr, '\n::: Document internals:' - print >>self._stderr, pprint.pformat(self.document.__dict__) + print('\n::: Document internals:', file=self._stderr) + print(pprint.pformat(self.document.__dict__), file=self._stderr) if self.settings.dump_transforms: - print >>self._stderr, '\n::: Transforms applied:' - print >>self._stderr, (' (priority, transform class, ' - 'pending node details, keyword args)') - print >>self._stderr, pprint.pformat( + 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]) + in self.document.transformer.applied]), file=self._stderr) if self.settings.dump_pseudo_xml: - print >>self._stderr, '\n::: Pseudo-XML:' - print >>self._stderr, self.document.pformat().encode( - 'raw_unicode_escape') + print('\n::: Pseudo-XML:', file=self._stderr) + print(self.document.pformat().encode( + 'raw_unicode_escape'), file=self._stderr) def report_Exception(self, error): if isinstance(error, utils.SystemMessage): @@ -275,8 +276,8 @@ class Publisher: u'Unable to open destination file for writing:\n' u' %s\n' % ErrorString(error)) else: - print >>self._stderr, u'%s' % ErrorString(error) - print >>self._stderr, ("""\ + print(u'%s' % ErrorString(error), file=self._stderr) + print(("""\ Exiting due to error. Use "--traceback" to diagnose. Please report errors to <docutils-users@lists.sf.net>. Include "--traceback" output, Docutils version (%s%s), @@ -284,12 +285,12 @@ Python version (%s), your OS type & version, and the command line used.""" % (__version__, docutils.__version_details__ and ' [%s]'%docutils.__version_details__ or '', - sys.version.split()[0])) + sys.version.split()[0])), file=self._stderr) def report_SystemMessage(self, error): - print >>self._stderr, ('Exiting due to level-%s (%s) system message.' - % (error.level, - utils.Reporter.levels[error.level])) + 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] diff --git a/docutils/src/main/resources/docutils/docutils/frontend.py b/docutils/src/main/resources/docutils/docutils/frontend.py index 1aeae5c..9b5ff69 100644 --- a/docutils/src/main/resources/docutils/docutils/frontend.py +++ b/docutils/src/main/resources/docutils/docutils/frontend.py @@ -1,4 +1,4 @@ -# $Id: frontend.py 8126 2017-06-23 09:34:28Z milde $ +# $Id: frontend.py 8880 2021-11-05 11:11:18Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -20,8 +20,8 @@ Also exports the following functions: `validate_encoding_error_handler`, `validate_encoding_and_error_handler`, `validate_boolean`, `validate_ternary`, `validate_threshold`, - `validate_colon_separated_string_list`, - `validate_comma_separated_string_list`, + `validate_colon_separated_list`, + `validate_comma_separated_list`, `validate_dependency_file`. * `make_paths_absolute`. * SettingSpec manipulation: `filter_settings_spec`. @@ -33,16 +33,25 @@ import os import os.path import sys import warnings -import ConfigParser as CP import codecs import optparse from optparse import SUPPRESS_HELP +if sys.version_info >= (3, 0): + from configparser import RawConfigParser + from os import getcwd +else: + from ConfigParser import RawConfigParser + from os import getcwdu as getcwd + import docutils import docutils.utils import docutils.nodes from docutils.utils.error_reporting import (locale_encoding, SafeString, ErrorOutput, ErrorString) +if sys.version_info >= (3, 0): + unicode = str # noqa + def store_multiple(option, opt, value, parser, *args, **kwargs): """ @@ -62,7 +71,7 @@ def read_config_file(option, opt, value, parser): """ try: new_settings = parser.get_config_file_settings(value) - except ValueError, error: + except ValueError as error: parser.error(error) parser.values.update(new_settings, parser) @@ -71,9 +80,8 @@ def validate_encoding(setting, value, option_parser, try: codecs.lookup(value) except LookupError: - raise (LookupError('setting "%s": unknown encoding: "%s"' - % (setting, value)), - None, sys.exc_info()[2]) + raise LookupError('setting "%s": unknown encoding: "%s"' + % (setting, value)) return value def validate_encoding_error_handler(setting, value, option_parser, @@ -81,12 +89,11 @@ def validate_encoding_error_handler(setting, value, option_parser, try: codecs.lookup_error(value) except LookupError: - raise (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), - None, sys.exc_info()[2]) + 'the Python ``codecs`` module)' % value) return value def validate_encoding_and_error_handler( @@ -122,8 +129,7 @@ def validate_boolean(setting, value, option_parser, try: return option_parser.booleans[value.strip().lower()] except KeyError: - raise (LookupError('unknown boolean value: "%s"' % value), - None, sys.exc_info()[2]) + raise LookupError('unknown boolean value: "%s"' % value) def validate_ternary(setting, value, option_parser, config_parser=None, config_section=None): @@ -154,8 +160,7 @@ def validate_threshold(setting, value, option_parser, try: return option_parser.thresholds[value.lower()] except (KeyError, AttributeError): - raise (LookupError('unknown threshold: %r.' % value), - None, sys.exc_info[2]) + raise LookupError('unknown threshold: %r.' % value) def validate_colon_separated_string_list( setting, value, option_parser, config_parser=None, config_section=None): @@ -170,8 +175,8 @@ def validate_comma_separated_list(setting, value, option_parser, config_parser=None, config_section=None): """Check/normalize list arguments (split at "," and strip whitespace). """ - # `value` is already a ``list`` when given as command line option - # and "action" is "append" and ``unicode`` or ``str`` else. + # `value` may be ``unicode``, ``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` @@ -242,7 +247,7 @@ def validate_smartquotes_locales(setting, value, option_parser, 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)) + lc_quotes.append((lang, quotes)) return lc_quotes def make_paths_absolute(pathdict, keys, base_path=None): @@ -253,7 +258,7 @@ def make_paths_absolute(pathdict, keys, base_path=None): `OptionParser.relative_path_settings`. """ if base_path is None: - base_path = os.getcwdu() # type(base_path) == unicode + base_path = getcwd() # type(base_path) == unicode # to allow combining non-ASCII cwd with unicode values in `pathdict` for key in keys: if key in pathdict: @@ -328,6 +333,13 @@ class Values(optparse.Values): """Return a shallow copy of `self`.""" return self.__class__(defaults=self.__dict__) + def setdefault(self, name, default): + """V.setdefault(n[,d]) -> getattr(V,n,d), also set D.n=d if n not in D or None. + """ + if getattr(self, name, None) is None: + setattr(self, name, default) + return getattr(self, name) + class Option(optparse.Option): @@ -346,11 +358,10 @@ class Option(optparse.Option): value = getattr(values, setting) try: new_value = self.validator(setting, value, parser) - except Exception, error: - raise (optparse.OptionValueError( + except Exception as error: + raise optparse.OptionValueError( 'Error in option "%s":\n %s' - % (opt, ErrorString(error))), - None, sys.exc_info()[2]) + % (opt, ErrorString(error))) setattr(values, setting, new_value) if self.overrides: setattr(values, self.overrides, None) @@ -545,7 +556,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): ['--help', '-h'], {'action': 'help'}), # Typically not useful for non-programmatical use: (SUPPRESS_HELP, ['--id-prefix'], {'default': ''}), - (SUPPRESS_HELP, ['--auto-id-prefix'], {'default': 'id'}), + (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'}), @@ -605,7 +616,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): if read_config_files and not self.defaults['_disable_config']: try: config_settings = self.get_standard_config_settings() - except ValueError, error: + except ValueError as error: self.error(SafeString(error)) self.set_defaults_from_dict(config_settings.__dict__) @@ -633,7 +644,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): option = group.add_option(help=help_text, *option_strings, **kwargs) if kwargs.get('action') == 'append': - self.lists[option.dest] = 1 + self.lists[option.dest] = True if component.settings_defaults: self.defaults.update(component.settings_defaults) for component in components: @@ -672,7 +683,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): parser.read(config_file, self) self.config_files.extend(parser._files) base_path = os.path.dirname(config_file) - applied = {} + applied = set() settings = Values() for component in self.components: if not component: @@ -681,7 +692,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): + (component.config_section,)): if section in applied: continue - applied[section] = 1 + applied.add(section) settings.update(parser.get_section(section), self) make_paths_absolute( settings.__dict__, self.relative_path_settings, base_path) @@ -737,7 +748,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): raise KeyError('No option with dest == %r.' % dest) -class ConfigParser(CP.RawConfigParser): +class ConfigParser(RawConfigParser): old_settings = { 'pep_stylesheet': ('pep_html writer', 'stylesheet'), @@ -748,7 +759,7 @@ class ConfigParser(CP.RawConfigParser): old_warning = """ The "[option]" section is deprecated. Support for old-format configuration -files may be removed in a future Docutils release. Please revise your +files will be removed in a future Docutils release. Please revise your configuration files. See <http://docutils.sf.net/docs/user/config.html>, section "Old-Format Configuration Files". """ @@ -759,7 +770,7 @@ Skipping "%s" configuration file. """ def __init__(self, *args, **kwargs): - CP.RawConfigParser.__init__(self, *args, **kwargs) + RawConfigParser.__init__(self, *args, **kwargs) self._files = [] """List of paths of configuration files read.""" @@ -772,15 +783,15 @@ Skipping "%s" configuration file. filenames = [filenames] for filename in filenames: try: - # Config files must be UTF-8-encoded: + # Config files are UTF-8-encoded: fp = codecs.open(filename, 'r', 'utf-8') except IOError: continue try: - if sys.version_info < (3,2): - CP.RawConfigParser.readfp(self, fp, filename) + if sys.version_info < (3, 0): + RawConfigParser.readfp(self, fp, filename) else: - CP.RawConfigParser.read_file(self, fp, filename) + RawConfigParser.read_file(self, fp, filename) except UnicodeDecodeError: self._stderr.write(self.not_utf8_error % (filename, filename)) fp.close() @@ -826,13 +837,13 @@ Skipping "%s" configuration file. new_value = option.validator( setting, value, option_parser, config_parser=self, config_section=section) - except Exception, error: - raise (ValueError( + except Exception as error: + raise ValueError( 'Error in config file "%s", section "[%s]":\n' ' %s\n' ' %s = %s' % (filename, section, ErrorString(error), - setting, value)), None, sys.exc_info()[2]) + setting, value)) self.set(section, setting, new_value) if option.overrides: self.set(section, option.overrides, None) @@ -855,5 +866,5 @@ Skipping "%s" configuration file. return section_dict -class ConfigDeprecationWarning(DeprecationWarning): +class ConfigDeprecationWarning(FutureWarning): """Warning for deprecated configuration file features.""" diff --git a/docutils/src/main/resources/docutils/docutils/io.py b/docutils/src/main/resources/docutils/docutils/io.py index 8428318..5967f1b 100644 --- a/docutils/src/main/resources/docutils/docutils/io.py +++ b/docutils/src/main/resources/docutils/docutils/io.py @@ -1,4 +1,4 @@ -# $Id: io.py 8129 2017-06-27 14:55:22Z grubert $ +# $Id: io.py 8880 2021-11-05 11:11:18Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -6,17 +6,21 @@ I/O classes provide a uniform API for low-level input and output. Subclasses exist for a variety of input/output mechanisms. """ +from __future__ import print_function __docformat__ = 'reStructuredText' -import sys +import codecs import os import re -import codecs +import sys +import warnings + from docutils import TransformSpec -from docutils._compat import b from docutils.utils.error_reporting import locale_encoding, ErrorString, ErrorOutput +if sys.version_info >= (3, 0): + unicode = str # noqa class InputError(IOError): pass class OutputError(IOError): pass @@ -114,7 +118,7 @@ class Input(TransformSpec): self.successful_encoding = enc # Return decoded, removing BOMs. return decoded.replace(u'\ufeff', u'') - except (UnicodeError, LookupError), err: + except (UnicodeError, LookupError) as err: error = err # in Python 3, the <exception instance> is # local to the except clause raise UnicodeError( @@ -122,10 +126,10 @@ class Input(TransformSpec): '%s.\n(%s)' % (', '.join([repr(enc) for enc in encodings]), ErrorString(error))) - coding_slug = re.compile(b(r"coding[:=]\s*([-\w.]+)")) + coding_slug = re.compile(br"coding[:=]\s*([-\w.]+)") """Encoding declaration pattern.""" - byte_order_marks = ((codecs.BOM_UTF8, 'utf-8'), # 'utf-8-sig' new in v2.5 + byte_order_marks = ((codecs.BOM_UTF8, 'utf-8'), (codecs.BOM_UTF16_BE, 'utf-16-be'), (codecs.BOM_UTF16_LE, 'utf-16-le'),) """Sequence of (start_bytes, encoding) tuples for encoding detection. @@ -204,7 +208,8 @@ class FileInput(Input): """ def __init__(self, source=None, source_path=None, encoding=None, error_handler='strict', - autoclose=True, mode='rU', **kwargs): + autoclose=True, + mode='r' if sys.version_info >= (3, 0) else 'rU'): """ :Parameters: - `source`: either a file-like object (which is read directly), or @@ -216,38 +221,27 @@ class FileInput(Input): `sys.stdin` is the source). - `mode`: how the file is to be opened (see standard function `open`). The default 'rU' provides universal newline support - for text files. + for text files with Python 2.x. """ Input.__init__(self, source, source_path, encoding, error_handler) self.autoclose = autoclose self._stderr = ErrorOutput() - # deprecation warning - for key in kwargs: - if key == 'handle_io_errors': - sys.stderr.write('deprecation warning: ' - 'io.FileInput() argument `handle_io_errors` ' - 'is ignored since "Docutils 0.10 (2012-12-16)" ' - 'and will soon be removed.') - else: - raise TypeError('__init__() got an unexpected keyword ' - "argument '%s'" % key) if source is None: if source_path: # Specify encoding in Python 3 - if sys.version_info >= (3,0): + if sys.version_info >= (3, 0): kwargs = {'encoding': self.encoding, 'errors': self.error_handler} else: kwargs = {} - try: self.source = open(source_path, mode, **kwargs) - except IOError, error: + except IOError as error: raise InputError(error.errno, error.strerror, source_path) else: self.source = sys.stdin - elif (sys.version_info >= (3,0) and + elif (sys.version_info >= (3, 0) and check_encoding(self.source, self.encoding) is False): # TODO: re-open, warn or raise error? raise UnicodeError('Encoding clash: encoding given is "%s" ' @@ -263,25 +257,24 @@ class FileInput(Input): """ Read and decode a single file and return the data (Unicode string). """ - try: # In Python < 2.5, try...except has to be nested in try...finally. - try: - if self.source is sys.stdin and sys.version_info >= (3,0): - # read as binary data to circumvent auto-decoding - data = self.source.buffer.read() - # normalize newlines - data = b('\n').join(data.splitlines()) + b('\n') - else: - data = self.source.read() - except (UnicodeError, LookupError), err: # (in Py3k read() decodes) - if not self.encoding and self.source_path: - # re-read in binary mode and decode with heuristics - b_source = open(self.source_path, 'rb') - data = b_source.read() - b_source.close() - # normalize newlines - data = b('\n').join(data.splitlines()) + b('\n') - else: - raise + try: + if self.source is sys.stdin and sys.version_info >= (3, 0): + # read as binary data to circumvent auto-decoding + data = self.source.buffer.read() + # normalize newlines + data = b'\n'.join(data.splitlines()) + b'\n' + else: + data = self.source.read() + except (UnicodeError, LookupError) as err: # (in Py3k read() decodes) + if not self.encoding and self.source_path: + # re-read in binary mode and decode with heuristics + b_source = open(self.source_path, 'rb') + data = b_source.read() + b_source.close() + # normalize newlines + data = b'\n'.join(data.splitlines()) + b'\n' + else: + raise finally: if self.autoclose: self.close() @@ -333,6 +326,10 @@ class FileOutput(Output): encoding, error_handler) self.opened = True self.autoclose = autoclose + if handle_io_errors is not None: + warnings.warn('io.FileOutput: initialization argument ' + '"handle_io_errors" is ignored and will be removed in ' + 'Docutils 1.2.', DeprecationWarning, stacklevel=2) if mode is not None: self.mode = mode self._stderr = ErrorOutput() @@ -344,9 +341,9 @@ class FileOutput(Output): elif (# destination is file-type object -> check mode: mode and hasattr(self.destination, 'mode') and mode != self.destination.mode): - print >>self._stderr, ('Warning: Destination mode "%s" ' - 'differs from specified mode "%s"' % - (self.destination.mode, 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 @@ -355,14 +352,14 @@ class FileOutput(Output): def open(self): # Specify encoding in Python 3. - if sys.version_info >= (3,0) and 'b' not in self.mode: + if sys.version_info >= (3, 0) and '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 IOError, error: + except IOError as error: raise OutputError(error.errno, error.strerror, self.destination_path) self.opened = True @@ -375,33 +372,32 @@ class FileOutput(Output): """ if not self.opened: self.open() - if ('b' not in self.mode and sys.version_info < (3,0) + if ('b' not in self.mode and sys.version_info < (3, 0) or check_encoding(self.destination, self.encoding) is False ): data = self.encode(data) - if sys.version_info >= (3,0) and os.linesep != '\n': - data = data.replace(b('\n'), b(os.linesep)) # fix endings + if sys.version_info >= (3, 0) and os.linesep != '\n': + data = data.replace(b'\n', bytes(os.linesep, 'ascii')) # fix endings - try: # In Python < 2.5, try...except has to be nested in try...finally. - try: - self.destination.write(data) - except TypeError, e: - if sys.version_info >= (3,0) and isinstance(data, bytes): - try: - self.destination.buffer.write(data) - except AttributeError: - if check_encoding(self.destination, - self.encoding) is False: - raise ValueError('Encoding of %s (%s) differs \n' - ' from specified encoding (%s)' % - (self.destination_path or 'destination', - self.destination.encoding, self.encoding)) - else: - raise e - except (UnicodeError, LookupError), err: - raise UnicodeError( - 'Unable to encode output data. output-encoding is: ' - '%s.\n(%s)' % (self.encoding, ErrorString(err))) + try: + self.destination.write(data) + except TypeError as err: + if sys.version_info >= (3, 0) and isinstance(data, bytes): + try: + self.destination.buffer.write(data) + except AttributeError: + if check_encoding(self.destination, + self.encoding) is False: + raise ValueError('Encoding of %s (%s) differs \n' + ' from specified encoding (%s)' % + (self.destination_path or 'destination', + self.destination.encoding, self.encoding)) + else: + raise err + except (UnicodeError, LookupError) as err: + raise UnicodeError( + 'Unable to encode output data. output-encoding is: ' + '%s.\n(%s)' % (self.encoding, ErrorString(err))) finally: if self.autoclose: self.close() diff --git a/docutils/src/main/resources/docutils/docutils/languages/__init__.py b/docutils/src/main/resources/docutils/docutils/languages/__init__.py index 108baed..b6a7a7c 100644 --- a/docutils/src/main/resources/docutils/docutils/languages/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/languages/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7648 2013-04-18 07:36:22Z milde $ +# $Id: __init__.py 8860 2021-10-22 16:39:59Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -12,37 +12,72 @@ This package contains modules for language-dependent features of Docutils. __docformat__ = 'reStructuredText' import sys +from importlib import import_module from docutils.utils import normalize_language_tag -if sys.version_info < (2,5): - from docutils._compat import __import__ -_languages = {} -def get_language(language_code, reporter=None): - """Return module with language localizations. +class LanguageImporter(object): + """Import language modules. - `language_code` is a "BCP 47" language tag. - If there is no matching module, warn and fall back to English. + 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. """ - # TODO: use a dummy module returning emtpy strings?, configurable? - for tag in normalize_language_tag(language_code): - tag = tag.replace('-','_') # '-' not valid in module names - if tag in _languages: - return _languages[tag] - try: - module = __import__(tag, globals(), locals(), level=1) - except ImportError: + 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__(tag, globals(), locals(), level=0) - except ImportError: + module = import_module(package+name) + self.check_content(module) + except (ImportError, AttributeError): + if reporter and module: + reporter.info('%s is no complete Docutils language module.' + %module) + elif reporter: + reporter.info('Module "%s" not found.'%(package+name)) continue - _languages[tag] = module + 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 - if reporter is not None: - reporter.warning( - 'language "%s" not supported: ' % language_code + - 'Docutils-generated text will be in English.') - module = __import__('en', globals(), locals(), level=1) - _languages[tag] = module # warn only one time! - return module + + +get_language = LanguageImporter() diff --git a/docutils/src/main/resources/docutils/docutils/languages/ar.py b/docutils/src/main/resources/docutils/docutils/languages/ar.py new file mode 100644 index 0000000..47c14f9 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/languages/ar.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# $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 <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. + +""" +Arabic-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + u'author': u'المؤلف', + u'authors': u'المؤلفون', + u'organization': u'التنظيم', + u'address': u'العنوان', + u'contact': u'اتصل', + u'version': u'نسخة', + u'revision': u'مراجعة', + u'status': u'الحالة', + u'date': u'تاریخ', + u'copyright': u'الحقوق', + u'dedication': u'إهداء', + u'abstract': u'ملخص', + u'attention': u'تنبيه', + u'caution': u'احتیاط', + u'danger': u'خطر', + u'error': u'خطأ', + u'hint': u'تلميح', + u'important': u'مهم', + u'note': u'ملاحظة', + u'tip': u'نصيحة', + u'warning': u'تحذير', + u'contents': u'المحتوى'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + u'مؤلف': u'author', + u'مؤلفون': u'authors', + u'التنظيم': u'organization', + u'العنوان': u'address', + u'اتصل': u'contact', + u'نسخة': u'version', + u'مراجعة': u'revision', + u'الحالة': u'status', + u'تاریخ': u'date', + u'الحقوق': u'copyright', + u'إهداء': u'dedication', + u'ملخص': u'abstract'} +"""Arabic (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [u'؛', u'،'] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/docutils/src/main/resources/docutils/docutils/languages/ko.py b/docutils/src/main/resources/docutils/docutils/languages/ko.py new file mode 100644 index 0000000..4cfbbd4 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/languages/ko.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# $Id: ko.py 8541 2020-08-22 22:16:25Z 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 <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. + +""" +Korean-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': u'저자', + 'authors': u'저자들', + 'organization': u'조직', + 'address': u'주소', + 'contact': u'연락처', + 'version': u'버전', + 'revision': u'리비전', + 'status': u'상태', + 'date': u'날짜', + 'copyright': u'저작권', + 'dedication': u'헌정', + 'abstract': u'요약', + 'attention': u'집중!', + 'caution': u'주의!', + 'danger': u'!위험!', + 'error': u'오류', + 'hint': u'실마리', + 'important': u'중요한', + 'note': u'비고', + 'tip': u'팁', + 'warning': u'경고', + 'contents': u'목차'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + u'저자': 'author', + u'저자들': 'authors', + u'조직': 'organization', + u'주소': 'address', + u'연락처': 'contact', + u'버전': 'version', + u'리비전': 'revision', + u'상태': 'status', + u'날짜': 'date', + u'저작권': 'copyright', + u'헌정': 'dedication', + u'요약': '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/docutils/src/main/resources/docutils/docutils/nodes.py b/docutils/src/main/resources/docutils/docutils/nodes.py index 8628e9c..c5f4789 100644 --- a/docutils/src/main/resources/docutils/docutils/nodes.py +++ b/docutils/src/main/resources/docutils/docutils/nodes.py @@ -1,4 +1,4 @@ -# $Id: nodes.py 7788 2015-02-16 22:10:52Z milde $ +# $Id: nodes.py 8890 2021-11-16 22:20:43Z milde $ # Author: David Goodger <goodger@python.org> # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -19,6 +19,8 @@ hierarchy. .. _DTD: http://docutils.sourceforge.net/docs/ref/docutils.dtd """ +from __future__ import print_function +from collections import Counter __docformat__ = 'reStructuredText' @@ -26,41 +28,60 @@ import sys import os import re import warnings -import types import unicodedata +# import xml.dom.minidom as dom # -> conditional import in Node.asdom() + +if sys.version_info >= (3, 0): + unicode = str # noqa + basestring = str # noqa + # ============================== # Functional Node Base Classes # ============================== class Node(object): - """Abstract base class of nodes in a document tree.""" parent = None """Back-reference to the Node immediately containing this Node.""" - document = None - """The `document` node at the root of the tree 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`.""" - def __nonzero__(self): + _document = None + + @property + def document(self): + """ + Return the `document` node at the root 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. Use `None` to represent a boolean - false value. + Use `len()` to check node length. """ return True - if sys.version_info < (3,): + if sys.version_info < (3, 0): + __nonzero__ = __bool__ + + if sys.version_info < (3, 0): # on 2.x, str(node) will be a byte string with Unicode # characters > 255 escaped; on 3.x this is no longer necessary def __str__(self): @@ -89,6 +110,10 @@ class Node(object): """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: @@ -187,41 +212,57 @@ class Node(object): visitor.dispatch_departure(self) return stop - def _fast_traverse(self, cls): - """Specialized traverse() that only supports instance checks.""" - result = [] + def _fast_findall(self, cls): + """Return iterator that only supports instance checks.""" if isinstance(self, cls): - result.append(self) + yield self for child in self.children: - result.extend(child._fast_traverse(cls)) - return result - - def _all_traverse(self): - """Specialized traverse() that doesn't check for a condition.""" - result = [] - result.append(self) + for subnode in child._fast_findall(cls): + yield subnode + + 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: - result.extend(child._all_traverse()) - return result + for subnode in child._superfast_findall(): + yield subnode 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 iterable containing + Return an iterator yielding nodes following `self`: - * self (if include_self is true) - * all descendants in tree traversal order (if descend is true) - * all siblings (if siblings is true) and their descendants (if - also descend is true) - * the siblings of the parent (if ascend is true) and their - descendants (if also descend is true), and so on + * 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 iterable contains only nodes + 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 `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:: @@ -233,11 +274,11 @@ class Node(object): <reference name="Baz" refid="baz"> Baz - Then list(emphasis.traverse()) equals :: + Then tuple(emphasis.traverse()) equals :: - [<emphasis>, <strong>, <#text: Foo>, <#text: Bar>] + (<emphasis>, <strong>, <#text: Foo>, <#text: Bar>) - and list(strong.traverse(ascend=True)) equals :: + and list(strong.traverse(ascend=True) equals :: [<strong>, <#text: Foo>, <#text: Bar>, <reference>, <#text: Baz>] """ @@ -247,56 +288,66 @@ class Node(object): # optimized version of traverse() if include_self and descend and not siblings: if condition is None: - return self._all_traverse() - elif isinstance(condition, (types.ClassType, type)): - return self._fast_traverse(condition) + for subnode in self._superfast_findall(): + yield subnode + return + elif isinstance(condition, type): + for subnode in self._fast_findall(condition): + yield subnode + return # Check if `condition` is a class (check for TypeType for Python # implementations that use only new-style classes, like PyPy). - if isinstance(condition, (types.ClassType, type)): + if isinstance(condition, type): node_class = condition def condition(node, node_class=node_class): return isinstance(node, node_class) - r = [] + + if include_self and (condition is None or condition(self)): - r.append(self) + yield self if descend and len(self.children): for child in self: - r.extend(child.traverse(include_self=True, descend=True, - siblings=False, ascend=False, - condition=condition)) + for subnode in child.findall(condition=condition, + include_self=True, descend=True, + siblings=False, ascend=False): + yield subnode if siblings or ascend: node = self while node.parent: index = node.parent.index(node) for sibling in node.parent[index+1:]: - r.extend(sibling.traverse(include_self=True, - descend=descend, - siblings=False, ascend=False, - condition=condition)) + for subnode in sibling.findall(condition=condition, + include_self=True, descend=descend, + siblings=False, ascend=False): + yield subnode if not ascend: break else: node = node.parent - return r def next_node(self, condition=None, include_self=False, descend=True, siblings=False, ascend=False): """ - Return the first node in the iterable returned by traverse(), + Return the first node in the iterator returned by findall(), or None if the iterable is empty. - Parameter list is the same as of traverse. Note that - include_self defaults to 0, though. + Parameter list is the same as of traverse. Note that `include_self` + defaults to False, though. """ - iterable = self.traverse(condition=condition, - include_self=include_self, descend=descend, - siblings=siblings, ascend=ascend) try: - return iterable[0] - except IndexError: + return next(self.findall(condition, include_self, + descend, siblings, ascend)) + except StopIteration: return None -if sys.version_info < (3,): + def previous_sibling(self): + """Return preceding sibling node or ``None``.""" + try: + return self.parent[self.parent.index(self)-1] + except (AttributeError, IndexError): + return None + +if sys.version_info < (3, 0): class reprunicode(unicode): """ A unicode sub-class that removes the initial u from unicode's repr. @@ -304,20 +355,32 @@ if sys.version_info < (3,): def __repr__(self): return unicode.__repr__(self)[1:] - - else: reprunicode = unicode def ensure_str(s): """ - Failsave conversion of `unicode` to `str`. + Failsafe conversion of `unicode` to `str`. """ - if sys.version_info < (3,) and isinstance(s, unicode): + if sys.version_info < (3, 0) and isinstance(s, unicode): return s.encode('ascii', 'backslashreplace') return s +# 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, reprunicode): @@ -332,21 +395,22 @@ class Text(Node, reprunicode): children = () """Text nodes have no children, and cannot have children.""" - if sys.version_info > (3,): + if sys.version_info > (3, 0): def __new__(cls, data, rawsource=None): - """Prevent the rawsource argument from propagating to str.""" + """Assert that `data` is not an array of bytes.""" if isinstance(data, bytes): raise TypeError('expecting str data, not bytes') return reprunicode.__new__(cls, data) else: def __new__(cls, data, rawsource=None): - """Prevent the rawsource argument from propagating to str.""" return reprunicode.__new__(cls, data) - def __init__(self, data, rawsource=''): - - self.rawsource = rawsource - """The raw text from which this element was constructed.""" + def __init__(self, data, rawsource=None): + """The `rawsource` argument is ignored and deprecated.""" + if rawsource is not None: + warnings.warn('nodes.Text: initialization argument "rawsource" ' + 'is ignored and will be removed in Docutils 1.3.', + DeprecationWarning, stacklevel=2) def shortrepr(self, maxlen=18): data = self @@ -361,7 +425,7 @@ class Text(Node, reprunicode): return domroot.createTextNode(unicode(self)) def astext(self): - return reprunicode(self) + return reprunicode(unescape(self)) # Note about __unicode__: The implementation of __unicode__ here, # and the one raising NotImplemented in the superclass Node had @@ -373,25 +437,33 @@ class Text(Node, reprunicode): # an infinite loop def copy(self): - return self.__class__(reprunicode(self), rawsource=self.rawsource) + return self.__class__(reprunicode(self)) def deepcopy(self): return self.copy() def pformat(self, indent=' ', level=0): - result = [] + try: + if self.document.settings.detailed: + lines = ['%s%s' % (indent*level, '<#text>') + ] + [indent*(level+1) + repr(reprunicode(line)) + for line in self.splitlines(True)] + return '\n'.join(lines) + '\n' + except AttributeError: + pass indent = indent * level - for line in self.splitlines(): - result.append(indent + line + '\n') - return ''.join(result) + 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. Note that then and now the - # rawsource member is lost. + # taken care of by UserString. def rstrip(self, chars=None): return self.__class__(reprunicode.rstrip(self, chars)) + def lstrip(self, chars=None): return self.__class__(reprunicode.lstrip(self, chars)) @@ -400,23 +472,35 @@ class Element(Node): """ `Element` is the superclass to all specific elements. - Elements contain attributes and child nodes. Elements emulate - dictionaries for attributes, indexing by attribute name (a string). To - set the attribute 'att' to 'value', do:: + 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, and names serve as human interfaces - to IDs. Names are case- and whitespace-normalized (see the - fully_normalize_name() function), and IDs conform to the regular - expression ``[a-z](-?[a-z0-9]+)*`` (see the make_id() function). + 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 also emulate lists for child nodes (element nodes and/or text + 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:: @@ -433,22 +517,22 @@ class Element(Node): """ basic_attributes = ('ids', 'classes', 'names', 'dupnames') - """List attributes which are defined for every Element-derived class + """Tuple of attributes which are defined for every Element-derived class instance and can be safely transferred to a different node.""" local_attributes = ('backrefs',) - """A list of class-specific attributes that should not be copied with the + """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 - """List attributes, automatically initialized to empty lists for - all nodes.""" + """Tuple of attributes that are automatically initialized to empty lists + for all nodes.""" known_attributes = list_attributes + ('source',) - """List attributes that are known to the Element base class.""" + """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 @@ -459,7 +543,10 @@ class Element(Node): def __init__(self, rawsource='', *children, **attributes): self.rawsource = rawsource - """The raw text from which this element was constructed.""" + """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`).""" @@ -522,8 +609,7 @@ class Element(Node): else: return self.emptytag() - if sys.version_info > (3,): - # 2to3 doesn't convert __unicode__ to __str__ + if sys.version_info >= (3, 0): __str__ = __unicode__ def starttag(self, quoteattr=None): @@ -556,8 +642,7 @@ class Element(Node): return len(self.children) def __contains__(self, key): - # support both membership test for children and attributes - # (has_key is translated to "in" by 2to3) + # Test for both, children and attributes with operator ``in``. if isinstance(key, basestring): return key in self.attributes return key in self.children @@ -567,12 +652,12 @@ class Element(Node): return self.attributes[key] elif isinstance(key, int): return self.children[key] - elif isinstance(key, types.SliceType): + 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') + raise TypeError('element index must be an integer, a slice, or ' + 'an attribute name string') def __setitem__(self, key, item): if isinstance(key, basestring): @@ -580,26 +665,26 @@ class Element(Node): elif isinstance(key, int): self.setup_child(item) self.children[key] = item - elif isinstance(key, types.SliceType): + 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') + raise TypeError('element index must be an integer, a slice, or ' + 'an attribute name string') def __delitem__(self, key): if isinstance(key, basestring): del self.attributes[key] elif isinstance(key, int): del self.children[key] - elif isinstance(key, types.SliceType): + 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') + raise TypeError('element index must be an integer, a simple ' + 'slice, or an attribute name string') def __add__(self, other): return self.children + other @@ -627,8 +712,7 @@ class Element(Node): return atts def attlist(self): - attlist = self.non_default_attributes().items() - attlist.sort() + attlist = sorted(self.non_default_attributes().items()) return attlist def get(self, key, failobj=None): @@ -646,9 +730,6 @@ class Element(Node): has_key = hasattr - # support operator ``in`` - __contains__ = hasattr - def get_language_code(self, fallback=''): """Return node's language tag. @@ -785,7 +866,7 @@ class Element(Node): def copy_attr_consistent(self, attr, value, replace): """ - If replace is True or selfpattr] is None, replace self[attr] with + If replace is True or self[attr] is None, replace self[attr] with value. Otherwise, do nothing. """ if self.get(attr) is not value: @@ -954,7 +1035,7 @@ class Element(Node): 'Losing "%s" attribute: %s' % (att, self[att]) self.parent.replace(self, new) - def first_child_matching_class(self, childclass, start=0, end=sys.maxint): + def first_child_matching_class(self, childclass, start=0, end=sys.maxsize): """ Return the index of the first child whose class exactly matches. @@ -974,7 +1055,7 @@ class Element(Node): return None def first_child_not_matching_class(self, childclass, start=0, - end=sys.maxint): + end=sys.maxsize): """ Return the index of the first child whose class does *not* match. @@ -1001,7 +1082,11 @@ class Element(Node): for child in self.children]) def copy(self): - return self.__class__(rawsource=self.rawsource, **self.attributes) + 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() @@ -1010,7 +1095,7 @@ class Element(Node): def set_class(self, name): """Add a new class to the "classes" attribute.""" - warnings.warn('docutils.nodes.Element.set_class deprecated; ' + warnings.warn('docutils.nodes.Element.set_class() is deprecated; ' "append to Element['classes'] list attribute directly", DeprecationWarning, stacklevel=2) assert ' ' not in name @@ -1090,12 +1175,12 @@ class FixedTextElement(TextElement): # Mixins # ======== -class Resolvable: +class Resolvable(object): resolved = 0 -class BackLinkable: +class BackLinkable(object): def add_backref(self, refid): self['backrefs'].append(refid) @@ -1105,39 +1190,63 @@ class BackLinkable: # Element Categories # ==================== -class Root: pass +class Root(object): + pass + + +class Titular(object): + pass -class Titular: pass -class PreBibliographic: +class PreBibliographic(object): """Category of Node which may occur before Bibliographic Nodes.""" -class Bibliographic: pass -class Decorative(PreBibliographic): pass +class Bibliographic(object): + pass + + +class Decorative(PreBibliographic): + pass + + +class Structural(object): + pass + + +class Body(object): + pass -class Structural: pass -class Body: pass +class General(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 Part(object): + pass + + +class Inline(object): + pass + -class Referential(Resolvable): pass +class Referential(Resolvable): + pass class Targetable(Resolvable): @@ -1149,7 +1258,7 @@ class Targetable(Resolvable): Required for MoinMoin/reST compatibility.""" -class Labeled: +class Labeled(object): """Contains a `label` as its first element.""" @@ -1237,8 +1346,8 @@ class document(Root, Structural, Element): self.symbol_footnote_start = 0 """Initial symbol footnote symbol index.""" - self.id_start = 1 - """Initial ID number.""" + self.id_counter = Counter() + """Numbers added to otherwise identical IDs.""" self.parse_messages = [] """System messages generated while parsing.""" @@ -1250,10 +1359,13 @@ class document(Root, Structural, Element): 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 + self._document = self def __getstate__(self): """ @@ -1272,24 +1384,47 @@ class document(Root, Structural, Element): domroot.appendChild(self._dom_node(domroot)) return domroot - def set_id(self, node, msgnode=None): - for id in node['ids']: - if id in self.ids and self.ids[id] is not node: - msg = self.reporter.severe('Duplicate ID: "%s".' % id) - if msgnode != None: - msgnode += msg - if not node['ids']: - for name in node['names']: - id = self.settings.id_prefix + make_id(name) - if id and id not in self.ids: - break + 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 != 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: - id = '' - while not id or id in self.ids: - id = (self.settings.id_prefix + - self.settings.auto_id_prefix + str(self.id_start)) - self.id_start += 1 - node['ids'].append(id) + 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 @@ -1299,25 +1434,26 @@ class document(Root, Structural, Element): booleans representing hyperlink type (True==explicit, False==implicit). This method updates the mappings. - The following state transition table shows how `self.nameids` ("ids") - and `self.nametypes` ("types") 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): + 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 ----------- -------- ----------------- ----------- ----- - ids types new type sys.msg. dupname ids types + id type new type sys.msg. dupname id type ==== ===== ======== ======== ======= ==== ===== ===== - - explicit - - new True - - implicit - - new False - None False explicit - - new True + - False explicit - - new True old False explicit implicit old new True - None True explicit explicit new None True - old True explicit explicit new,old None True [#]_ - None False implicit implicit new None False - old False implicit implicit new,old None False - None True implicit implicit new None 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 ==== ===== ======== ======== ======= ==== ===== ===== @@ -1325,9 +1461,10 @@ class document(Root, Structural, Element): both old and new targets are external and refer to identical URIs. The new target is invalidated regardless. """ - for name in node['names']: + 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 @@ -1380,7 +1517,7 @@ class document(Root, Structural, Element): # "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=None) + self.set_name_id_map(target, id, msgnode, explicit=False) def note_explicit_target(self, target, msgnode=None): id = self.set_id(target, msgnode) @@ -1468,13 +1605,16 @@ class document(Root, Structural, Element): self.current_line = offset + 1 def copy(self): - return self.__class__(self.settings, self.reporter, + 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) + index = self.first_child_not_matching_class((Titular, meta)) if index is None: self.append(self.decoration) else: @@ -1491,6 +1631,13 @@ 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 # ======================== @@ -1668,13 +1815,14 @@ class system_message(Special, BackLinkable, PreBibliographic, Element): """ def __init__(self, message=None, *children, **attributes): + rawsource = attributes.pop('rawsource', '') if message: p = paragraph('', message) children = (p,) + children try: - Element.__init__(self, '', *children, **attributes) + Element.__init__(self, rawsource, *children, **attributes) except: - print 'system_message: children=%r' % (children,) + print('system_message: children=%r' % (children,)) raise def astext(self): @@ -1728,8 +1876,7 @@ class pending(Special, Invisible, Element): ' .transform: %s.%s' % (self.transform.__module__, self.transform.__name__), ' .details:'] - details = self.details.items() - details.sort() + details = sorted(self.details.items()) for key, value in details: if isinstance(value, Node): internals.append('%7s%s:' % ('', key)) @@ -1748,8 +1895,12 @@ class pending(Special, Invisible, Element): for line in internals])) def copy(self): - return self.__class__(self.transform, self.details, self.rawsource, + 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): @@ -1811,7 +1962,7 @@ node_class_names = """ header hint image important inline label legend line line_block list_item literal literal_block - math math_block + math math_block meta note option option_argument option_group option_list option_list_item option_string organization @@ -1826,7 +1977,7 @@ node_class_names = """ """A list of names of all concrete Node subclasses.""" -class NodeVisitor: +class NodeVisitor(object): """ "Visitor" pattern [GoF95]_ abstract superclass implementation for @@ -1842,11 +1993,11 @@ class NodeVisitor: "``depart_`` + node class name", resp. This is a base class for visitors whose ``visit_...`` & ``depart_...`` - methods should be implemented for *all* node types encountered (such as - for `docutils.writers.Writer` subclasses). Unimplemented methods will - raise exceptions. + 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, + 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`. @@ -1855,7 +2006,7 @@ class NodeVisitor: 1995. """ - optional = () + optional = ('meta',) """ Tuple containing node class names (as strings). @@ -2068,7 +2219,7 @@ class NodeFound(TreePruningException): class StopTraversal(TreePruningException): """ - Stop the traversal alltogether. The current node's ``depart_...`` method + 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 diff --git a/docutils/src/main/resources/docutils/docutils/parsers/__init__.py b/docutils/src/main/resources/docutils/docutils/parsers/__init__.py index 3d2b40a..062d33c 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7646 2013-04-17 14:17:37Z milde $ +# $Id: __init__.py 8671 2021-04-07 12:09:51Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -9,13 +9,38 @@ This package contains Docutils parser modules. __docformat__ = 'reStructuredText' import sys -from docutils import Component -if sys.version_info < (2,5): - from docutils._compat import __import__ +from importlib import import_module +from docutils import Component, frontend -class Parser(Component): +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' @@ -26,6 +51,10 @@ class Parser(Component): 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) @@ -39,7 +68,10 @@ _parser_aliases = { 'restructuredtext': 'rst', 'rest': 'rst', 'restx': 'rst', - 'rtxt': 'rst',} + 'rtxt': 'rst', + 'recommonmark': 'recommonmark_wrapper', + 'commonmark': 'recommonmark_wrapper', + 'markdown': 'recommonmark_wrapper'} def get_parser_class(parser_name): """Return the Parser class from the `parser_name` module.""" @@ -47,7 +79,7 @@ def get_parser_class(parser_name): if parser_name in _parser_aliases: parser_name = _parser_aliases[parser_name] try: - module = __import__(parser_name, globals(), locals(), level=1) + module = import_module('docutils.parsers.'+parser_name) except ImportError: - module = __import__(parser_name, globals(), locals(), level=0) + module = import_module(parser_name) return module.Parser diff --git a/docutils/src/main/resources/docutils/docutils/parsers/recommonmark_wrapper.py b/docutils/src/main/resources/docutils/docutils/parsers/recommonmark_wrapper.py new file mode 100644 index 0000000..19081d0 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/parsers/recommonmark_wrapper.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# -*- coding: utf8 -*- +# :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: 8885 $ +# Date: $Date: 2021-11-11 17:29:16 +0100 (Do, 11. Nov 2021) $ +""" +A parser for CommonMark MarkDown text using `recommonmark`__. + +__ https://pypi.org/project/recommonmark/ + +This module is provisional: +the API is not settled and may change with any minor Docutils version. +""" + +import docutils.parsers +from docutils import nodes, Component + +try: + from recommonmark.parser import CommonMarkParser +except ImportError as err: + CommonMarkParser = None + class Parser(docutils.parsers.Parser): + def parse(self, inputstring, document): + error = document.reporter.warning( + 'Missing dependency: MarkDown input is processed by a 3rd ' + 'party parser but Python did not find the required module ' + '"recommonmark" (https://pypi.org/project/recommonmark/).') + document.append(error) + +# recommonmark 0.5.0 introduced a hard dependency on Sphinx +# https://github.com/readthedocs/recommonmark/issues/202 +# There is a PR to change this to an optional dependency +# https://github.com/readthedocs/recommonmark/pull/218 +try: + from sphinx import addnodes +except ImportError: + # create a stub + class addnodes(nodes.pending): pass + + +if CommonMarkParser: + 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') + 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: + 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(lambda n: isinstance(n, + (nodes.literal, nodes.literal_block))): + 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'] + + # remove empty target nodes + for node in list(document.findall(nodes.target)): + # remove empty name + node['names'] = [v for v in node['names'] if v] + if node.children or [v for v in node.attributes.values() if v]: + continue + node.parent.remove(node) + + # 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) + + # fix section nodes + for node in document.findall(nodes.section): + # remove spurious IDs (first may be from duplicate name) + if len(node['ids']) > 1: + node['ids'].pop() + # fix section levels (recommonmark 0.4.0 + # later versions silently ignore incompatible levels) + if 'level' in node: + section_level = self.get_section_level(node) + if node['level'] != section_level: + warning = document.reporter.warning( + 'Title level inconsistent. Changing from %d to %d.' + %(node['level'], section_level), + nodes.literal_block('', node[0].astext())) + node.insert(1, warning) + # remove non-standard attribute "level" + del node['level'] + + # 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 get_section_level(self, node): + """Auxiliary function for post-processing in self.parse()""" + level = 1 + while True: + node = node.parent + if isinstance(node, nodes.document): + return level + if isinstance(node, nodes.section): + level += 1 + + def visit_document(self, node): + """Dummy function to prevent spurious warnings. + + cf. https://github.com/readthedocs/recommonmark/issues/177 + """ + pass diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/__init__.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/__init__.py index 35e6f55..456cbc4 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 8068 2017-05-08 22:10:39Z milde $ +# $Id: __init__.py 8671 2021-04-07 12:09:51Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -72,7 +72,7 @@ __docformat__ = 'reStructuredText' import docutils.parsers import docutils.statemachine -from docutils.parsers.rst import states +from docutils.parsers.rst import roles, states from docutils import frontend, nodes, Component from docutils.transforms import universal @@ -84,7 +84,7 @@ class Parser(docutils.parsers.Parser): supported = ('restructuredtext', 'rst', 'rest', 'restx', 'rtxt', 'rstx') """Aliases this parser supports.""" - settings_spec = ( + settings_spec = docutils.parsers.Parser.settings_spec + ( 'reStructuredText Parser Options', None, (('Recognize and link to standalone PEP references (like "PEP 258").', @@ -115,26 +115,8 @@ class Parser(docutils.parsers.Parser): ('Leave spaces before footnote references.', ['--leave-footnote-reference-space'], {'action': 'store_false', 'dest': 'trim_footnote_reference_space'}), - ('Disable directives that insert the contents of external file ' - '("include" & "raw"); 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 external file ' - '("include" & "raw"). Enabled by default.', - ['--file-insertion-enabled'], - {'action': 'store_true'}), - ('Disable the "raw" directives; replaced with a "warning" ' - 'system message.', - ['--no-raw'], - {'action': 'store_false', 'default': 1, 'dest': 'raw_enabled', - 'validator': frontend.validate_boolean}), - ('Enable the "raw" directive. Enabled by default.', - ['--raw-enabled'], - {'action': 'store_true'}), ('Token name set for parsing code with Pygments: one of ' - '"long", "short", or "none (no parsing)". Default is "long".', + '"long", "short", or "none" (no parsing). Default is "long".', ['--syntax-highlight'], {'choices': ['long', 'short', 'none'], 'default': 'long', 'metavar': '<format>'}), @@ -181,6 +163,9 @@ class Parser(docutils.parsers.Parser): 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, @@ -188,7 +173,17 @@ class Parser(docutils.parsers.Parser): inputlines = docutils.statemachine.string2lines( inputstring, tab_width=document.settings.tab_width, convert_whitespace=True) - self.statemachine.run(inputlines, document, inliner=self.inliner) + 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() diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/__init__.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/__init__.py index 5109058..5994b60 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 8024 2017-02-06 00:41:48Z goodger $ +# $Id: __init__.py 8860 2021-10-22 16:39:59Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -11,12 +11,14 @@ __docformat__ = 'reStructuredText' import re import codecs import sys +from importlib import import_module -from docutils import nodes +from docutils import nodes, parsers from docutils.utils import split_escaped_whitespace, escape2null, unescape from docutils.parsers.rst.languages import en as _fallback_language_module -if sys.version_info < (2,5): - from docutils._compat import __import__ + +if sys.version_info >= (3, 0): + unichr = chr # noqa _directive_registry = { @@ -55,7 +57,7 @@ _directive_registry = { #'footnotes': ('parts', 'footnotes'), #'citations': ('parts', 'citations'), 'target-notes': ('references', 'TargetNotes'), - 'meta': ('html', 'Meta'), + 'meta': ('misc', 'Meta'), #'imagemap': ('html', 'imagemap'), 'raw': ('misc', 'Raw'), 'include': ('misc', 'Include'), @@ -88,7 +90,7 @@ def directive(directive_name, language_module, document): canonicalname = None try: canonicalname = language_module.directives[normname] - except AttributeError, error: + except AttributeError as error: msg_text.append('Problem retrieving directive entry from language ' 'module %r: %s.' % (language_module, error)) except KeyError: @@ -114,8 +116,8 @@ def directive(directive_name, language_module, document): # Error handling done by caller. return None, messages try: - module = __import__(modulename, globals(), locals(), level=1) - except ImportError, detail: + 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), @@ -215,6 +217,7 @@ def nonnegative_int(argument): def percentage(argument): """ Check for an integer percentage value with optional percent sign. + (Directive option conversion function.) """ try: argument = argument.rstrip(' %') @@ -229,6 +232,7 @@ 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. """ @@ -247,6 +251,7 @@ def length_or_unitless(argument): 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). @@ -311,7 +316,7 @@ def unicode_code(code): return unichr(int(value, 16)) else: # other text return code - except OverflowError, detail: + except OverflowError as detail: raise ValueError('code too large (%s)' % detail) def single_char_or_unicode(argument): @@ -364,7 +369,7 @@ def positive_int_list(argument): def encoding(argument): """ - Verfies the encoding argument by lookup. + Verifies the encoding argument by lookup. (Directive option conversion function.) Raises ValueError for unknown encodings. @@ -407,6 +412,8 @@ def format_values(values): def value_or(values, other): """ + Directive option conversion function. + The argument can be any of `values` or `argument_type`. """ def auto_or_other(argument): @@ -416,3 +423,16 @@ def value_or(values, other): 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`. + """ + if not argument: + return None + try: + return parsers.get_parser_class(argument) + except ImportError: + raise ValueError('Unknown parser name "%s".'%argument) diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/body.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/body.py index c8bf172..205d34d 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/body.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/body.py @@ -1,4 +1,4 @@ -# $Id: body.py 7267 2011-12-20 14:14:21Z milde $ +# $Id: body.py 8860 2021-10-22 16:39:59Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -11,13 +11,13 @@ See `docutils.parsers.rst.directives` for API details. __docformat__ = 'reStructuredText' -import sys 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 @@ -36,16 +36,20 @@ class BasePseudoSection(Directive): raise self.error('The "%s" directive may not be used within ' 'topics or body elements.' % self.name) self.assert_has_content() - 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) + 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', []) @@ -64,6 +68,8 @@ 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 @@ -71,6 +77,10 @@ class Sidebar(BasePseudoSection): 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) @@ -147,8 +157,12 @@ class CodeBlock(Directive): try: tokens = Lexer(u'\n'.join(self.content), language, self.state.document.settings.syntax_highlight) - except LexerError, error: - raise self.warning(error) + except LexerError as error: + if self.state.document.settings.report_level > 2: + # don't report warnings -> insert without syntax highlight + tokens = Lexer(u'\n'.join(self.content), language, 'none') + else: + raise self.warning(error) if 'number-lines' in self.options: # optional argument `startline`, defaults to 1 @@ -167,12 +181,11 @@ class CodeBlock(Directive): node.attributes['source'] = self.options['source'] # analyze content and add nodes for every token for classes, value in tokens: - # print (classes, value) if classes: node += nodes.inline(value, value, classes=classes) else: # insert as Text to decrease the verbosity of the output - node += nodes.Text(value, value) + node += nodes.Text(value) return [node] diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/html.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/html.py index 7df8071..ca8eebc 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/html.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/html.py @@ -1,86 +1,21 @@ -# $Id: html.py 7320 2012-01-19 22:33:02Z milde $ +# $Id: html.py 8896 2021-11-18 19:48:58Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. """ -Directives for typically HTML-specific constructs. +Dummy module for backwards compatibility. + +This module is provisional: it will be removed in Docutils version 1.2. """ __docformat__ = 'reStructuredText' -import sys -from docutils import nodes, utils -from docutils.parsers.rst import Directive -from docutils.parsers.rst import states -from docutils.transforms import components - - -class MetaBody(states.SpecializedBody): - - class meta(nodes.Special, nodes.PreBibliographic, nodes.Element): - """HTML-specific "meta" element.""" - pass - - 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) - indented, indent, line_offset, blank_finish = \ - self.state_machine.get_first_known_indented(match.end()) - node = self.meta() - pending = nodes.pending(components.Filter, - {'component': 'writer', - 'format': 'html', - 'nodes': [node]}) - node['content'] = ' '.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, 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 - self.document.note_pending(pending) - return pending, blank_finish - - -class Meta(Directive): - - has_content = True +import warnings - SMkwargs = {'state_classes': (MetaBody,)} +from docutils.parsers.rst.directives.misc import MetaBody, Meta - 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.state_machine.reporter.error( - 'Invalid meta directive.', - nodes.literal_block(self.block_text, self.block_text), - line=self.lineno) - node += error - return node.children +warnings.warn('The `docutils.parsers.rst.directive.html` module' + ' will be removed in Docutils 1.2.' + ' Since Docutils 0.18, the "Meta" node is defined in' + ' `docutils.parsers.rst.directives.misc`.', + DeprecationWarning, stacklevel=2) diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/images.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/images.py index c813fa3..1f631e8 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/images.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/images.py @@ -1,4 +1,4 @@ -# $Id: images.py 7753 2014-06-24 14:52:59Z milde $ +# $Id: images.py 8583 2020-12-01 10:53:27Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -10,12 +10,13 @@ __docformat__ = 'reStructuredText' import sys -import urllib + from docutils import nodes, utils from docutils.parsers.rst import Directive from docutils.parsers.rst import directives, states from docutils.nodes import fully_normalize_name, whitespace_normalize_name from docutils.parsers.rst.roles import set_classes + try: # check for the Python Imaging Library import PIL.Image except ImportError: @@ -26,6 +27,12 @@ except ImportError: except ImportError: PIL = None +if sys.version_info >= (3, 0): + from urllib.request import url2pathname +else: + from urllib import url2pathname + + class Image(Directive): align_h_values = ('left', 'center', 'right') @@ -46,9 +53,9 @@ class Image(Directive): 'width': directives.length_or_percentage_or_unitless, 'scale': directives.percentage, 'align': align, - 'name': directives.unchanged, 'target': directives.unchanged_required, - 'class': directives.class_option} + 'class': directives.class_option, + 'name': directives.unchanged} def run(self): if 'align' in self.options: @@ -125,7 +132,7 @@ class Figure(Image): figure_node = nodes.figure('', image_node) if figwidth == 'image': if PIL and self.state.document.settings.file_insertion_enabled: - imagepath = urllib.url2pathname(image_node['uri']) + imagepath = url2pathname(image_node['uri']) try: img = PIL.Image.open( imagepath.encode(sys.getfilesystemencoding())) diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py index f843bdc..6590daf 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py @@ -1,4 +1,4 @@ -# $Id: misc.py 7961 2016-07-28 22:02:47Z milde $ +# $Id: misc.py 8851 2021-10-12 17:33:24Z milde $ # Authors: David Goodger <goodger@python.org>; Dethe Elza # Copyright: This module has been placed in the public domain. @@ -37,6 +37,7 @@ class Include(Directive): option_spec = {'literal': directives.flag, 'code': directives.unchanged, 'encoding': directives.encoding, + 'parser': directives.parser_name, 'tab-width': int, 'start-line': int, 'end-line': int, @@ -51,7 +52,11 @@ class Include(Directive): 'include') def run(self): - """Include a file as part of the content of this reST file.""" + """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. + """ if not self.state.document.settings.file_insertion_enabled: raise self.warning('"%s" directive disabled.' % self.name) source = self.state_machine.input_lines.source( @@ -73,14 +78,16 @@ class Include(Directive): include_file = io.FileInput(source_path=path, encoding=encoding, error_handler=e_handler) - except UnicodeEncodeError, error: + except UnicodeEncodeError as error: raise self.severe(u'Problems with "%s" directive path:\n' 'Cannot encode input file path "%s" ' '(wrong locale?).' % (self.name, SafeString(path))) - except IOError, error: + except IOError as error: raise self.severe(u'Problems with "%s" directive path:\n%s.' % (self.name, ErrorString(error))) + + # Get to-be-included content startline = self.options.get('start-line', None) endline = self.options.get('end-line', None) try: @@ -89,7 +96,7 @@ class Include(Directive): rawtext = ''.join(lines[startline:endline]) else: rawtext = include_file.read() - except UnicodeError, error: + except UnicodeError as error: raise self.severe(u'Problem with "%s" directive:\n%s' % (self.name, ErrorString(error))) # start-after/end-before: no restrictions on newlines in match-text, @@ -113,8 +120,13 @@ class Include(Directive): include_lines = statemachine.string2lines(rawtext, tab_width, convert_whitespace=True) + for i, line in enumerate(include_lines): + if len(line) > self.state.document.settings.line_length_limit: + raise self.warning('"%s": line %d exceeds the' + ' line-length-limit.' % (path, i+1)) + if 'literal' in self.options: - # Convert tabs to spaces, if `tab_width` is positive. + # Don't convert tabs to spaces, if `tab_width` is negative. if tab_width >= 0: text = rawtext.expandtabs(tab_width) else: @@ -138,12 +150,16 @@ class Include(Directive): literal_block += nodes.inline(value, value, classes=classes) else: - literal_block += nodes.Text(value, value) + literal_block += nodes.Text(value) else: - literal_block += nodes.Text(text, text) + 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, @@ -154,7 +170,34 @@ class Include(Directive): 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 + include_log.append((utils.relative_path(None, source), + (None, None, None, None))) + if (path, clip_options) in include_log: + raise self.warning('circular inclusion in "%s" directive: %s' + % (self.name, ' < '.join([path] + [pth for (pth, opt) + in include_log[::-1]]))) + + if 'parser' in self.options: + # parse into a dummy document and return created nodes + parser = self.options['parser']() + document = utils.new_document(path, self.state.document.settings) + document.include_log = include_log + [(path, clip_options)] + parser.parse('\n'.join(include_lines), document) + 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 [] @@ -174,7 +217,8 @@ class Raw(Directive): final_argument_whitespace = True option_spec = {'file': directives.path, 'url': directives.uri, - 'encoding': directives.encoding} + 'encoding': directives.encoding, + 'class': directives.class_option} has_content = True def run(self): @@ -210,12 +254,12 @@ class Raw(Directive): # TODO: currently, raw input files are recorded as # dependencies even if not used for the chosen output format. self.state.document.settings.record_dependencies.add(path) - except IOError, error: + except IOError as error: raise self.severe(u'Problems with "%s" directive path:\n%s.' % (self.name, ErrorString(error))) try: text = raw_file.read() - except UnicodeError, error: + except UnicodeError as error: raise self.severe(u'Problem with "%s" directive:\n%s' % (self.name, ErrorString(error))) attributes['source'] = path @@ -224,10 +268,14 @@ class Raw(Directive): # Do not import urllib2 at the top of the module because # it may fail due to broken SSL dependencies, and it takes # about 0.15 seconds to load. - import urllib2 + if sys.version_info >= (3, 0): + from urllib.request import urlopen + from urllib.error import URLError + else: + from urllib2 import urlopen, URLError try: - raw_text = urllib2.urlopen(source).read() - except (urllib2.URLError, IOError, OSError), error: + raw_text = urlopen(source).read() + except (URLError, IOError, OSError) as error: raise self.severe(u'Problems with "%s" directive URL "%s":\n%s.' % (self.name, self.options['url'], ErrorString(error))) raw_file = io.StringInput(source=raw_text, source_path=source, @@ -235,14 +283,15 @@ class Raw(Directive): error_handler=e_handler) try: text = raw_file.read() - except UnicodeError, error: + except UnicodeError as error: raise self.severe(u'Problem with "%s" directive:\n%s' % (self.name, ErrorString(error))) attributes['source'] = source else: # This will always fail because there is no content. self.assert_has_content() - raw_node = nodes.raw('', text, **attributes) + 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] @@ -317,7 +366,7 @@ class Unicode(Directive): for code in codes: try: decoded = directives.unicode_code(code) - except ValueError, error: + except ValueError as error: raise self.error(u'Invalid character code: %s\n%s' % (code, ErrorString(error))) element += nodes.Text(decoded) @@ -403,7 +452,7 @@ class Role(Directive): self.state.parse_directive_block( self.content[1:], self.content_offset, converted_role, option_presets={})) - except states.MarkupError, detail: + except states.MarkupError as detail: error = self.state_machine.reporter.error( 'Error in "%s" directive:\n%s.' % (self.name, detail), nodes.literal_block(self.block_text, self.block_text), @@ -412,7 +461,7 @@ class Role(Directive): if 'class' not in options: try: options['class'] = directives.class_option(new_role_name) - except ValueError, detail: + except ValueError as detail: error = self.state_machine.reporter.error( u'Invalid argument for "%s" directive:\n%s.' % (self.name, SafeString(detail)), nodes.literal_block( @@ -446,7 +495,6 @@ class DefaultRole(Directive): line=self.lineno) return messages + [error] roles._roles[''] = role - # @@@ should this be local to the document, not the parser? return messages @@ -461,6 +509,74 @@ class Title(Directive): 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 = utils.unescape(utils.escape2null(name)) + indented, indent, line_offset, blank_finish = \ + self.state_machine.get_first_known_indented(match.end()) + node = nodes.meta() + node['content'] = utils.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.state_machine.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 @@ -485,13 +601,11 @@ class Date(Directive): # __ https://reproducible-builds.org/ # # Con: Changes the specs, hard to predict behaviour, - # no actual use case! # # 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 - # and self.state.document.settings.use_source_date_epoch): + # if (source_date_epoch): # text = time.strftime(format_str, # time.gmtime(int(source_date_epoch))) # else: diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/tables.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/tables.py index f1977e4..9c21d00 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/tables.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/tables.py @@ -1,4 +1,4 @@ -# $Id: tables.py 8039 2017-02-28 12:19:20Z milde $ +# $Id: tables.py 8860 2021-10-22 16:39:59Z milde $ # Authors: David Goodger <goodger@python.org>; David Priest # Copyright: This module has been placed in the public domain. @@ -35,6 +35,7 @@ class Table(Directive): 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 @@ -94,21 +95,26 @@ class Table(Directive): 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, max_cols): - if type(self.widths) == list: - if len(self.widths) != max_cols: + 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.state_machine.reporter.error( '"%s" widths do not match the number of columns in table ' - '(%s).' % (self.name, max_cols), nodes.literal_block( + '(%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 max_cols: - col_widths = [100 // max_cols] * max_cols + elif n_cols: + col_widths = [100 // n_cols] * n_cols else: error = self.state_machine.reporter.error( 'No table data detected in CSV file.', nodes.literal_block( @@ -143,17 +149,19 @@ class RSTTable(Table): 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') - tgroup = table_node[0] - if type(self.widths) == list: + 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, self.widths): + for colspec, col_width in zip(colspecs, col_widths): colspec['colwidth'] = col_width - # @@@ the colwidths argument for <tgroup> is not part of the - # XML Exchange Table spec (https://www.oasis-open.org/specs/tm9901.htm) - # and hence violates the docutils.dtd. if self.widths == 'auto': table_node['classes'] += ['colwidths-auto'] elif self.widths: # "grid" or list of integers @@ -169,6 +177,7 @@ 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, @@ -252,11 +261,11 @@ class CSVTable(Table): col_widths = self.get_column_widths(max_cols) self.extend_short_rows_with_empty_cells(max_cols, (table_head, table_body)) - except SystemMessagePropagation, detail: + except SystemMessagePropagation as detail: return [detail.args[0]] - except csv.Error, detail: + except csv.Error as detail: message = str(detail) - if sys.version_info < (3,) and '1-character string' in message: + if sys.version_info < (3, 0) and '1-character string' in message: message += '\nwith Python 2.x this must be an ASCII character.' error = self.state_machine.reporter.error( 'Error with CSV data in "%s" directive:\n%s' @@ -269,6 +278,7 @@ class CSVTable(Table): 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) @@ -312,7 +322,7 @@ class CSVTable(Table): encoding=encoding, error_handler=error_handler) csv_data = csv_file.read().splitlines() - except IOError, error: + except IOError as error: severe = self.state_machine.reporter.severe( u'Problems with "%s" directive path:\n%s.' % (self.name, SafeString(error)), @@ -324,11 +334,16 @@ class CSVTable(Table): # Do not import urllib2 at the top of the module because # it may fail due to broken SSL dependencies, and it takes # about 0.15 seconds to load. - import urllib2 + if sys.version_info >= (3, 0): + from urllib.request import urlopen + from urllib.error import URLError + else: + from urllib2 import urlopen, URLError + source = self.options['url'] try: - csv_text = urllib2.urlopen(source).read() - except (urllib2.URLError, IOError, OSError, ValueError), error: + csv_text = urlopen(source).read() + except (URLError, IOError, OSError, ValueError) as error: severe = self.state_machine.reporter.severe( 'Problems with "%s" directive URL "%s":\n%s.' % (self.name, self.options['url'], SafeString(error)), @@ -348,7 +363,7 @@ class CSVTable(Table): raise SystemMessagePropagation(error) return csv_data, source - if sys.version_info < (3,): + if sys.version_info < (3, 0): # 2.x csv module doesn't do Unicode def decode_from_csv(s): return s.decode('utf-8') @@ -392,6 +407,7 @@ class ListTable(Table): 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, @@ -415,13 +431,14 @@ class ListTable(Table): 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, detail: + 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) @@ -436,6 +453,7 @@ class ListTable(Table): 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] @@ -448,9 +466,6 @@ class ListTable(Table): self.block_text, self.block_text), line=self.lineno) raise SystemMessagePropagation(error) elif item_index: - # ATTN pychecker users: num_cols is guaranteed to be set in the - # "else" clause below for item_index==0, before this branch is - # triggered. if len(item[0]) != num_cols: error = self.state_machine.reporter.error( 'Error parsing content block for the "%s" directive: ' @@ -470,7 +485,7 @@ class ListTable(Table): table = nodes.table() if self.widths == 'auto': table['classes'] += ['colwidths-auto'] - elif self.widths: # "grid" or list of integers + elif self.widths: # explicitly set column widths table['classes'] += ['colwidths-given'] tgroup = nodes.tgroup(cols=len(col_widths)) table += tgroup diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/include/README.txt b/docutils/src/main/resources/docutils/docutils/parsers/rst/include/README.txt index cd03135..adee0c2 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/include/README.txt +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/include/README.txt @@ -10,8 +10,8 @@ angle brackets around the file name:: .. include:: <isonum.txt> See the documentation for the `"include" directive`__ and -`reStructuredText Standard Substitution Definition Sets`__ for +`reStructuredText Standard Definition Files`__ for details. __ http://docutils.sf.net/docs/ref/rst/directives.html#include -__ http://docutils.sf.net/docs/ref/rst/substitutions.html +__ http://docutils.sf.net/docs/ref/rst/definitions.html diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/__init__.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/__init__.py index 6b58fcc..d06d11f 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7648 2013-04-18 07:36:22Z milde $ +# $Id: __init__.py 8467 2020-01-26 21:23:42Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -14,24 +14,26 @@ __docformat__ = 'reStructuredText' import sys -from docutils.utils import normalize_language_tag -if sys.version_info < (2,5): - from docutils._compat import __import__ - -_languages = {} - -def get_language(language_code): - for tag in normalize_language_tag(language_code): - tag = tag.replace('-','_') # '-' not valid in module names - if tag in _languages: - return _languages[tag] - try: - module = __import__(tag, globals(), locals(), level=1) - except ImportError: - try: - module = __import__(tag, globals(), locals(), level=0) - except ImportError: - continue - _languages[tag] = module - return module - return None +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/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ar.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ar.py new file mode 100644 index 0000000..6893353 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ar.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# $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 <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. + +""" +Arabic-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + u'تنبيه': u'attention', + u'احتیاط': u'caution', + u'كود': u'code', + u'كود': u'code', + u'كود': u'code', + u'خطر': u'danger', + u'خطأ': u'error', + u'تلميح': u'hint', + u'مهم': u'important', + u'ملاحظة': u'note', + u'نصيحة': u'tip', + u'تحذير': u'warning', + u'تذكير': u'admonition', + u'شريط-جانبي': u'sidebar', + u'موضوع': u'topic', + u'قالب-سطري': u'line-block', + u'لفظ-حرفي': u'parsed-literal', + u'معيار': u'rubric', + u'فكرة-الكتاب': u'epigraph', + u'تمييز': u'highlights', + u'نقل-قول': u'pull-quote', + u'ترکیب': u'compound', + u'وعاء': u'container', + #'questions': u'questions', + u'جدول': u'table', + u'جدول-csv': u'csv-table', + u'جدول-قوائم': u'list-table', + #'qa': u'questions', + #'faq': u'questions', + u'ميتا': u'meta', + u'رياضيات': u'math', + #'imagemap': u'imagemap', + u'صورة': u'image', + u'رسم-توضيحي': u'figure', + u'تضمين': u'include', + u'خام': u'raw', + u'تبديل': u'replace', + u'یونیکد': u'unicode', + u'تاریخ': u'date', + u'كائن': u'class', + u'قانون': u'role', + u'قانون-افتراضي': u'default-role', + u'عنوان': u'title', + u'المحتوى': u'contents', + u'رقم-الفصل': u'sectnum', + u'رقم-القسم': u'sectnum', + u'رأس-الصفحة': u'header', + u'هامش': u'footer', + #'footnotes': u'footnotes', + #'citations': u'citations', + u'': u'target-notes', + } +"""Arabic name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + u'اختصار': u'abbreviation', + u'اختزال': u'acronym', + u'كود': u'code', + u'فهرس': u'index', + u'خفض': u'subscript', + u'رفع': u'superscript', + u'عنوان-مرجع': u'title-reference', + u'مرجع-pep': u'pep-reference', + u'rfc-مرجع': u'rfc-reference', + u'تأكيد': u'emphasis', + u'عريض': u'strong', + u'لفظی': u'literal', + u'رياضيات': u'math', + u'مرجع-مسمى': u'named-reference', + u'مرجع-مجهول': u'anonymous-reference', + u'مرجع-هامشي': u'footnote-reference', + u'مرجع-منقول': u'citation-reference', + u'مرجع-معوض': u'substitution-reference', + u'هدف': u'target', + u'منبع-uri': u'uri-reference', + u'uri': u'uri-reference', + u'url': u'uri-reference', + u'خام': u'raw',} +"""Mapping of Arabic role names to canonical role names for interpreted text. +""" diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ko.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ko.py new file mode 100644 index 0000000..83a24af --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ko.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +# $Id: ko.py 8541 2020-08-22 22:16:25Z 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 <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. + +""" +Korean-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + u'집중': 'attention', + u'주의': 'caution', + u'코드': 'code', + u'코드-블록': 'code', + u'소스코드': 'code', + u'위험': 'danger', + u'오류': 'error', + u'실마리': 'hint', + u'중요한': 'important', + u'비고': 'note', + u'팁': 'tip', + u'경고': 'warning', + u'권고': 'admonition', + u'사이드바': 'sidebar', + u'주제': 'topic', + u'라인-블록': 'line-block', + u'파싱된-리터럴': 'parsed-literal', + u'지시문': 'rubric', + u'제명': 'epigraph', + u'하이라이트': 'highlights', + u'발췌문': 'pull-quote', + u'합성어': 'compound', + u'컨테이너': 'container', + #u'질문': 'questions', + u'표': 'table', + u'csv-표': 'csv-table', + u'list-표': 'list-table', + #u'qa': 'questions', + #u'faq': 'questions', + u'메타': 'meta', + u'수학': 'math', + #u'이미지맵': 'imagemap', + u'이미지': 'image', + u'도표': 'figure', + u'포함': 'include', + 'raw': 'raw', + u'대신하다': 'replace', + u'유니코드': 'unicode', + u'날짜': 'date', + u'클래스': 'class', + u'역할': 'role', + u'기본-역할': 'default-role', + u'제목': 'title', + u'내용': 'contents', + 'sectnum': 'sectnum', + u'섹션-번호-매기기': 'sectnum', + u'머리말': 'header', + u'꼬리말': 'footer', + #u'긱주': 'footnotes', + #u'인용구': 'citations', + u'목표-노트': 'target-notes', + u'restructuredtext 테스트 지시어': 'restructuredtext-test-directive'} +"""Korean name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + u'약어': 'abbreviation', + u'ab': 'abbreviation', + u'두음문자': 'acronym', + u'ac': 'acronym', + u'코드': 'code', + u'색인': 'index', + u'i': 'index', + u'다리-글자': 'subscript', + u'sub': 'subscript', + u'어깨-글자': 'superscript', + u'sup': 'superscript', + u'제목-참조': 'title-reference', + u'제목': 'title-reference', + u't': 'title-reference', + u'pep-참조': 'pep-reference', + u'pep': 'pep-reference', + u'rfc-참조': 'rfc-reference', + u'rfc': 'rfc-reference', + u'강조': 'emphasis', + u'굵게': 'strong', + u'기울기': 'literal', + u'수학': 'math', + u'명명된-참조': 'named-reference', + u'익명-참조': 'anonymous-reference', + u'각주-참조': 'footnote-reference', + u'인용-참조': 'citation-reference', + u'대리-참조': 'substitution-reference', + u'대상': 'target', + u'uri-참조': 'uri-reference', + u'uri': 'uri-reference', + u'url': 'uri-reference', + 'raw': 'raw',} +"""Mapping of Korean role names to canonical role names for interpreted text. +""" diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py index 7fa8c1f..1df9f86 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py @@ -1,11 +1,14 @@ -# $Id: roles.py 7937 2016-05-24 10:48:48Z milde $ +# $Id: roles.py 8873 2021-11-01 21:50:32Z 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. +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:: @@ -29,7 +32,8 @@ Parameters: - ``text`` is the interpreted text content, with backslash escapes converted to nulls (``\x00``). -- ``lineno`` is the line number where the interpreted text beings. +- ``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``, @@ -78,14 +82,16 @@ 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. +"""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.""" +"""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 @@ -94,9 +100,11 @@ 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. + 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 = [] @@ -109,7 +117,7 @@ def role(role_name, language_module, lineno, reporter): canonicalname = None try: canonicalname = language_module.roles[normname] - except AttributeError, error: + except AttributeError as error: msg_text.append('Problem retrieving role entry from language ' 'module %r: %s.' % (language_module, error)) except KeyError: @@ -140,8 +148,7 @@ def role(role_name, language_module, lineno, reporter): role_fn = _role_registry[canonicalname] register_local_role(normname, role_fn) return role_fn, messages - else: - return None, messages # Error message will be generated by caller. + return None, messages # Error message will be generated by caller. def register_canonical_role(name, role_fn): """ @@ -152,7 +159,7 @@ def register_canonical_role(name, role_fn): - `role_fn`: The role function. See the module docstring. """ set_implicit_options(role_fn) - _role_registry[name] = role_fn + _role_registry[name.lower()] = role_fn def register_local_role(name, role_fn): """ @@ -163,7 +170,7 @@ def register_local_role(name, role_fn): - `role_fn`: The role function. See the module docstring. """ set_implicit_options(role_fn) - _roles[name] = role_fn + _roles[name.lower()] = role_fn def set_implicit_options(role_fn): """ @@ -181,11 +188,11 @@ def register_generic_role(canonical_name, node_class): register_canonical_role(canonical_name, role) -class GenericRole: - +class GenericRole(object): """ - Generic interpreted text role, where the interpreted text is simply - wrapped with the provided node class. + Generic interpreted text role. + + The interpreted text is simply wrapped with the provided node class. """ def __init__(self, role_name, node_class): @@ -195,14 +202,11 @@ class GenericRole: def __call__(self, role, rawtext, text, lineno, inliner, options={}, content=[]): set_classes(options) - return [self.node_class(rawtext, utils.unescape(text), **options)], [] - + return [self.node_class(rawtext, text, **options)], [] -class CustomRole: - """ - Wrapper for custom interpreted text roles. - """ +class CustomRole(object): + """Wrapper for custom interpreted text roles.""" def __init__(self, role_name, base_role, options={}, content=[]): self.name = role_name @@ -230,11 +234,11 @@ class CustomRole: def generic_custom_role(role, rawtext, text, lineno, inliner, options={}, content=[]): - """""" + """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(). set_classes(options) - return [nodes.inline(rawtext, utils.unescape(text), **options)], [] + return [nodes.inline(rawtext, text, **options)], [] generic_custom_role.options = {'class': directives.class_option} @@ -255,7 +259,7 @@ register_generic_role('title-reference', nodes.title_reference) def pep_reference_role(role, rawtext, text, lineno, inliner, options={}, content=[]): try: - pepnum = int(text) + pepnum = int(utils.unescape(text)) if pepnum < 0 or pepnum > 9999: raise ValueError except ValueError: @@ -268,16 +272,20 @@ def pep_reference_role(role, rawtext, text, lineno, inliner, ref = (inliner.document.settings.pep_base_url + inliner.document.settings.pep_file_url_template % pepnum) set_classes(options) - return [nodes.reference(rawtext, 'PEP ' + utils.unescape(text), refuri=ref, + 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={}, content=[]): + if "#" in text: + rfcnum, section = utils.unescape(text).split("#", 1) + else: + rfcnum, section = utils.unescape(text), None try: - rfcnum = int(text) - if rfcnum <= 0: + rfcnum = int(rfcnum) + if rfcnum < 1: raise ValueError except ValueError: msg = inliner.reporter.error( @@ -287,8 +295,10 @@ def rfc_reference_role(role, rawtext, text, lineno, inliner, 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 set_classes(options) - node = nodes.reference(rawtext, 'RFC ' + utils.unescape(text), refuri=ref, + node = nodes.reference(rawtext, 'RFC ' + str(rfcnum), refuri=ref, **options) return [node], [] @@ -308,7 +318,7 @@ def raw_role(role, rawtext, text, lineno, inliner, options={}, content=[]): prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] set_classes(options) - node = nodes.raw(rawtext, utils.unescape(text, 1), **options) + node = nodes.raw(rawtext, utils.unescape(text, True), **options) node.source, node.line = inliner.reporter.get_source_and_line(lineno) return [node], [] @@ -325,9 +335,9 @@ def code_role(role, rawtext, text, lineno, inliner, options={}, content=[]): if language and language not in classes: classes.append(language) try: - tokens = Lexer(utils.unescape(text, 1), language, + tokens = Lexer(utils.unescape(text, True), language, inliner.document.settings.syntax_highlight) - except LexerError, error: + except LexerError as error: msg = inliner.reporter.warning(error) prb = inliner.problematic(rawtext, rawtext, msg) return [prb], [msg] @@ -336,24 +346,21 @@ def code_role(role, rawtext, text, lineno, inliner, options={}, content=[]): # analyse content and add nodes for every token for classes, value in tokens: - # print (classes, value) if classes: node += nodes.inline(value, value, classes=classes) else: # insert as Text to decrease the verbosity of the output - node += nodes.Text(value, value) + node += nodes.Text(value) return [node], [] -code_role.options = {'class': directives.class_option, - 'language': directives.unchanged} +code_role.options = {'language': directives.unchanged} register_canonical_role('code', code_role) def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]): set_classes(options) - i = rawtext.find('`') - text = rawtext.split('`')[1] + text = utils.unescape(text, True) # raw text without inline role markup node = nodes.math(rawtext, text, **options) return [node], [] @@ -363,7 +370,8 @@ register_canonical_role('math', math_role) # Register roles that are currently unimplemented. ###################################################################### -def unimplemented_role(role, rawtext, text, lineno, inliner, attributes={}): +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) diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/states.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/states.py index fa4c507..9b2c780 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/states.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/states.py @@ -1,4 +1,4 @@ -# $Id: states.py 8060 2017-04-19 20:00:04Z milde $ +# $Id: states.py 8885 2021-11-11 16:29:16Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -126,7 +126,7 @@ class ParserError(ApplicationError): pass class MarkupMismatch(Exception): pass -class Struct: +class Struct(object): """Stores data attributes for dotted-attribute access.""" @@ -151,7 +151,7 @@ class RSTStateMachine(StateMachineWS): run the StateMachine. """ self.language = languages.get_language( - document.settings.language_code) + document.settings.language_code, document.reporter) self.match_titles = match_titles if inliner is None: inliner = Inliner() @@ -328,7 +328,7 @@ class RSTState(StateWS): def check_subsection(self, source, style, lineno): """ - Check for a valid subsection header. Return 1 (true) or None (false). + 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 @@ -350,10 +350,10 @@ class RSTState(StateWS): except ValueError: # new title style if len(title_styles) == memo.section_level: # new subsection title_styles.append(style) - return 1 + return True else: # not at lowest level self.parent += self.title_inconsistent(source, lineno) - return None + return False if level <= mylevel: # sibling or supersection memo.section_level = level # bubble up to parent section if len(style) == 2: @@ -362,10 +362,10 @@ class RSTState(StateWS): self.state_machine.previous_line(len(style) + 1) raise EOFError # let parent section re-evaluate if level == mylevel + 1: # immediate subsection - return 1 + return True else: # invalid subsection self.parent += self.title_inconsistent(source, lineno) - return None + return False def title_inconsistent(self, sourcetext, lineno): error = self.reporter.severe( @@ -424,7 +424,9 @@ class RSTState(StateWS): """ Return 2 lists: nodes (text and inline elements), and system_messages. """ - return self.inliner.parse(text, lineno, self.memo, self.parent) + 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 @@ -445,7 +447,7 @@ def build_regexp(definition, compile=True): name, prefix, suffix, parts = definition part_strings = [] for part in parts: - if type(part) is tuple: + if isinstance(part, tuple): part_strings.append(build_regexp(part, None)) else: part_strings.append(part) @@ -457,7 +459,7 @@ def build_regexp(definition, compile=True): return regexp -class Inliner: +class Inliner(object): """ Parse inline markup; call the `parse()` method. @@ -624,6 +626,9 @@ class Inliner: 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 @@ -711,18 +716,19 @@ class Inliner: return (string[:matchend], [], string[matchend:], [], '') endmatch = end_pattern.search(string[matchend:]) if endmatch and endmatch.start(1): # 1 or more chars - text = unescape(endmatch.string[:endmatch.start(1)], - restore_backslashes) + text = endmatch.string[:endmatch.start(1)] + if restore_backslashes: + text = unescape(text, True) textend = matchend + endmatch.end(1) - rawsource = unescape(string[matchstart:textend], 1) - return (string[:matchstart], [nodeclass(rawsource, text)], + 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], 1) - rawsource = unescape(string[matchstart:matchend], 1) - prb = self.problematic(text, rawsource, msg) + 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): @@ -764,25 +770,25 @@ class Inliner: 'Multiple roles in interpreted text (both ' 'prefix and suffix present; only one allowed).', line=lineno) - text = unescape(string[rolestart:textend], 1) + 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], 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], 1) + 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, unescape(escaped)) + rawsource, escaped) else: - rawsource = unescape(string[rolestart:textend], 1) + rawsource = unescape(string[rolestart:textend], True) nodelist, messages = self.interpreted(rawsource, escaped, role, lineno) return (string[:rolestart], nodelist, @@ -790,29 +796,34 @@ class Inliner: msg = self.reporter.warning( 'Inline interpreted text or phrase reference start-string ' 'without end-string.', line=lineno) - text = unescape(string[matchstart:matchend], 1) + 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): + 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 = unescape(escaped[:match.start(0)]) + text = escaped[:match.start(0)] + unescaped = unescape(text) + rawtext = unescape(text, True) aliastext = match.group(2) - underscore_escaped = aliastext.endswith('\x00_') - aliastext = unescape(aliastext) + 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(aliastext[:-1]) + alias = normalize_name(unescape(aliastext[:-1])) target = nodes.target(match.group(1), refname=alias) - target.indirect_reference_name = aliastext[:-1] + 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(unescape(part).split()) + alias = ' '.join(''.join(part.split()) for part in alias_parts) - alias = self.adjust_uri(alias) + alias = self.adjust_uri(unescape(alias)) if alias.endswith(r'\_'): alias = alias[:-2] + '_' target = nodes.target(match.group(1), refuri=alias) @@ -822,12 +833,19 @@ class Inliner: % 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(text) + refname = normalize_name(unescaped) reference = nodes.reference(rawsource, text, - name=whitespace_normalize_name(text)) + name=whitespace_normalize_name(unescaped)) + reference[0].rawsource = rawtext + node_list = [reference] if rawsource[-2:] == '__': @@ -956,6 +974,7 @@ class Inliner: referencenode = nodes.reference( referencename + match.group('refend'), referencename, name=whitespace_normalize_name(referencename)) + referencenode[0].rawsource = referencename if anonymous: referencenode['anonymous'] = 1 else: @@ -977,36 +996,35 @@ class Inliner: else: addscheme = '' text = match.group('whole') - unescaped = unescape(text, 0) - return [nodes.reference(unescape(text, 1), unescaped, - refuri=addscheme + unescaped)] + 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(match.group('pepnum1')) + pepnum = int(unescape(match.group('pepnum1'))) elif text.startswith('PEP'): - pepnum = int(match.group('pepnum2')) + pepnum = int(unescape(match.group('pepnum2'))) else: raise MarkupMismatch ref = (self.document.settings.pep_base_url + self.document.settings.pep_file_url_template % pepnum) - unescaped = unescape(text, 0) - return [nodes.reference(unescape(text, 1), unescaped, refuri=ref)] + 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(match.group('rfcnum')) + rfcnum = int(unescape(match.group('rfcnum'))) ref = self.document.settings.rfc_base_url + self.rfc_url % rfcnum else: raise MarkupMismatch - unescaped = unescape(text, 0) - return [nodes.reference(unescape(text, 1), unescaped, refuri=ref)] + return [nodes.reference(unescape(text, True), text, refuri=ref)] def implicit_inline(self, text, lineno): """ @@ -1028,7 +1046,7 @@ class Inliner: self.implicit_inline(text[match.end():], lineno)) except MarkupMismatch: pass - return [nodes.Text(unescape(text), rawsource=unescape(text, 1))] + return [nodes.Text(text)] dispatch = {'*': emphasis, '**': strong, @@ -1119,7 +1137,7 @@ class Body(RSTState): patterns = { 'bullet': u'[-+*\u2022\u2023\u2043]( +|$)', 'enumerator': r'(%(parens)s|%(rparen)s|%(period)s)( +|$)' % pats, - 'field_marker': r':(?![: ])([^:\\]|\\.)*(?<! ):( +|$)', + 'field_marker': r':(?![: ])([^:\\]|\\.|:(?!([ `]|$)))*(?<! ):( +|$)', 'option_marker': r'%(option)s(, %(option)s)*( +| ?$)' % pats, 'doctest': r'>>>( +|$)', 'line_block': r'\|( +|$)', @@ -1156,17 +1174,19 @@ class Body(RSTState): 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) - blockquote = nodes.block_quote() self.nested_parse(blockquote_lines, line_offset, blockquote) elements.append(blockquote) if attribution_lines: attribution, messages = self.parse_attribution( - attribution_lines, attribution_offset) + attribution_lines, line_offset+attribution_offset) blockquote += attribution elements += messages line_offset = new_line_offset @@ -1188,8 +1208,8 @@ class Body(RSTState): * Every line after that must have consistent indentation. * Attributions must be preceded by block quote content. - Return a tuple of: (block quote content lines, content offset, - attribution lines, attribution offset, remaining indented lines). + Return a tuple of: (block quote content lines, attribution lines, + attribution offset, remaining indented lines, remaining lines offset). """ blank = None nonblank_seen = False @@ -1236,7 +1256,7 @@ class Body(RSTState): def parse_attribution(self, indented, line_offset): text = '\n'.join(indented).rstrip() - lineno = self.state_machine.abs_line_number() + line_offset + 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) @@ -1480,7 +1500,7 @@ class Body(RSTState): (optionlist.source, optionlist.line) = self.state_machine.get_source_and_line() try: listitem, blank_finish = self.option_list_item(match) - except MarkupError, error: + except MarkupError as error: # This shouldn't happen; pattern won't match. msg = self.reporter.error(u'Invalid option list marker: %s' % error) @@ -1669,7 +1689,7 @@ class Body(RSTState): + 1) table = self.build_table(tabledata, tableline) nodelist = [table] + messages - except tableparser.TableMarkupError, err: + except tableparser.TableMarkupError as err: nodelist = self.malformed_table(block, ' '.join(err.args), offset=err.offset) + messages else: @@ -1681,7 +1701,7 @@ class Body(RSTState): blank_finish = 1 try: block = self.state_machine.get_text_block(flush_left=True) - except statemachine.UnexpectedIndentationError, err: + except statemachine.UnexpectedIndentationError as err: block, src, srcline = err.args messages.append(self.reporter.error('Unexpected indentation.', source=src, line=srcline)) @@ -2044,11 +2064,12 @@ class Body(RSTState): del substitution_node[i] else: i += 1 - for node in substitution_node.traverse(nodes.Element): + 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:', + 'Substitution definition contains illegal element <%s>:' + % node.tagname, pformat, nodes.literal_block(blocktext, blocktext), source=src, line=srcline) return [msg], blank_finish @@ -2066,9 +2087,9 @@ class Body(RSTState): if (node['ids'] or isinstance(node, nodes.reference) and node.get('anonymous') or isinstance(node, nodes.footnote_reference) and node.get('auto')): - return 1 + return True else: - return 0 + return False def directive(self, match, **option_presets): """Returns a 2-tuple: list of nodes, and a "blank finish" boolean.""" @@ -2117,7 +2138,7 @@ class Body(RSTState): arguments, options, content, content_offset = ( self.parse_directive_block(indented, line_offset, directive, option_presets)) - except MarkupError, detail: + except MarkupError as detail: error = self.reporter.error( 'Error in "%s" directive:\n%s.' % (type_name, ' '.join(detail.args)), @@ -2128,7 +2149,7 @@ class Body(RSTState): content_offset, block_text, self, self.state_machine) try: result = directive_instance.run() - except docutils.parsers.rst.DirectiveError, error: + 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) @@ -2245,11 +2266,11 @@ class Body(RSTState): return 0, 'invalid option block' try: options = utils.extract_extension_options(node, option_spec) - except KeyError, detail: + except KeyError as detail: return 0, ('unknown option: "%s"' % detail.args[0]) - except (ValueError, TypeError), detail: + except (ValueError, TypeError) as detail: return 0, ('invalid option value: %s' % ' '.join(detail.args)) - except utils.ExtensionOptionError, detail: + except utils.ExtensionOptionError as detail: return 0, ('invalid option data: %s' % ' '.join(detail.args)) if blank_finish: return 1, options @@ -2267,9 +2288,14 @@ class Body(RSTState): return [error], blank_finish def comment(self, match): - if not match.string[match.end():].strip() \ - and self.state_machine.is_next_line_blank(): # an empty comment? - return [nodes.comment()], 1 # "A tiny but practical wart." + 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(): @@ -2336,7 +2362,7 @@ class Body(RSTState): if expmatch: try: return method(self, expmatch) - except MarkupError, error: + except MarkupError as error: lineno = self.state_machine.abs_line_number() message = ' '.join(error.args) errors.append(self.reporter.warning(message, line=lineno)) @@ -2683,7 +2709,7 @@ class Text(RSTState): def blank(self, match, context, next_state): """End of paragraph.""" - # NOTE: self.paragraph returns [ node, system_message(s) ], literalnext + # NOTE: self.paragraph returns [node, system_message(s)], literalnext paragraph, literalnext = self.paragraph( context, self.state_machine.abs_line_number() - 1) self.parent += paragraph @@ -2740,8 +2766,8 @@ class Text(RSTState): 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() + # 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) @@ -2759,7 +2785,7 @@ class Text(RSTState): msg = None try: block = self.state_machine.get_text_block(flush_left=True) - except statemachine.UnexpectedIndentationError, err: + except statemachine.UnexpectedIndentationError as err: block, src, srcline = err.args msg = self.reporter.error('Unexpected indentation.', source=src, line=srcline) @@ -2785,7 +2811,8 @@ class Text(RSTState): return self.quoted_literal_block() data = '\n'.join(indented) literal_block = nodes.literal_block(data, data) - literal_block.line = offset + 1 + (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')) @@ -2829,23 +2856,23 @@ class Text(RSTState): """Return a definition_list's term and optional classifiers.""" assert len(lines) == 1 text_nodes, messages = self.inline_text(lines[0], lineno) - term_node = nodes.term() + term_node = nodes.term(lines[0]) (term_node.source, term_node.line) = self.state_machine.get_source_and_line(lineno) - term_node.rawsource = unescape(lines[0]) node_list = [term_node] for i in range(len(text_nodes)): node = text_nodes[i] if isinstance(node, nodes.Text): - parts = self.classifier_delimiter.split(node.rawsource) + parts = self.classifier_delimiter.split(node) if len(parts) == 1: node_list[-1] += node else: - - node_list[-1] += nodes.Text(parts[0].rstrip()) + text = parts[0].rstrip() + textnode = nodes.Text(text) + node_list[-1] += textnode for part in parts[1:]: - classifier_node = nodes.classifier('', part) - node_list.append(classifier_node) + node_list.append( + nodes.classifier(unescape(part, True), part)) else: node_list[-1] += node return node_list, messages @@ -2908,9 +2935,12 @@ class Line(SpecializedText): elif len(marker) < 4: self.state_correction(context) if self.eofcheck: # ignore EOFError with sections - lineno = self.state_machine.abs_line_number() - 1 + src, srcline = self.state_machine.get_source_and_line() + # lineno = self.state_machine.abs_line_number() - 1 transition = nodes.transition(rawsource=context[0]) - transition.line = lineno + transition.source = src + transition.line = srcline - 1 + # transition.line = lineno self.parent += transition self.eofcheck = 1 return [] diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py index e19388b..5539a74 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py @@ -1,4 +1,4 @@ -# $Id: tableparser.py 7898 2015-05-29 20:49:28Z milde $ +# $Id: tableparser.py 8373 2019-08-27 12:11:30Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -40,7 +40,7 @@ class TableMarkupError(DataError): DataError.__init__(self, *args) -class TableParser: +class TableParser(object): """ Abstract superclass for the common parts of the syntax-specific parsers. @@ -286,13 +286,11 @@ class GridTableParser(TableParser): From the data collected by `scan_cell()`, convert to the final data structure. """ - rowseps = self.rowseps.keys() # list of row boundaries - rowseps.sort() + 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 = self.colseps.keys() # list of column boundaries - colseps.sort() + 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 @@ -498,7 +496,7 @@ class SimpleTableParser(TableParser): """ # "Infinite" value for a dummy last column's beginning, used to # check for text overflow: - columns.append((sys.maxint, None)) + 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] diff --git a/docutils/src/main/resources/docutils/docutils/readers/__init__.py b/docutils/src/main/resources/docutils/docutils/readers/__init__.py index 689d235..37911c7 100644 --- a/docutils/src/main/resources/docutils/docutils/readers/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/readers/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7648 2013-04-18 07:36:22Z milde $ +# $Id: __init__.py 8478 2020-01-30 12:29:29Z milde $ # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer # Copyright: This module has been placed in the public domain. @@ -9,11 +9,10 @@ This package contains Docutils Reader modules. __docformat__ = 'reStructuredText' import sys +from importlib import import_module from docutils import utils, parsers, Component from docutils.transforms import universal -if sys.version_info < (2,5): - from docutils._compat import __import__ class Reader(Component): @@ -23,8 +22,9 @@ class Reader(Component): Each reader module or package must export a subclass also called 'Reader'. - The two steps of a Reader's responsibility are `scan()` and - `parse()`. Call `read()` to process a document. + 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' @@ -107,7 +107,7 @@ def get_reader_class(reader_name): if reader_name in _reader_aliases: reader_name = _reader_aliases[reader_name] try: - module = __import__(reader_name, globals(), locals(), level=1) + module = import_module('docutils.readers.'+reader_name) except ImportError: - module = __import__(reader_name, globals(), locals(), level=0) + module = import_module(reader_name) return module.Reader diff --git a/docutils/src/main/resources/docutils/docutils/readers/standalone.py b/docutils/src/main/resources/docutils/docutils/readers/standalone.py index 3c302ed..1719c1c 100644 --- a/docutils/src/main/resources/docutils/docutils/readers/standalone.py +++ b/docutils/src/main/resources/docutils/docutils/readers/standalone.py @@ -1,4 +1,4 @@ -# $Id: standalone.py 4802 2006-11-12 18:02:17Z goodger $ +# $Id: standalone.py 8722 2021-05-17 20:28:42Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -23,7 +23,7 @@ class Reader(readers.Reader): """A single document tree.""" settings_spec = ( - 'Standalone Reader', + 'Standalone Reader Options', None, (('Disable the promotion of a lone top-level section title to ' 'document title (and subsequent section title to document ' diff --git a/docutils/src/main/resources/docutils/docutils/statemachine.py b/docutils/src/main/resources/docutils/docutils/statemachine.py index 653e7c8..6388007 100644 --- a/docutils/src/main/resources/docutils/docutils/statemachine.py +++ b/docutils/src/main/resources/docutils/docutils/statemachine.py @@ -1,4 +1,4 @@ - # $Id: statemachine.py 7464 2012-06-25 13:16:03Z milde $ + # $Id: statemachine.py 8886 2021-11-14 22:00:50Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -103,18 +103,22 @@ How To Use This Module sm.unlink() """ +from __future__ import print_function __docformat__ = 'restructuredtext' import sys import re -import types -import unicodedata +from unicodedata import east_asian_width + from docutils import utils from docutils.utils.error_reporting import ErrorOutput +if sys.version_info >= (3, 0): + unicode = str # noqa + -class StateMachine: +class StateMachine(object): """ A finite state machine for text filters using regular expressions. @@ -213,15 +217,14 @@ class StateMachine: self.line_offset = -1 self.current_state = initial_state or self.initial_state if self.debug: - print >>self._stderr, ( - u'\nStateMachine.run: input_lines (line_offset=%s):\n| %s' - % (self.line_offset, u'\n| '.join(self.input_lines))) + print(u'\nStateMachine.run: input_lines (line_offset=%s):\n| %s' + % (self.line_offset, u'\n| '.join(self.input_lines)), file=self._stderr) transitions = None results = [] state = self.get_state() try: if self.debug: - print >>self._stderr, '\nStateMachine.run: bof transition' + print('\nStateMachine.run: bof transition', file=self._stderr) context, result = state.bof(context) results.extend(result) while True: @@ -231,32 +234,29 @@ class StateMachine: if self.debug: source, offset = self.input_lines.info( self.line_offset) - print >>self._stderr, ( - u'\nStateMachine.run: line (source=%r, ' - u'offset=%r):\n| %s' - % (source, offset, self.line)) + print(u'\nStateMachine.run: line (source=%r, ' + u'offset=%r):\n| %s' + % (source, offset, self.line), file=self._stderr) context, next_state, result = self.check_line( context, state, transitions) except EOFError: if self.debug: - print >>self._stderr, ( - '\nStateMachine.run: %s.eof transition' - % state.__class__.__name__) + print('\nStateMachine.run: %s.eof transition' + % state.__class__.__name__, file=self._stderr) result = state.eof(context) results.extend(result) break else: results.extend(result) - except TransitionCorrection, exception: + except TransitionCorrection as exception: self.previous_line() # back up for another try transitions = (exception.args[0],) if self.debug: - print >>self._stderr, ( - '\nStateMachine.run: TransitionCorrection to ' + print('\nStateMachine.run: TransitionCorrection to ' 'state "%s", transition %s.' - % (state.__class__.__name__, transitions[0])) + % (state.__class__.__name__, transitions[0]), file=self._stderr) continue - except StateCorrection, exception: + except StateCorrection as exception: self.previous_line() # back up for another try next_state = exception.args[0] if len(exception.args) == 1: @@ -264,10 +264,9 @@ class StateMachine: else: transitions = (exception.args[1],) if self.debug: - print >>self._stderr, ( - '\nStateMachine.run: StateCorrection to state ' + print('\nStateMachine.run: StateCorrection to state ' '"%s", transition %s.' - % (next_state, transitions[0])) + % (next_state, transitions[0]), file=self._stderr) else: transitions = None state = self.get_state(next_state) @@ -288,11 +287,10 @@ class StateMachine: """ if next_state: if self.debug and next_state != self.current_state: - print >>self._stderr, ( - '\nStateMachine.get_state: Changing state from ' - '"%s" to "%s" (input line %s).' - % (self.current_state, next_state, - self.abs_line_number())) + print('\nStateMachine.get_state: Changing state from ' + '"%s" to "%s" (input line %s).' + % (self.current_state, next_state, + self.abs_line_number()), file=self._stderr) self.current_state = next_state try: return self.states[self.current_state] @@ -313,7 +311,7 @@ class StateMachine: self.notify_observers() def is_next_line_blank(self): - """Return 1 if the next line is blank or non-existant.""" + """Return True if the next line is blank or non-existent.""" try: return not self.input_lines[self.line_offset + 1].strip() except IndexError: @@ -382,15 +380,11 @@ class StateMachine: # 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 + 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()))) - # assert offset == srcoffset, str(self.input_lines) - # print "get_source_and_line(%s):" % lineno, - # print offset + 1, '->', src, srcline - # print self.input_lines return (src, srcline) def insert_input(self, input_lines, source): @@ -416,7 +410,7 @@ class StateMachine: flush_left) self.next_line(len(block) - 1) return block - except UnexpectedIndentationError, err: + except UnexpectedIndentationError as err: block = err.args[0] self.next_line(len(block) - 1) # advance to last line of block raise @@ -445,24 +439,21 @@ class StateMachine: transitions = state.transition_order state_correction = None if self.debug: - print >>self._stderr, ( - '\nStateMachine.check_line: state="%s", transitions=%r.' - % (state.__class__.__name__, transitions)) + print('\nStateMachine.check_line: state="%s", transitions=%r.' + % (state.__class__.__name__, transitions), file=self._stderr) for name in transitions: pattern, method, next_state = state.transitions[name] match = pattern.match(self.line) if match: if self.debug: - print >>self._stderr, ( - '\nStateMachine.check_line: Matched transition ' + print('\nStateMachine.check_line: Matched transition ' '"%s" in state "%s".' - % (name, state.__class__.__name__)) + % (name, state.__class__.__name__), file=self._stderr) return method(match, context, next_state) else: if self.debug: - print >>self._stderr, ( - '\nStateMachine.check_line: No match in state "%s".' - % state.__class__.__name__) + print('\nStateMachine.check_line: No match in state "%s".' + % state.__class__.__name__, file=self._stderr) return state.no_match(context, transitions) def add_state(self, state_class): @@ -494,10 +485,10 @@ class StateMachine: def error(self): """Report error details.""" type, value, module, line, function = _exception_data() - print >>self._stderr, u'%s: %s' % (type, value) - print >>self._stderr, 'input line %s' % (self.abs_line_number()) - print >>self._stderr, (u'module %s, line %s, function %s' % - (module, line, function)) + print(u'%s: %s' % (type, value), file=self._stderr) + print('input line %s' % (self.abs_line_number()), file=self._stderr) + print((u'module %s, line %s, function %s' % + (module, line, function)), file=self._stderr) def attach_observer(self, observer): """ @@ -518,7 +509,7 @@ class StateMachine: observer(*info) -class State: +class State(object): """ State superclass. Contains a list of transitions, and transition methods. @@ -530,7 +521,7 @@ class State: ``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 retured context is passed on to the next transition + 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 @@ -715,7 +706,7 @@ class State: try: pattern = self.patterns[name] if not hasattr(pattern, 'match'): - pattern = re.compile(pattern) + pattern = self.patterns[name] = re.compile(pattern) except KeyError: raise TransitionPatternNotFound( '%s.patterns[%r]' % (self.__class__.__name__, name)) @@ -738,7 +729,7 @@ class State: names = [] transitions = {} for namestate in name_list: - if type(namestate) is stringtype: + if isinstance(namestate, stringtype): transitions[namestate] = self.make_transition(namestate) names.append(namestate) else: @@ -859,7 +850,7 @@ class StateMachineWS(StateMachine): offset += 1 return indented, offset, blank_finish - def get_first_known_indented(self, indent, until_blank=False, + def get_first_known_indented(self, indent, until_blank=False, strip_indent=True, strip_top=True): """ Return an indented block and info. @@ -945,8 +936,8 @@ class StateWS(State): `indent_sm_kwargs`. Override it in subclasses to avoid the default. """ - ws_patterns = {'blank': ' *$', - 'indent': ' +'} + ws_patterns = {'blank': re.compile(' *$'), + 'indent': re.compile(' +')} """Patterns for default whitespace transitions. May be overridden in subclasses.""" @@ -1034,7 +1025,7 @@ class StateWS(State): return context, next_state, results -class _SearchOverride: +class _SearchOverride(object): """ Mix-in class to override `StateMachine` regular expression behavior. @@ -1067,7 +1058,7 @@ class SearchStateMachineWS(_SearchOverride, StateMachineWS): pass -class ViewList: +class ViewList(object): """ List with extended functionality: slices of ViewList objects are child @@ -1127,7 +1118,12 @@ class ViewList: def __ne__(self, other): return self.data != self.__cast(other) def __gt__(self, other): return self.data > self.__cast(other) def __ge__(self, other): return self.data >= self.__cast(other) - def __cmp__(self, other): return cmp(self.data, self.__cast(other)) + + def __cmp__(self, other): + # from https://docs.python.org/3.0/whatsnew/3.0.html + mine = self.data + yours = self.__cast(other) + return (mine > yours) - (yours < mine) def __cast(self, other): if isinstance(other, ViewList): @@ -1143,7 +1139,7 @@ class ViewList: # just works. def __getitem__(self, i): - if isinstance(i, types.SliceType): + 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], @@ -1152,7 +1148,7 @@ class ViewList: return self.data[i] def __setitem__(self, i, item): - if isinstance(i, types.SliceType): + 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') @@ -1293,8 +1289,7 @@ class ViewList: self.parent = None def sort(self, *args): - tmp = zip(self.data, self.items) - tmp.sort(*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 @@ -1329,14 +1324,14 @@ class ViewList: def pprint(self): """Print the list in `grep` format (`source:offset:value` lines)""" for line in self.xitems(): - print "%s:%d:%s" % line + print("%s:%d:%s" % line) class StringList(ViewList): """A `ViewList` with string-specific methods.""" - def trim_left(self, length, start=0, end=sys.maxint): + 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 @@ -1384,7 +1379,7 @@ class StringList(ViewList): - `first_indent`: The indent of the first line, if known. :Return: - - a StringList of indented lines with mininum indent removed; + - 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? """ @@ -1452,10 +1447,6 @@ class StringList(ViewList): Pad all double-width characters in self by appending `pad_char` to each. For East Asian language support. """ - if hasattr(unicodedata, 'east_asian_width'): - east_asian_width = unicodedata.east_asian_width - else: - return # new in Python 2.4 for i in range(len(self.data)): line = self.data[i] if isinstance(line, unicode): @@ -1500,7 +1491,6 @@ class StateCorrection(Exception): transition name. """ - def string2lines(astring, tab_width=8, convert_whitespace=False, whitespace=re.compile('[\v\f]')): """ @@ -1515,10 +1505,13 @@ def string2lines(astring, tab_width=8, convert_whitespace=False, - `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()] + lines = [s.expandtabs(tab_width).rstrip() for s in astring.splitlines()] + return lines def _exception_data(): """ diff --git a/docutils/src/main/resources/docutils/docutils/transforms/__init__.py b/docutils/src/main/resources/docutils/docutils/transforms/__init__.py index 5b6926a..3be3349 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 6433 2010-09-28 08:21:25Z milde $ +# $Id: __init__.py 8358 2019-08-26 16:45:09Z milde $ # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer # Copyright: This module has been placed in the public domain. @@ -30,7 +30,7 @@ from docutils import languages, ApplicationError, TransformSpec class TransformError(ApplicationError): pass -class Transform: +class Transform(object): """ Docutils transform component abstract base class. @@ -70,8 +70,9 @@ class Transformer(TransformSpec): def __init__(self, document): self.transforms = [] - """List of transforms to apply. Each item is a 3-tuple: - ``(priority string, transform class, pending node or None)``.""" + """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""" @@ -152,9 +153,8 @@ class Transformer(TransformSpec): unknown_reference_resolvers = [] for i in components: unknown_reference_resolvers.extend(i.unknown_reference_resolvers) - decorated_list = [(f.priority, f) for f in unknown_reference_resolvers] - decorated_list.sort() - self.unknown_reference_resolvers.extend([f[1] for f in decorated_list]) + decorated_list = sorted((f.priority, f) for f in unknown_reference_resolvers) + self.unknown_reference_resolvers.extend(f[1] for f in decorated_list) def apply_transforms(self): """Apply all of the stored transforms, in priority order.""" diff --git a/docutils/src/main/resources/docutils/docutils/transforms/components.py b/docutils/src/main/resources/docutils/docutils/transforms/components.py index d3e548c..fcc624a 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/components.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/components.py @@ -1,4 +1,4 @@ -# $Id: components.py 4564 2006-05-21 20:44:42Z wiemann $ +# $Id: components.py 8766 2021-06-17 14:33:09Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -32,21 +32,29 @@ class Filter(Transform): ``details['nodes']`` (a list of nodes); otherwise, the "pending" element is removed. - For example, the reStructuredText "meta" directive creates a "pending" - element containing a "meta" element (in ``pending.details['nodes']``). - Only writers (``pending.details['component'] == 'writer'``) supporting the - "html" format (``pending.details['format'] == 'html'``) will include the - "meta" element; it will be deleted from the output of all other writers. + 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' - format = pending.details['format'] + formats = (pending.details['format']).split(',') component = self.document.transformer.components[component_type] - if component.supports(format): - pending.replace_self(pending.details['nodes']) + for format in formats: + if component.supports(format): + pending.replace_self(pending.details['nodes']) + break else: pending.parent.remove(pending) diff --git a/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py b/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py index 92287da..c2f70e2 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py @@ -1,4 +1,4 @@ -# $Id: frontmatter.py 8117 2017-06-18 23:38:18Z milde $ +# $Id: frontmatter.py 8885 2021-11-11 16:29:16Z milde $ # Author: David Goodger, Ueli Schlaepfer <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -22,10 +22,16 @@ Transforms related to the front matter of a document or a section __docformat__ = 'reStructuredText' import re +import sys + from docutils import nodes, utils from docutils.transforms import TransformError, Transform +if sys.version_info >= (3, 0): + unicode = str # noqa + + class TitlePromoter(Transform): """ @@ -51,21 +57,20 @@ class TitlePromoter(Transform): """ # Type check if not isinstance(node, nodes.Element): - raise TypeError, 'node must be of Element-derived type.' + 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 None + return False # Transfer the section's attributes to the node: - # NOTE: Change second parameter to False to NOT replace - # attributes that already exist in node with those in - # section - # NOTE: Remove third parameter to NOT copy the 'source' + # 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, True, True) + node.update_all_atts_concatenating(section, replace=True, and_source=True) # setup_child is called automatically for all nodes. node[:] = (section[:1] # section title @@ -73,7 +78,7 @@ class TitlePromoter(Transform): # node before the section + section[1:]) # everything that was in the section assert isinstance(node[0], nodes.title) - return 1 + return True def promote_subtitle(self, node): """ @@ -94,20 +99,19 @@ class TitlePromoter(Transform): """ # Type check if not isinstance(node, nodes.Element): - raise TypeError, 'node must be of Element-derived type.' + raise TypeError('node must be of Element-derived type.') subsection, index = self.candidate_index(node) if index is None: - return None + return False subtitle = nodes.subtitle() # Transfer the subsection's attributes to the new subtitle - # NOTE: Change second parameter to False to NOT replace - # attributes that already exist in node with those in - # section - # NOTE: Remove third parameter to NOT copy the 'source' - # attribute from section - subtitle.update_all_atts_concatenating(subsection, True, True) + # 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: @@ -118,7 +122,7 @@ class TitlePromoter(Transform): + node[1:index] # everything that was in the subsection: + subsection[1:]) - return 1 + return True def candidate_index(self, node): """ @@ -128,8 +132,8 @@ class TitlePromoter(Transform): """ 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): + if (index is None or len(node) > (index + 1) + or not isinstance(node[index], nodes.section)): return None, None else: return node[index], index @@ -237,7 +241,7 @@ class DocTitle(TitlePromoter): self.document['title'] = self.document[0].astext() def apply(self): - if getattr(self.document.settings, 'doctitle_xform', 1): + 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 @@ -275,13 +279,12 @@ class SectionSubTitle(TitlePromoter): default_priority = 350 def apply(self): - if not getattr(self.document.settings, 'sectsubtitle_xform', 1): + if not self.document.settings.setdefault('sectsubtitle_xform', True): return - for section in self.document.traverse(nodes.section): - # On our way through the node tree, we are deleting - # sections, but we call self.promote_subtitle for those - # sections nonetheless. To do: Write a test case which - # shows the problem and discuss on Docutils-develop. + 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) @@ -380,7 +383,7 @@ class DocInfo(Transform): bibliographic fields (field_list).""" def apply(self): - if not getattr(self.document.settings, 'docinfo_xform', 1): + if not self.document.settings.setdefault('docinfo_xform', True): return document = self.document index = document.first_child_not_matching_class( @@ -390,7 +393,7 @@ class DocInfo(Transform): candidate = document[index] if isinstance(candidate, nodes.field_list): biblioindex = document.first_child_not_matching_class( - (nodes.Titular, nodes.Decorative)) + (nodes.Titular, nodes.Decorative, nodes.meta)) nodelist = self.extract_bibliographic(candidate) del document[index] # untransformed field list (candidate) document[biblioindex:biblioindex] = nodelist @@ -424,6 +427,7 @@ class DocInfo(Transform): 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: @@ -433,10 +437,10 @@ class DocInfo(Transform): 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) + # if normedname not in bibliofields: + classvalue = nodes.make_id(normedname) + if classvalue: + field['classes'].append(classvalue) docinfo.append(field) nodelist = [] if len(docinfo) != 0: @@ -503,20 +507,30 @@ class DocInfo(Transform): raise def authors_from_one_paragraph(self, field): - text = field[1][0].astext().strip() + """Return list of Text nodes for authornames. + + The set of separators is locale dependent (default: ";"- or ","). + """ + # @@ keep original formatting? (e.g. ``:authors: A. Test, *et-al*``) + text = ''.join(unicode(node) + for node in field[1].findall(nodes.Text)) if not text: raise TransformError for authorsep in self.language.author_separators: - authornames = text.split(authorsep) + # don't split at escaped `authorsep`: + pattern = '(?<!\x00)%s' % authorsep + authornames = re.split(pattern, text) if len(authornames) > 1: break - authornames = [author.strip() for author in authornames] - authors = [[nodes.Text(author)] for author in authornames if author] + authornames = (name.strip() for name in authornames) + authors = [[nodes.Text(name)] for name in authornames if name] return authors 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) @@ -526,7 +540,8 @@ class DocInfo(Transform): def authors_from_paragraphs(self, field): for item in field[1]: - if not isinstance(item, nodes.paragraph): + if not isinstance(item, (nodes.paragraph, nodes.comment)): raise TransformError - authors = [item.children for item in field[1]] + authors = [item.children for item in field[1] + if not isinstance(item, nodes.comment)] return authors diff --git a/docutils/src/main/resources/docutils/docutils/transforms/misc.py b/docutils/src/main/resources/docutils/docutils/transforms/misc.py index cd68ee1..4cbd545 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/misc.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/misc.py @@ -1,4 +1,4 @@ -# $Id: misc.py 6314 2010-04-26 10:04:17Z milde $ +# $Id: misc.py 8885 2021-11-11 16:29:16Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -94,7 +94,7 @@ class Transitions(Transform): default_priority = 830 def apply(self): - for node in self.document.traverse(nodes.transition): + for node in self.document.findall(nodes.transition): self.visit_transition(node) def visit_transition(self, node): diff --git a/docutils/src/main/resources/docutils/docutils/transforms/parts.py b/docutils/src/main/resources/docutils/docutils/transforms/parts.py index ec6eda3..a4bc15e 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/parts.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/parts.py @@ -1,4 +1,4 @@ -# $Id: parts.py 6073 2009-08-06 12:21:10Z milde $ +# $Id: parts.py 8826 2021-09-11 11:18:39Z milde $ # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Dmitry Jemerov # Copyright: This module has been placed in the public domain. @@ -37,7 +37,7 @@ class SectNum(Transform): self.startnode.parent.remove(self.startnode) if self.document.settings.sectnum_xform: if self.maxdepth is None: - self.maxdepth = sys.maxint + 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 @@ -86,10 +86,8 @@ class Contents(Transform): default_priority = 720 def apply(self): - try: # let the writer (or output software) build the contents list? - toc_by_writer = self.document.settings.use_latex_toc - except AttributeError: - toc_by_writer = False + # 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 @@ -120,14 +118,15 @@ class Contents(Transform): sections = [sect for sect in node if isinstance(sect, nodes.section)] entries = [] autonum = 0 - depth = self.startnode.details.get('depth', sys.maxint) + 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) + 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') @@ -142,8 +141,8 @@ class Contents(Transform): entries.append(item) if entries: contents = nodes.bullet_list('', *entries) - if auto: - contents['classes'].append('auto-toc') + if auto: # auto-numbered sections + contents['classes'].append('auto-toc') # auto-numbered sections return contents else: return [] @@ -174,7 +173,6 @@ class ContentsFilter(nodes.TreeCopyVisitor): def ignore_node_but_process_children(self, node): raise nodes.SkipDeparture - visit_interpreted = ignore_node_but_process_children visit_problematic = ignore_node_but_process_children visit_reference = ignore_node_but_process_children visit_target = ignore_node_but_process_children diff --git a/docutils/src/main/resources/docutils/docutils/transforms/peps.py b/docutils/src/main/resources/docutils/docutils/transforms/peps.py index 94b47c1..e5182ba 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/peps.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/peps.py @@ -1,4 +1,4 @@ -# $Id: peps.py 7995 2016-12-10 17:50:59Z milde $ +# $Id: peps.py 8527 2020-07-14 16:41:15Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -92,9 +92,12 @@ class Headers(Transform): 'a single paragraph:\n%s' % field.pformat(level=1)) elif name == 'last-modified': - date = time.strftime( - '%d-%b-%Y', - time.localtime(os.stat(self.document['source'])[8])) + 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)) diff --git a/docutils/src/main/resources/docutils/docutils/transforms/references.py b/docutils/src/main/resources/docutils/docutils/transforms/references.py index f271067..007a026 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/references.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/references.py @@ -1,4 +1,5 @@ -# $Id: references.py 8067 2017-05-04 20:10:03Z milde $ +# .. coding: utf-8 +# $Id: references.py 8885 2021-11-11 16:29:16Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -40,49 +41,52 @@ class PropagateTargets(Transform): default_priority = 260 def apply(self): - for target in self.document.traverse(nodes.target): - # Only block-level targets without reference (like ".. target:"): + 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 not None and - ((not isinstance(next_node, nodes.Invisible) and - not isinstance(next_node, nodes.Targetable)) or - isinstance(next_node, nodes.target))): - 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) + 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): @@ -114,10 +118,10 @@ class AnonymousHyperlinks(Transform): def apply(self): anonymous_refs = [] anonymous_targets = [] - for node in self.document.traverse(nodes.reference): + for node in self.document.findall(nodes.reference): if node.get('anonymous'): anonymous_refs.append(node) - for node in self.document.traverse(nodes.target): + for node in self.document.findall(nodes.target): if node.get('anonymous'): anonymous_targets.append(node) if len(anonymous_refs) \ @@ -350,7 +354,7 @@ class ExternalTargets(Transform): default_priority = 640 def apply(self): - for target in self.document.traverse(nodes.target): + for target in self.document.findall(nodes.target): if target.hasattr('refuri'): refuri = target['refuri'] for name in target['names']: @@ -370,7 +374,7 @@ class InternalTargets(Transform): default_priority = 660 def apply(self): - for target in self.document.traverse(nodes.target): + for target in self.document.findall(nodes.target): if not target.hasattr('refuri') and not target.hasattr('refid'): self.resolve_reference_ids(target) @@ -415,10 +419,10 @@ class Footnotes(Transform): <document> <paragraph> - A labeled autonumbered footnote referece: + A labeled autonumbered footnote reference: <footnote_reference auto="1" id="id1" refname="footnote"> <paragraph> - An unlabeled autonumbered footnote referece: + An unlabeled autonumbered footnote reference: <footnote_reference auto="1" id="id2"> <footnote auto="1" id="id3"> <paragraph> @@ -437,11 +441,11 @@ class Footnotes(Transform): <document> <paragraph> - A labeled autonumbered footnote referece: + A labeled autonumbered footnote reference: <footnote_reference auto="1" id="id1" refid="footnote"> 2 <paragraph> - An unlabeled autonumbered footnote referece: + An unlabeled autonumbered footnote reference: <footnote_reference auto="1" id="id2" refid="id3"> 1 <footnote auto="1" id="id3" backrefs="id2"> @@ -475,17 +479,17 @@ class Footnotes(Transform): # Entries 1-4 and 6 below are from section 12.51 of # The Chicago Manual of Style, 14th edition. '*', # asterisk/star - u'\u2020', # dagger † - u'\u2021', # double dagger ‡ - u'\u00A7', # section mark § - u'\u00B6', # paragraph mark (pilcrow) ¶ + u'\u2020', # † † dagger + u'\u2021', # ‡ ‡ double dagger + u'\u00A7', # § § section mark + u'\u00B6', # ¶ ¶ paragraph mark (pilcrow) # (parallels ['||'] in CMoS) '#', # number sign # The entries below were chosen arbitrarily. - u'\u2660', # spade suit ♠ - u'\u2665', # heart suit ♥ - u'\u2666', # diamond suit ♦ - u'\u2663', # club suit ♣ + u'\u2660', # ♠ ♠ spade suit + u'\u2665', # ♡ ♥ heart suit + u'\u2666', # ♢ ♦ diamond suit + u'\u2663', # ♣ ♣ club suit ] def apply(self): @@ -658,93 +662,99 @@ class Substitutions(Transform): default_priority = 220 """The Substitutions transform has to be applied very early, before - `docutils.tranforms.frontmatter.DocTitle` and others.""" + `docutils.transforms.frontmatter.DocTitle` and others.""" def apply(self): defs = self.document.substitution_defs normed = self.document.substitution_names - subreflist = self.document.traverse(nodes.substitution_reference) 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'] - key = None if refname in defs: key = refname else: normed_name = refname.lower() - if normed_name in normed: - key = normed[normed_name] + 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) - else: - subdef = defs[key] + 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 - 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.replace(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.replace(parent[index + 1], - parent[index + 1].lstrip()) - subdef_copy = subdef.deepcopy() - try: - # Take care of nested substitution references: - for nested_ref in subdef_copy.traverse( - nodes.substitution_reference): - nested_name = normed[nested_ref['refname'].lower()] - if nested_name in nested.setdefault(nested_name, []): - raise CircularSubstitutionDefinitionError - else: - 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 cased 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) + 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: - ref.replace_self(subdef_copy.children) - # register refname of the replacment 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) + # 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): @@ -767,7 +777,7 @@ class TargetNotes(Transform): def apply(self): notes = {} nodelist = [] - for target in self.document.traverse(nodes.target): + for target in self.document.findall(nodes.target): # Only external targets. if not target.hasattr('refuri'): continue @@ -783,7 +793,7 @@ class TargetNotes(Transform): notes[target['refuri']] = footnote nodelist.append(footnote) # Take care of anonymous references. - for ref in self.document.traverse(nodes.reference): + for ref in self.document.findall(nodes.reference): if not ref.get('anonymous'): continue if ref.hasattr('refuri'): @@ -846,7 +856,7 @@ class DanglingReferences(Transform): self.document.walk(visitor) # *After* resolving all references, check for unreferenced # targets: - for target in self.document.traverse(nodes.target): + for target in self.document.findall(nodes.target): if not target.referenced: if target.get('anonymous'): # If we have unreferenced anonymous targets, there @@ -868,7 +878,7 @@ class DanglingReferences(Transform): class DanglingReferencesVisitor(nodes.SparseNodeVisitor): - + def __init__(self, document, unknown_reference_resolvers): nodes.SparseNodeVisitor.__init__(self, document) self.document = document diff --git a/docutils/src/main/resources/docutils/docutils/transforms/universal.py b/docutils/src/main/resources/docutils/docutils/transforms/universal.py index 40036c0..0e0bcfd 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/universal.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/universal.py @@ -1,4 +1,4 @@ -# $Id: universal.py 8144 2017-07-26 21:25:08Z milde $ +# $Id: universal.py 8885 2021-11-11 16:29:16Z milde $ # -*- coding: utf-8 -*- # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Günter Milde # Maintainer: docutils-develop@lists.sourceforge.net @@ -8,10 +8,15 @@ Transforms needed by most or all documents: - `Decorations`: Generate a document's header & footer. -- `Messages`: Placement of system messages stored in - `nodes.document.transform_messages`. +- `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. -- `FinalReferences`: Resolve remaining references. +- `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' @@ -23,6 +28,11 @@ from docutils import nodes, utils from docutils.transforms import TransformError, Transform from docutils.utils import smartquotes + +if sys.version_info >= (3, 0): + unicode = str # noqa + + class Decorations(Transform): """ @@ -98,7 +108,7 @@ class ExposeInternals(Transform): def apply(self): if self.document.settings.expose_internals: - for node in self.document.traverse(self.not_Text): + 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: @@ -139,7 +149,7 @@ class FilterMessages(Transform): default_priority = 870 def apply(self): - for node in self.document.traverse(nodes.system_message): + for node in tuple(self.document.findall(nodes.system_message)): if node['level'] < self.document.reporter.report_level: node.parent.remove(node) @@ -171,7 +181,7 @@ class StripComments(Transform): def apply(self): if self.document.settings.strip_comments: - for node in self.document.traverse(nodes.comment): + for node in tuple(self.document.findall(nodes.comment)): node.parent.remove(node) @@ -186,27 +196,31 @@ class StripClassesAndElements(Transform): default_priority = 420 def apply(self): - if not (self.document.settings.strip_elements_with_classes - or self.document.settings.strip_classes): + if self.document.settings.strip_elements_with_classes: + self.strip_elements = set( + 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 - # prepare dicts for lookup (not sets, for Python 2.2 compatibility): - self.strip_elements = dict( - [(key, None) - for key in (self.document.settings.strip_elements_with_classes - or [])]) - self.strip_classes = dict( - [(key, None) for key in (self.document.settings.strip_classes - or [])]) - for node in self.document.traverse(self.check_classes): - node.parent.remove(node) + 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 isinstance(node, nodes.Element): - for class_value in node['classes'][:]: - if class_value in self.strip_classes: - node['classes'].remove(class_value) - if class_value in self.strip_elements: - return 1 + 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): @@ -222,9 +236,10 @@ class SmartQuotes(Transform): nodes_to_skip = (nodes.FixedTextElement, nodes.Special) """Do not apply "smartquotes" to instances of these block-level nodes.""" - literal_nodes = (nodes.image, nodes.literal, nodes.math, + literal_nodes = (nodes.FixedTextElement, nodes.Special, + nodes.image, nodes.literal, nodes.math, nodes.raw, nodes.problematic) - """Do not change quotes in instances of these inline nodes.""" + """Do apply smartquotes to instances of these inline nodes.""" smartquotes_action = 'qDe' """Setting to select smartquote transformations. @@ -240,24 +255,25 @@ class SmartQuotes(Transform): def get_tokens(self, txtnodes): # A generator that yields ``(texttype, nodetext)`` tuples for a list # of "Text" nodes (interface to ``smartquotes.educate_tokens()``). - - texttype = {True: 'literal', # "literal" text is not changed: - False: 'plain'} - for txtnode in txtnodes: - nodetype = texttype[isinstance(txtnode.parent, - self.literal_nodes)] - yield (nodetype, txtnode.astext()) - + for node in txtnodes: + if (isinstance(node.parent, self.literal_nodes) + or isinstance(node.parent.parent, self.literal_nodes)): + yield ('literal', unicode(node)) + else: + # SmartQuotes uses backslash escapes instead of null-escapes + # Insert backslashes before escaped "active" characters. + txt = re.sub('(?<=\x00)([-\\\'".`])', r'\\\1', unicode(node)) + yield ('plain', txt) def apply(self): - smart_quotes = self.document.settings.smart_quotes + smart_quotes = self.document.settings.setdefault('smart_quotes', + False) if not smart_quotes: return try: alternative = smart_quotes.startswith('alt') except AttributeError: alternative = False - # print repr(alternative) document_language = self.document.settings.language_code lc_smartquotes = self.document.settings.smartquotes_locales @@ -266,7 +282,7 @@ class SmartQuotes(Transform): # "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.traverse(nodes.TextElement): + for node in self.document.findall(nodes.TextElement): # skip preformatted text blocks and special elements: if isinstance(node, self.nodes_to_skip): continue @@ -275,7 +291,7 @@ class SmartQuotes(Transform): continue # list of text nodes in the "text block": - txtnodes = [txtnode for txtnode in node.traverse(nodes.Text) + txtnodes = [txtnode for txtnode in node.findall(nodes.Text) if not isinstance(txtnode.parent, nodes.option_string)] @@ -305,7 +321,6 @@ class SmartQuotes(Transform): attr=self.smartquotes_action, language=lang) for txtnode, newtext in zip(txtnodes, teacher): - txtnode.parent.replace(txtnode, nodes.Text(newtext, - rawsource=txtnode.rawsource)) + txtnode.parent.replace(txtnode, nodes.Text(newtext)) self.unsupported_languages = set() # reset diff --git a/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py b/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py index c5818d9..c828e88 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py @@ -1,4 +1,4 @@ -# $Id: writer_aux.py 7808 2015-02-27 17:03:32Z milde $ +# $Id: writer_aux.py 8885 2021-11-11 16:29:16Z milde $ # Author: Lea Wiemann <LeWiemann@gmail.com> # Copyright: This module has been placed in the public domain. @@ -38,7 +38,7 @@ class Compound(Transform): default_priority = 910 def apply(self): - for compound in self.document.traverse(nodes.compound): + for compound in self.document.findall(nodes.compound): first_child = True for child in compound: if first_child: @@ -75,7 +75,7 @@ class Admonitions(Transform): def apply(self): language = languages.get_language(self.document.settings.language_code, self.document.reporter) - for node in self.document.traverse(nodes.Admonition): + 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) diff --git a/docutils/src/main/resources/docutils/docutils/utils/__init__.py b/docutils/src/main/resources/docutils/docutils/utils/__init__.py index 39976fb..7f293b1 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/utils/__init__.py @@ -1,5 +1,5 @@ # coding: utf-8 -# $Id: __init__.py 8141 2017-07-08 17:05:18Z goodger $ +# $Id: __init__.py 8892 2021-11-18 10:12:49Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -18,9 +18,13 @@ import warnings import unicodedata from docutils import ApplicationError, DataError, __version_info__ from docutils import nodes +from docutils.nodes import unescape import docutils.io from docutils.utils.error_reporting import ErrorOutput, SafeString +if sys.version_info >= (3, 0): + unicode = str + class SystemMessage(ApplicationError): @@ -32,7 +36,7 @@ class SystemMessage(ApplicationError): class SystemMessagePropagation(ApplicationError): pass -class Reporter: +class Reporter(object): """ Info/warning/error reporter and ``system_message`` element generator. @@ -126,8 +130,8 @@ class Reporter: def set_conditions(self, category, report_level, halt_level, stream=None, debug=False): - warnings.warn('docutils.utils.Reporter.set_conditions deprecated; ' - 'set attributes via configuration settings or directly', + warnings.warn('docutils.utils.Reporter.set_conditions() deprecated; ' + 'set attributes via configuration settings or directly.', DeprecationWarning, stacklevel=2) self.report_level = report_level self.halt_level = halt_level @@ -172,7 +176,6 @@ class Reporter: if not 'source' in attributes: # 'line' is absolute line number try: # look up (source, line-in-source) source, line = self.get_source_and_line(attributes.get('line')) - # print "locator lookup", kwargs.get('line'), "->", source, line except AttributeError: source, line = None, None if source is not None: @@ -326,7 +329,7 @@ def assemble_option_dict(option_list, options_spec): raise DuplicateOptionError('duplicate option "%s"' % name) try: options[name] = convertor(value) - except (ValueError, TypeError), detail: + except (ValueError, TypeError) as detail: raise detail.__class__('(option: "%s"; value: %r)\n%s' % (name, value, ' '.join(detail.args))) return options @@ -339,7 +342,7 @@ def decode_path(path): """ Ensure `path` is Unicode. Return `nodes.reprunicode` object. - Decode file/path string in a failsave manner if not already done. + Decode file/path string in a failsafe manner if not already done. """ # see also http://article.gmane.org/gmane.text.docutils.user/2905 if isinstance(path, unicode): @@ -347,7 +350,10 @@ def decode_path(path): try: path = path.decode(sys.getfilesystemencoding(), 'strict') except AttributeError: # default value None has no decode method - return nodes.reprunicode(path) + if not path: + return nodes.reprunicode('') + raise ValueError('`path` value must be a String or ``None``, not %r' + %path) except UnicodeDecodeError: try: path = path.decode('utf-8', 'strict') @@ -428,7 +434,7 @@ def new_document(source_path, settings=None): 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, at least provide the parser settings, + example, if parsing rST, at least provide the rst-parser settings, obtainable as follows:: settings = docutils.frontend.OptionParser( @@ -486,6 +492,10 @@ def get_stylesheet_reference(settings, relative_to=None): 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 1.2.', + DeprecationWarning, stacklevel=2) if settings.stylesheet_path: assert not settings.stylesheet, ( 'stylesheet and stylesheet_path are mutually exclusive.') @@ -512,12 +522,16 @@ def get_stylesheet_list(settings): 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 can be string or unicode: + # programmatically set default may be string with comma separated list: if not isinstance(stylesheets, list): stylesheets = [path.strip() for path in stylesheets.split(',')] - # expand relative paths if found in stylesheet-dirs: - return [find_file_in_dirs(path, settings.stylesheet_dirs) - for path in stylesheets] + 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] + if os.sep != '/': # for URLs, we need POSIX paths + stylesheets = [path.replace(os.sep, '/') for path in stylesheets] + return stylesheets def find_file_in_dirs(path, dirs): """ @@ -546,9 +560,8 @@ def get_trim_footnote_ref_space(settings): If trim_footnote_reference_space is None, return False unless the footnote reference style is 'superscript'. """ - if settings.trim_footnote_reference_space is None: - return hasattr(settings, 'footnote_references') and \ - settings.footnote_references == '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 @@ -576,17 +589,7 @@ def escape2null(text): parts.append('\x00' + text[found+1:found+2]) start = found + 2 # skip character after escape -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. - """ - if restore_backslashes: - return text.replace('\x00', '\\') - else: - for sep in ['\x00 ', '\x00\n', '\x00']: - text = ''.join(text.split(sep)) - return text +# `unescape` definition moved to `nodes` to avoid circular import dependency. def split_escaped_whitespace(text): """ @@ -599,7 +602,7 @@ def split_escaped_whitespace(text): return list(itertools.chain(*strings)) def strip_combining_chars(text): - if isinstance(text, str) and sys.version_info < (3,0): + if isinstance(text, str) and sys.version_info < (3, 0): return text return u''.join([c for c in text if not unicodedata.combining(c)]) @@ -611,7 +614,7 @@ def find_combining_chars(text): [3, 6, 9] """ - if isinstance(text, str) and sys.version_info < (3,0): + if isinstance(text, str) and sys.version_info < (3, 0): return [] return [i for i,c in enumerate(text) if unicodedata.combining(c)] @@ -625,7 +628,7 @@ def column_indices(text): """ # TODO: account for asian wide chars here instead of using dummy # replacements in the tableparser? - string_indices = range(len(text)) + 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] @@ -645,13 +648,10 @@ def column_width(text): Correct ``len(text)`` for wide East Asian and combining Unicode chars. """ - if isinstance(text, str) and sys.version_info < (3,0): - return len(text) - try: - width = sum([east_asian_widths[unicodedata.east_asian_width(c)] - for c in text]) - except AttributeError: # east_asian_width() New in version 2.4. - width = len(text) + if isinstance(text, str) and sys.version_info < (3, 0): + return len(text) # shortcut for binary strings + 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 @@ -663,16 +663,6 @@ def uniq(L): r.append(item) return r -# by Li Daobing http://code.activestate.com/recipes/190465/ -# since Python 2.6 there is also itertools.combinations() -def unique_combinations(items, n): - """Return n-length tuples, in sorted order, no repeated elements""" - if n==0: yield [] - else: - for i in xrange(len(items)-n+1): - for cc in unique_combinations(items[i+1:],n-1): - yield [items[i]]+cc - def normalize_language_tag(tag): """Return a list of normalized combinations for a `BCP 47` language tag. @@ -686,15 +676,15 @@ def normalize_language_tag(tag): """ # normalize: - tag = tag.lower().replace('-','_') + 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)] + base_tag = (subtags.pop(0),) # find all combinations of subtags taglist = [] for n in range(len(subtags), 0, -1): - for tags in unique_combinations(subtags, n): + for tags in itertools.combinations(subtags, n): taglist.append('-'.join(base_tag+tags)) taglist += base_tag return taglist @@ -775,31 +765,33 @@ release_level_abbreviations = { 'final': '',} def version_identifier(version_info=None): - # to add in Docutils 0.15: - # version_info is a namedtuple, an instance of Docutils.VersionInfo. """ - Given a `version_info` tuple (default is docutils.__version_info__), - build & return a version identifier string. + 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[2]: # version_info.micro - micro = '.%s' % version_info[2] + if version_info.micro: + micro = '.%s' % version_info.micro else: + # 0 is omitted: micro = '' - releaselevel = release_level_abbreviations[ - version_info[3]] # version_info.releaselevel - if version_info[4]: # version_info.serial - serial = version_info[4] + releaselevel = release_level_abbreviations[version_info.releaselevel] + if version_info.serial: + serial = version_info.serial else: + # 0 is omitted: serial = '' - if version_info[5]: # version_info.release + if version_info.release: dev = '' else: dev = '.dev' version = '%s.%s%s%s%s%s' % ( - version_info[0], # version_info.major - version_info[1], # version_info.minor + version_info.major, + version_info.minor, micro, releaselevel, serial, diff --git a/docutils/src/main/resources/docutils/docutils/utils/code_analyzer.py b/docutils/src/main/resources/docutils/docutils/utils/code_analyzer.py index 314a506..b8cc5a0 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/code_analyzer.py +++ b/docutils/src/main/resources/docutils/docutils/utils/code_analyzer.py @@ -4,7 +4,7 @@ """Lexical analysis of formal languages (i.e. code) using Pygments.""" # :Author: Georg Brandl; Felix Wiemann; Günter Milde -# :Date: $Date: 2015-04-20 16:05:27 +0200 (Mo, 20 Apr 2015) $ +# :Date: $Date: 2021-10-22 18:39:59 +0200 (Fr, 22. Okt 2021) $ # :Copyright: This module has been placed in the public domain. from docutils import ApplicationError @@ -13,7 +13,7 @@ try: from pygments.lexers import get_lexer_by_name from pygments.formatters.html import _get_ttype_class with_pygments = True -except (ImportError, SyntaxError): # pygments 2.0.1 fails with Py 3.1 and 3.2 +except ImportError: with_pygments = False # Filter the following token types from the list of class arguments: @@ -22,7 +22,7 @@ unstyled_tokens = ['token', # Token (base token type) ''] # short name for Token and Text # (Add, e.g., Token.Punctuation with ``unstyled_tokens += 'punctuation'``.) -class LexerError(ApplicationError): +class LexerError(ApplicationError): pass class Lexer(object): @@ -32,7 +32,7 @@ class Lexer(object): code -- string of source code to parse, language -- formal language the code is written in, - tokennames -- either 'long', 'short', or '' (see below). + tokennames -- either 'long', 'short', or 'none' (see below). Merge subsequent tokens of the same token-type. @@ -42,7 +42,7 @@ class Lexer(object): '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. + 'none': skip lexical analysis. """ def __init__(self, code, language, tokennames='short'): @@ -64,18 +64,21 @@ class Lexer(object): 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') - # Since version 1.2. (released Jan 01, 2010) Pygments has a - # TokenMergeFilter. However, this requires Python >= 2.4. When Docutils - # requires same minimal version, ``self.merge(tokens)`` in __iter__ can - # be replaced by ``self.lexer.add_filter('tokenmerge')`` in __init__. def merge(self, tokens): """Merge subsequent tokens of same token-type. Also strip the final newline (added by pygments). """ tokens = iter(tokens) - (lasttype, lastval) = tokens.next() + (lasttype, lastval) = next(tokens) for ttype, value in tokens: if ttype is lasttype: lastval += value @@ -114,7 +117,7 @@ class NumberLines(object): Iterating over an instance yields the tokens with a ``(['ln'], '<the line number>')`` token added for every code line. - Multi-line tokens are splitted.""" + Multi-line tokens are split.""" def __init__(self, tokens, startline, endline): self.tokens = tokens diff --git a/docutils/src/main/resources/docutils/docutils/utils/error_reporting.py b/docutils/src/main/resources/docutils/docutils/utils/error_reporting.py index 02e62eb..e6cfa1b 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/error_reporting.py +++ b/docutils/src/main/resources/docutils/docutils/utils/error_reporting.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# :Id: $Id: error_reporting.py 8119 2017-06-22 20:59:19Z milde $ +# :Id: $Id: error_reporting.py 8880 2021-11-05 11:11:18Z milde $ # :Copyright: © 2011 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -10,9 +10,14 @@ # 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 +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause """ +Provisional module to handle Exceptions across Python versions. + +This module will be deprecated with the end of support for Python 2.7 +and be removed in Docutils 1.2. + Error reporting should be safe from encoding/decoding errors. However, implicit conversions of strings and exceptions like @@ -35,7 +40,8 @@ The `SafeString`, `ErrorString` and `ErrorOutput` classes handle common exceptions. """ -import sys, codecs +import codecs +import sys # Guess the locale's encoding. # If no valid guess can be made, locale_encoding is set to `None`: @@ -49,7 +55,7 @@ else: # locale.getpreferredencoding([do_setlocale=True|False]) # has side-effects | might return a wrong guess. # (cf. Update 1 in http://stackoverflow.com/questions/4082645/using-python-2-xs-locale-module-t...) - except ValueError, error: # OS X may set UTF-8 without language code + except ValueError as error: # OS X may set UTF-8 without language code # see http://bugs.python.org/issue18378 # and https://sourceforge.net/p/docutils/bugs/298/ if "unknown locale: UTF-8" in error.args: @@ -64,6 +70,9 @@ else: locale_encoding = None +if sys.version_info >= (3, 0): + unicode = str # noqa + class SafeString(object): """ @@ -89,7 +98,7 @@ class SafeString(object): for arg in self.data.args] return ', '.join(args) if isinstance(self.data, unicode): - if sys.version_info > (3,0): + if sys.version_info > (3, 0): return self.data else: return self.data.encode(self.encoding, @@ -113,7 +122,7 @@ class SafeString(object): if isinstance(self.data, EnvironmentError): u = u.replace(": u'", ": '") # normalize filename quoting return u - except UnicodeError, error: # catch ..Encode.. and ..Decode.. errors + except UnicodeError as error: # catch ..Encode.. and ..Decode.. errors if isinstance(self.data, EnvironmentError): return u"[Errno %s] %s: '%s'" % (self.data.errno, SafeString(self.data.strerror, self.encoding, @@ -145,7 +154,7 @@ class ErrorString(SafeString): class ErrorOutput(object): """ Wrapper class for file-like error streams with - failsave de- and encoding of `str`, `bytes`, `unicode` and + failsafe de- and encoding of `str`, `bytes`, `unicode` and `Exception` instances. """ @@ -199,9 +208,9 @@ class ErrorOutput(object): self.stream.write(data) except UnicodeEncodeError: self.stream.write(data.encode(self.encoding, self.encoding_errors)) - except TypeError: + except TypeError: if isinstance(data, unicode): # passed stream may expect bytes - self.stream.write(data.encode(self.encoding, + self.stream.write(data.encode(self.encoding, self.encoding_errors)) return if self.stream in (sys.stderr, sys.stdout): diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py b/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py index 673f93e..3937fa7 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py @@ -1,4 +1,4 @@ -# :Id: $Id: __init__.py 7865 2015-04-12 10:06:43Z milde $ +# :Id: $Id: __init__.py 8760 2021-06-17 09:58:31Z milde $ # :Author: Guenter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -7,7 +7,7 @@ # 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 +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause """ This is the Docutils (Python Documentation Utilities) "math" sub-package. @@ -19,12 +19,18 @@ It contains various modules for conversion between different math formats :latex2mathml: LaTeX math -> presentational MathML :unichar2tex: Unicode character to LaTeX math translation table :tex2unichar: LaTeX math to Unicode character translation dictionaries -:tex2mathml_extern: Wrapper for TeX -> MathML command line converters +:tex2mathml_extern: Wrapper for 3rd party TeX -> MathML converters """ # helpers for Docutils math support # ================================= +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`. @@ -35,11 +41,7 @@ def pick_math_environment(code, numbered=False): If `numbered` evaluates to ``False``, the "starred" versions are used to suppress numbering. """ - # cut out environment content: - chunks = code.split(r'\begin{') - toplevel_code = ''.join([chunk.split(r'\end{')[-1] - for chunk in chunks]) - if toplevel_code.find(r'\\') >= 0: + if toplevel_code(code).find(r'\\') >= 0: env = 'align' else: env = 'equation' diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/latex2mathml.py b/docutils/src/main/resources/docutils/docutils/utils/math/latex2mathml.py index bcb4877..4d61c8b 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/latex2mathml.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/latex2mathml.py @@ -1,571 +1,1417 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# :Id: $Id: latex2mathml.py 7995 2016-12-10 17:50:59Z milde $ -# :Copyright: © 2010 Günter Milde. -# Based on rst2mathml.py from the latex_math sandbox project -# © 2005 Jens Jørgen Mortensen +# :Id: $Id: latex2mathml.py 8878 2021-11-05 11:10:44Z milde $ +# :Copyright: © 2005 Jens Jørgen Mortensen [1]_ +# © 2010, 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 +# +# .. _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. -"""Convert LaTex math code into presentational MathML""" +This module is provisional: +the API is not settled and may change with any minor Docutils version. +""" -# Based on the `latex_math` sandbox project by Jens Jørgen Mortensen +# Usage: +# +# >>> from latex2mathml import * -import docutils.utils.math.tex2unichar as tex2unichar +import collections +import copy +import re +import sys +import unicodedata +if sys.version_info >= (3, 0): + unicode = str # noqa -# TeX spacing combining -over = {'acute': u'\u00B4', # u'\u0301', - 'bar': u'\u00AF', # u'\u0304', - 'breve': u'\u02D8', # u'\u0306', - 'check': u'\u02C7', # u'\u030C', - 'dot': u'\u02D9', # u'\u0307', - 'ddot': u'\u00A8', # u'\u0308', - 'dddot': u'\u20DB', - 'grave': u'`', # u'\u0300', - 'hat': u'^', # u'\u0302', - 'mathring': u'\u02DA', # u'\u030A', - 'overleftrightarrow': u'\u20e1', - # 'overline': # u'\u0305', - 'tilde': u'\u02DC', # u'\u0303', - 'vec': u'\u20D7'} +from docutils.utils.math import tex2unichar, toplevel_code -Greek = { # Capital Greek letters: (upright in TeX style) + +# Character data +# -------------- + +# LaTeX math macro to Unicode mappings. +# Character categories. + +# identifiers -> <mi> + +letters = tex2unichar.mathalpha +letters['hbar'] = u'\u210F' # compatibility mapping to ℏ (\hslash). +# (ħ LATIN SMALL LETTER H WITH STROKE is upright) + +# special case: Capital Greek letters: (upright in TeX style) +greek_capitals = { 'Phi':u'\u03a6', 'Xi':u'\u039e', 'Sigma':u'\u03a3', 'Psi':u'\u03a8', 'Delta':u'\u0394', 'Theta':u'\u0398', 'Upsilon':u'\u03d2', 'Pi':u'\u03a0', 'Omega':u'\u03a9', 'Gamma':u'\u0393', 'Lambda':u'\u039b'} -letters = tex2unichar.mathalpha - -special = tex2unichar.mathbin # Binary symbols -special.update(tex2unichar.mathrel) # Relation symbols, arrow symbols -special.update(tex2unichar.mathord) # Miscellaneous symbols -special.update(tex2unichar.mathop) # Variable-sized symbols -special.update(tex2unichar.mathopen) # Braces -special.update(tex2unichar.mathclose) # Braces -special.update(tex2unichar.mathfence) - -sumintprod = ''.join([special[symbol] for symbol in - ['sum', 'int', 'oint', 'prod']]) - -functions = ['arccos', 'arcsin', 'arctan', 'arg', 'cos', 'cosh', - 'cot', 'coth', 'csc', 'deg', 'det', 'dim', - 'exp', 'gcd', 'hom', 'inf', 'ker', 'lg', - 'lim', 'liminf', 'limsup', 'ln', 'log', 'max', - 'min', 'Pr', 'sec', 'sin', 'sinh', 'sup', - 'tan', 'tanh', - 'injlim', 'varinjlim', 'varlimsup', - 'projlim', 'varliminf', 'varprojlim'] - - -mathbb = { - 'A': u'\U0001D538', - 'B': u'\U0001D539', - 'C': u'\u2102', - 'D': u'\U0001D53B', - 'E': u'\U0001D53C', - 'F': u'\U0001D53D', - 'G': u'\U0001D53E', - 'H': u'\u210D', - 'I': u'\U0001D540', - 'J': u'\U0001D541', - 'K': u'\U0001D542', - 'L': u'\U0001D543', - 'M': u'\U0001D544', - 'N': u'\u2115', - 'O': u'\U0001D546', - 'P': u'\u2119', - 'Q': u'\u211A', - 'R': u'\u211D', - 'S': u'\U0001D54A', - 'T': u'\U0001D54B', - 'U': u'\U0001D54C', - 'V': u'\U0001D54D', - 'W': u'\U0001D54E', - 'X': u'\U0001D54F', - 'Y': u'\U0001D550', - 'Z': u'\u2124', +# functions -> <mi> +functions = {# functions with a space in the name + 'liminf': u'lim\u202finf', + 'limsup': u'lim\u202fsup', + 'injlim': u'inj\u202flim', + 'projlim': u'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). + + +# math font selection -> <mi mathvariant=...> or <mstyle mathvariant=...> +math_alphabets = {# 'cmdname': 'mathvariant value' # package + 'boldsymbol': 'bold', + 'mathbf': 'bold', + 'mathit': 'italic', + 'mathtt': 'monospace', + 'mathrm': 'normal', + 'mathsf': 'sans-serif', + 'mathcal': 'script', + 'mathbfit': 'bold-italic', # isomath + 'mathbb': 'double-struck', # amssymb + 'mathfrak': 'fraktur', # amssymb + 'mathsfit': 'sans-serif-italic', # isomath + 'mathsfbfit': 'sans-serif-bold-italic', # isomath + 'mathscr': 'script', # mathrsfs + # unsupported: bold-fraktur + # bold-script + # bold-sans-serif + } + +# operator, fence, or separator -> <mo> + + +stretchables = {# extensible delimiters allowed in left/right cmds + 'backslash': '\\', + 'uparrow': u'\u2191', # ↑ UPWARDS ARROW + 'downarrow': u'\u2193', # ↓ DOWNWARDS ARROW + 'updownarrow': u'\u2195', # ↕ UP DOWN ARROW + 'Uparrow': u'\u21d1', # ⇑ UPWARDS DOUBLE ARROW + 'Downarrow': u'\u21d3', # ⇓ DOWNWARDS DOUBLE ARROW + 'Updownarrow': u'\u21d5', # ⇕ UP DOWN DOUBLE ARROW + 'lmoustache': u'\u23b0', # ⎰ UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION + 'rmoustache': u'\u23b1', # ⎱ UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION + 'arrowvert': u'\u23d0', # ⏐ VERTICAL LINE EXTENSION + 'bracevert': u'\u23aa', # ⎪ CURLY BRACKET EXTENSION + 'lvert': u'|', # left | + 'lVert': u'\u2016', # left ‖ + 'rvert': u'|', # right | + 'rVert': u'\u2016', # right ‖ + 'Arrowvert': u'\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': u'\u2266\u0338', # ≦̸ + 'ngeqq': u'\u2267\u0338', # ≧̸ + 'nleqslant': u'\u2a7d\u0338', # ⩽̸ + 'ngeqslant': u'\u2a7e\u0338', # ⩾̸ + 'ngtrless': u'\u2277\u0338', # txfonts + 'nlessgtr': u'\u2276\u0338', # txfonts + 'nsubseteqq': u'\u2AC5\u0338', # ⫅̸ + 'nsupseteqq': u'\u2AC6\u0338', # ⫆̸ + # compatibility definitions: + 'centerdot': u'\u2B1D', # BLACK VERY SMALL SQUARE | mathbin + 'varnothing': u'\u2300', # ⌀ DIAMETER SIGN | empty set + 'varpropto': u'\u221d', # ∝ PROPORTIONAL TO | sans serif + 'triangle': u'\u25B3', # WHITE UP-POINTING TRIANGLE | mathord + 'triangledown': u'\u25BD', # WHITE DOWN-POINTING TRIANGLE | mathord + # alias commands: + 'dotsb': u'\u22ef', # ⋯ with binary operators/relations + 'dotsc': u'\u2026', # … with commas + 'dotsi': u'\u22ef', # ⋯ with integrals + 'dotsm': u'\u22ef', # ⋯ multiplication dots + 'dotso': u'\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.mathord) # Miscellaneous 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': u'\u223C', # ∼ + 'thickapprox':u'\u2248', # ≈ + } + +small_operators = {# mathsize='75%' + 'shortmid': u'\u2223', # ∣ + 'shortparallel': u'\u2225', # ∥ + 'nshortmid': u'\u2224', # ∤ + 'nshortparallel': u'\u2226', # ∦ + 'smallfrown': u'\u2322', # ⌢ FROWN + 'smallsmile': u'\u2323', # ⌣ SMILE + 'smallint': u'\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 + 'medspace': '0.2222em', # 4mu = 2/9em + 'thinspace': '0.1667em', # 3mu = 1/6em + 'negthinspace': '-0.1667em', # -3mu = -1/6em + 'negmedspace': '-0.2222em', # -4mu = -2/9em + 'negthickspace': '-0.2778em', # -5mu = -5/18em + ' ': '0.25em', # inter word space + ';': '0.2778em', # 5mu thickspace + ':': '0.2222em', # 4mu medspace + ',': '0.1667em', # 3mu thinspace + '!': '-0.1667em', # negthinspace } -mathscr = { - 'A': u'\U0001D49C', - 'B': u'\u212C', # bernoulli function - 'C': u'\U0001D49E', - 'D': u'\U0001D49F', - 'E': u'\u2130', - 'F': u'\u2131', - 'G': u'\U0001D4A2', - 'H': u'\u210B', # hamiltonian - 'I': u'\u2110', - 'J': u'\U0001D4A5', - 'K': u'\U0001D4A6', - 'L': u'\u2112', # lagrangian - 'M': u'\u2133', # physics m-matrix - 'N': u'\U0001D4A9', - 'O': u'\U0001D4AA', - 'P': u'\U0001D4AB', - 'Q': u'\U0001D4AC', - 'R': u'\u211B', - 'S': u'\U0001D4AE', - 'T': u'\U0001D4AF', - 'U': u'\U0001D4B0', - 'V': u'\U0001D4B1', - 'W': u'\U0001D4B2', - 'X': u'\U0001D4B3', - 'Y': u'\U0001D4B4', - 'Z': u'\U0001D4B5', - 'a': u'\U0001D4B6', - 'b': u'\U0001D4B7', - 'c': u'\U0001D4B8', - 'd': u'\U0001D4B9', - 'e': u'\u212F', - 'f': u'\U0001D4BB', - 'g': u'\u210A', - 'h': u'\U0001D4BD', - 'i': u'\U0001D4BE', - 'j': u'\U0001D4BF', - 'k': u'\U0001D4C0', - 'l': u'\U0001D4C1', - 'm': u'\U0001D4C2', - 'n': u'\U0001D4C3', - 'o': u'\u2134', # order of - 'p': u'\U0001D4C5', - 'q': u'\U0001D4C6', - 'r': u'\U0001D4C7', - 's': u'\U0001D4C8', - 't': u'\U0001D4C9', - 'u': u'\U0001D4CA', - 'v': u'\U0001D4CB', - 'w': u'\U0001D4CC', - 'x': u'\U0001D4CD', - 'y': u'\U0001D4CE', - 'z': u'\U0001D4CF', - } - -negatables = {'=': u'\u2260', - r'\in': u'\u2209', - r'\equiv': u'\u2262'} - -# LaTeX to MathML translation stuff: -class math: - """Base class for MathML elements.""" - - nchildren = 1000000 - """Required number of children""" - - def __init__(self, children=None, inline=None): - """math([children]) -> MathML element - - children can be one child or a list of children.""" +# accents -> <mover stretchy="false"> +accents = {# TeX: (spacing, combining) + 'acute': (u'´', u'\u0301'), + 'bar': (u'ˉ', u'\u0304'), + 'breve': (u'˘', u'\u0306'), + 'check': (u'ˇ', u'\u030C'), + 'dot': (u'˙', u'\u0307'), + 'ddot': (u'¨', u'\u0308'), + 'dddot': (u'⋯', u'\u20DB'), + 'grave': (u'`', u'\u0300'), + 'hat': (u'ˆ', u'\u0302'), + 'mathring': (u'˚', u'\u030A'), + 'tilde': (u'˜', u'\u0303'), # tilde ~ or small tilde ˜? + 'vec': (u'→', u'\u20d7'), # → too heavy, accents="false" + # TODO: ddddot + } + +# limits etc. -> <mover> or <munder> +over = {# TeX: (char, offset-correction/em) + 'overbrace': (u'\u23DE', -0.2), # DejaVu Math -0.6 + 'overleftarrow': (u'\u2190', -0.2), + 'overleftrightarrow': (u'\u2194', -0.2), + 'overline': (u'_', -0.2), # \u2012' FIGURE DASH does not stretch + 'overrightarrow': (u'\u2192', -0.2), + 'widehat': (u'^', -0.5), + 'widetilde': (u'~', -0.3), + } +under = {'underbrace': (u'\u23DF', 0.1), # DejaVu Math -0.7 + 'underleftarrow': (u'\u2190', -0.2), + 'underleftrightarrow': (u'\u2194', -0.2), + 'underline': (u'_', -0.8), + 'underrightarrow': (u'\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 = {'-': u'\u2212', # HYPHEN-MINUS -> MINUS SIGN + ':': u'\u2236', # COLON -> RATIO + '~': u'\u00a0', # NO-BREAK SPACE + } + +# blackboard bold (Greek characters not working with "mathvariant" (Firefox 78) +mathbb = {u'Γ': u'\u213E', # ℾ + u'Π': u'\u213F', # ℿ + u'Σ': u'\u2140', # ⅀ + u'γ': u'\u213D', # ℽ + u'π': u'\u213C', # ℼ + } +# Matrix environments +matrices = {# name: fences + 'matrix': ('', ''), + 'smallmatrix': ('', ''), # smaller, see begin_environment()! + 'pmatrix': ('(', ')'), + 'bmatrix': ('[', ']'), + 'Bmatrix': ('{', '}'), + 'vmatrix': ('|', '|'), + 'Vmatrix': (u'\u2016', u'\u2016'), # ‖ + '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: style_attrs, frac_attrs + '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, + } + + +# MathML element classes +# ---------------------- + +class math(object): + """Base class for MathML elements and root of MathML trees.""" + + 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 DOM tree.""" + _level = 0 # indentation level (static class variable) + xml_entities = { # for invalid and invisible characters + ord('<'): u'<', + ord('>'): u'>', + ord('&'): u'&', + 0x2061: u'⁡', + } + _boolstrings = {True: 'true', False: 'false'} + """String representation of boolean MathML attribute values.""" + + html_tagname = 'span' + """Tag name for HTML representation.""" + + def __init__(self, *children, **attributes): + """Set up node with `children` and `attributes`. + + Attributes are downcased: Use CLASS to set "class" value. + >>> math(mn(3), CLASS='test') + math(mn(3), class='test') + >>> math(CLASS='test').toprettyxml() + '<math class="test">\n</math>' + + """ self.children = [] - if children is not None: - if type(children) is list: - for child in children: - self.append(child) - else: - # Only one child: - self.append(children) + self.extend(children) - if inline is not None: - self.inline = inline + self.attributes = collections.OrderedDict() + # sort attributes for predictable functional tests + # as self.attributes.update(attributes) does not keep order in Python < 3.6 + for key in sorted(attributes.keys()): + # Use .lower() to allow argument `CLASS` for attribute `class` + # (Python keyword). MathML uses only lowercase attributes. + self.attributes[key.lower()] = attributes[key] def __repr__(self): - if hasattr(self, 'children'): - return self.__class__.__name__ + '(%s)' % \ - ','.join([repr(child) for child in self.children]) - else: - return self.__class__.__name__ + content = [repr(item) for item in getattr(self, 'children', [])] + if hasattr(self, 'data'): + content.append(repr(self.data)) + if isinstance(self, MathSchema) and self.switch: + content.append('switch=True') + content += ["%s=%r"%(k, v) for k, v in self.attributes.items() + if v is not None] + + return self.__class__.__name__ + '(%s)' % ', '.join(content) + + def __len__(self): + return len(self.children) + + # emulate dictionary-like access to attributes + # see `docutils.nodes.Element` for dict/list interface + def __getitem__(self, key): + return self.attributes[key] + def __setitem__(self, key, item): + self.attributes[key] = item + def get(self, *args, **kwargs): + return self.attributes.get(*args, **kwargs) def full(self): - """Room for more children?""" - - return len(self.children) >= self.nchildren + """Return boolean indicating whether children may be appended.""" + return (self.nchildren is not None + and len(self) >= self.nchildren) def append(self, child): - """append(child) -> element - - Appends child and returns self if self is not full or first - non-full parent.""" + """Append child and return self or first non-full parent. - assert not self.full() + If self is full, go up the tree and return first non-full node or + `None`. + """ + if self.full(): + raise SyntaxError('Node %s already full!' % self) self.children.append(child) child.parent = self - node = self - while node.full(): - node = node.parent - return node + if self.full(): + return self.close() + return self - def delete_child(self): - """delete_child() -> child - - Delete last child and return it.""" - - child = self.children[-1] - del self.children[-1] - return child + def extend(self, children): + for child in children: + self.append(child) + return self def close(self): - """close() -> parent - - Close element and return first non-full element.""" - + """Close element and return first non-full parent or None.""" parent = self.parent - while parent.full(): + while parent is not None and parent.full(): parent = parent.parent return parent - def xml(self): - """xml() -> xml-string""" + def toprettyxml(self): + """Return XML representation of self as string.""" + return ''.join(self._xml()) - return self.xml_start() + self.xml_body() + self.xml_end() + def _xml(self, level=0): + return ([self.xml_starttag()] + + self._xml_body(level) + + ['</%s>' % self.__class__.__name__]) - def xml_start(self): - if not hasattr(self, 'inline'): - return ['<%s>' % self.__class__.__name__] - xmlns = 'http://www.w3.org/1998/Math/MathML' - if self.inline: - return ['<math xmlns="%s">' % xmlns] - else: - return ['<math xmlns="%s" mode="display">' % xmlns] - - def xml_end(self): - return ['</%s>' % self.__class__.__name__] + def xml_starttag(self): + attrs = ['%s="%s"' % (k, str(v).replace('True', 'true').replace('False', 'false')) + for k, v in self.attributes.items() + if v is not None] + return '<%s>' % ' '.join([self.__class__.__name__] + attrs) - def xml_body(self): + def _xml_body(self, level=0): xml = [] for child in self.children: - xml.extend(child.xml()) + xml.extend(['\n', ' ' * (level+1)]) + xml.extend(child._xml(level+1)) + xml.extend(['\n', ' ' * level]) return xml +# >>> n2 = math(mn(2)) +# >>> n2 +# math(mn(2)) +# >>> n2.toprettyxml() +# '<math>\n <mn>2</mn>\n</math>' +# >>> len(n2) +# 1 +# >>> eq3 = math(id='eq3', display='block') +# >>> eq3 +# math(display='block', id='eq3') +# >>> eq3.toprettyxml() +# '<math display="block" id="eq3">\n</math>' +# >>> len(eq3) +# 0 +# >>> math(CLASS='bold').xml_starttag() +# '<math class="bold">' + +class mtable(math): pass + +# >>> mt = mtable(displaystyle=True) +# >>> mt +# mtable(displaystyle=True) +# >>> math(mt).toprettyxml() +# '<math>\n <mtable displaystyle="true">\n </mtable>\n</math>' + class mrow(math): - def xml_start(self): - return ['\n<%s>' % self.__class__.__name__] + """Group sub-expressions as a horizontal row.""" + + def close(self): + """Close element and return first non-full parent or None. + + Remove <mrow>, if it is single child and the parent infers an mrow + or if it has only one child element. + """ + parent = self.parent + if isinstance(parent, MathRowSchema) and parent.nchildren == 1: + parent.nchildren = len(parent.children) + parent.children = self.children + for child in self.children: + child.parent = parent + return parent.close() + if len(self) == 1: + try: + parent.children[parent.children.index(self)] = self.children[0] + self.children[0].parent = parent + except (AttributeError, ValueError): + return self.children[0] + return super(mrow, self).close() + +# >>> mrow(displaystyle=False) +# mrow(displaystyle=False) + +# 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. +class MathRowSchema(math): + """Base class for elements treating content as a single inferred mrow.""" -class mtable(math): - def xml_start(self): - return ['\n<%s>' % self.__class__.__name__] +class mtr(MathRowSchema): pass -class mtr(mrow): pass -class mtd(mrow): pass +class mtd(MathRowSchema): pass -class mx(math): - """Base class for mo, mi, and mn""" +class menclose(MathRowSchema): + nchildren = 1 # \boxed expects one argument or a group +class mphantom(MathRowSchema): + nchildren = 1 # \phantom expects one argument or a group + +class msqrt(MathRowSchema): + nchildren = 1 # \sqrt expects one argument or a group + +class mstyle(MathRowSchema): + nchildren = 1 # \mathrm, ... expect one argument or a group + + +class MathToken(math): + """Token Element: contains textual data instead of children. + + Base class for mo, mi, and mn. + """ nchildren = 0 - def __init__(self, data): + + def __init__(self, data, **attributes): self.data = data + super(MathToken, self).__init__(**attributes) - def xml_body(self): - return [self.data] + def _xml_body(self, level=0): + return [unicode(self.data).translate(self.xml_entities)] -class mo(mx): - translation = {'<': '<', '>': '>'} - def xml_body(self): - return [self.translation.get(self.data, self.data)] +class mtext(MathToken): pass +class mi(MathToken): pass +class mo(MathToken): pass +class mn(MathToken): pass -class mi(mx): pass -class mn(mx): pass +# >>> mo(u'<') +# mo('<') +# >>> mo(u'<')._xml() +# ['<mo>', '<', '</mo>'] -class msub(math): - nchildren = 2 +class MathSchema(math): + """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. + """ -class msup(math): nchildren = 2 -class msqrt(math): - nchildren = 1 + def __init__(self, *children, **kwargs): + self.switch = kwargs.pop('switch', False) + math.__init__(self, *children, **kwargs) + + def append(self, child): + current_node = super(MathSchema, self).append(child) + # normalize order if full + if self.switch and self.full(): + self.children[-1], self.children[-2] = self.children[-2], self.children[-1] + self.switch = False + return current_node + +class msub(MathSchema): pass +class msup(MathSchema): pass +class msubsup(MathSchema): + nchildren = 3 -class mroot(math): +# >>> msub(mi('x'), mo('-')) +# msub(mi('x'), mo('-')) +# >>> msubsup(mi('base'), mi('sub'), mi('super')) +# msubsup(mi('base'), mi('sub'), mi('super')) +# >>> msubsup(mi('base'), mi('super'), mi('sub'), switch=True) +# msubsup(mi('base'), mi('sub'), mi('super')) + +class munder(msub): pass +class mover(msup): pass + +# >>> munder(mi('lim'), mo('-'), accent=False) +# munder(mi('lim'), mo('-'), accent=False) +# >>> mu = munder(mo('-'), accent=False, switch=True) +# >>> mu +# munder(mo('-'), switch=True, accent=False) +# >>> mu.append(mi('lim')) +# >>> mu +# munder(mi('lim'), mo('-'), accent=False) +# >>> mu.append(mi('lim')) +# Traceback (most recent call last): +# SyntaxError: Node munder(mi('lim'), mo('-'), accent=False) already full! +# >>> munder(mo('-'), mi('lim'), accent=False, switch=True).toprettyxml() +# '<munder accent="false">\n <mi>lim</mi>\n <mo>-</mo>\n</munder>' + +class munderover(msubsup): pass + +class mroot(MathSchema): nchildren = 2 class mfrac(math): nchildren = 2 -class msubsup(math): - nchildren = 3 - def __init__(self, children=None, reversed=False): - self.reversed = reversed - math.__init__(self, children) - - def xml(self): - if self.reversed: -## self.children[1:3] = self.children[2:0:-1] - self.children[1:3] = [self.children[2], self.children[1]] - self.reversed = False - return math.xml(self) - -class mfenced(math): - translation = {'\\{': '{', '\\langle': u'\u2329', - '\\}': '}', '\\rangle': u'\u232A', - '.': ''} - def __init__(self, par): - self.openpar = par - math.__init__(self) - - def xml_start(self): - open = self.translation.get(self.openpar, self.openpar) - close = self.translation.get(self.closepar, self.closepar) - return ['<mfenced open="%s" close="%s">' % (open, close)] - class mspace(math): nchildren = 0 -class mstyle(math): - def __init__(self, children=None, nchildren=None, **kwargs): - if nchildren is not None: - self.nchildren = nchildren - math.__init__(self, children) - self.attrs = kwargs - def xml_start(self): - return ['<mstyle '] + ['%s="%s"' % item - for item in self.attrs.items()] + ['>'] +# LaTeX to MathML translation +# --------------------------- -class mover(math): - nchildren = 2 - def __init__(self, children=None, reversed=False): - self.reversed = reversed - math.__init__(self, children) +# auxiliary functions +# ~~~~~~~~~~~~~~~~~~~ - def xml(self): - if self.reversed: - self.children.reverse() - self.reversed = False - return math.xml(self) +def tex_cmdname(string): + """Return leading TeX command name and remainder of `string`. -class munder(math): - nchildren = 2 + >>> 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') -class munderover(math): - nchildren = 3 - def __init__(self, children=None): - math.__init__(self, children) + """ + m = re.match(r'([a-zA-Z]+) *(.*)', string) + if m is None: + m = re.match(r'(.?)(.*)', string) + return m.group(1), m.group(2) + +# Test: +# +# >>> tex_cmdname('name_2') # first non-letter terminates +# ('name', '_2') +# >>> tex_cmdname(' next') # leading whitespace is returned +# (' ', 'next') +# >>> tex_cmdname('1 2') # whitespace after non-letter is kept +# ('1', ' 2') +# >>> tex_cmdname('') # empty string +# ('', '') + +def tex_number(string): + """Return leading number literal and remainder of `string`. + + >>> tex_number('123.4') + ('123.4', '') -class mtext(math): - nchildren = 0 - def __init__(self, text): - self.text = text + """ + m = re.match(r'([0-9.,]*[0-9]+)(.*)', string) + 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) + 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 SyntaxError('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') - def xml_body(self): - return [self.text] + """ + 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 parse_latex_math(string, inline=True): - """parse_latex_math(string [,inline]) -> MathML-tree +def tex_optarg(string): + """Return optional argument and remainder. - Returns a MathML-tree parsed from string. inline=True is for - inline math and inline=False is for displayed math. + >>> 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}') - tree is the whole tree and node is the current element.""" + """ + m = re.match(r"""\s* # leading whitespace + \[(?P<optarg>(\\]|[^\[\]]|\\])*)\] # [group] without nested groups + (?P<remainder>.*$) + """, string, re.VERBOSE) + if m is None and not string.startswith('['): + return '', string + try: + return m.group('optarg'), m.group('remainder') + except AttributeError: + raise SyntaxError('Could not extract optional argument from %r' % string) + +# Test: +# >>> tex_optarg(' [optional argument] after whitespace') +# ('optional argument', ' after whitespace') +# >>> tex_optarg('[missing right bracket') +# Traceback (most recent call last): +# SyntaxError: Could not extract optional argument from '[missing right bracket' +# >>> tex_optarg('[group with [nested group]]') +# Traceback (most recent call last): +# SyntaxError: Could not extract optional argument from '[group with [nested group]]' + + +def parse_latex_math(node, string): + """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 = ' '.join(string.split()) - - if inline: - node = mrow() - tree = math(node, inline=True) - else: - node = mtd() - tree = math(mtable(mtr(node)), inline=False) + tree = node while len(string) > 0: - n = len(string) - c = string[0] - skip = 1 # number of characters consumed - if n > 1: - c2 = string[1] - else: - c2 = '' -## print n, string, c, c2, node.__class__.__name__ + # Take off first character: + c, string = string[0], string[1:] + if c == ' ': - pass - elif c == '\\': - if c2 in '{}': - node = node.append(mo(c2)) - skip = 2 - elif c2 == ' ': - node = node.append(mspace()) - skip = 2 - elif c2 == ',': # TODO: small space - node = node.append(mspace()) - skip = 2 - elif c2.isalpha(): - # We have a LaTeX-name: - i = 2 - while i < n and string[i].isalpha(): - i += 1 - name = string[1:i] - node, skip = handle_keyword(name, node, string[i:]) - skip += i - elif c2 == '\\': - # End of a row: - entry = mtd() - row = mtr(entry) - node.close().close().append(row) - node = entry - skip = 2 - else: - raise SyntaxError(ur'Syntax error: "%s%s"' % (c, c2)) + 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 == '{': + 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(): - node = node.append(mn(c)) - elif c in "+-*/=()[]|<>,.!?':;@": + 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 node.children: + previous_node = node.children[-1] + if (getattr(previous_node, 'data', '-') 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)) - elif c == '_': - child = node.delete_child() - if isinstance(child, msup): - sub = msubsup(child.children, reversed=True) - elif isinstance(child, mo) and child.data in sumintprod: - sub = munder(child) - else: - sub = msub(child) - node.append(sub) - node = sub - elif c == '^': - child = node.delete_child() - if isinstance(child, msub): - sup = msubsup(child.children) - elif isinstance(child, mo) and child.data in sumintprod: - sup = mover(child) - elif (isinstance(child, munder) and - child.children[0].data in sumintprod): - sup = munderover(child.children) - else: - sup = msup(child) - node.append(sup) - node = sup - elif c == '{': + else: + raise SyntaxError(u'Unsupported character: "%s"' % c) + return tree + +# Test: + +# >>> print(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} < 3') +# math(msqrt(mn('2'), mo('+'), mn('3')), mo('<'), mn('3')) +# >>> parse_latex_math(math(), '\\sqrt[3]{2 + 3}') +# math(mroot(mrow(mn('2'), mo('+'), mn('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'))) + +def handle_cmd(name, node, string): + """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['class'] = 'capital-greek' + node = node.append(new_node) + return node, 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(u'_')) + elif name == 'varlimsup': # \overline\lim + new_node = mover(new_node, mo(u'¯'), accent=False) + elif name == 'varprojlim': # \underleftarrow\lim + new_node = munder(new_node, mo(u'\u2190')) + elif name == 'varinjlim': # \underrightarrow\lim + new_node = munder(new_node, mo(u'\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(u'\u2061')) # ⁡ + return node, string + + if name in math_alphabets: + if name == 'boldsymbol': + attributes = {'class': 'boldsymbol'} + else: + attributes = {'mathvariant': math_alphabets[name]} + if name == 'mathscr': + attributes['class'] = 'mathscr' + # Check for single symbol (letter, name, or ⅀) + arg, remainder = tex_token_or_group(string) + if arg.startswith('\\'): + # convert single letters (so the isalpha() test below works). + # TODO: convert all LICRs in a group (\matrm{\mu\Omega}) + arg = letters.get(arg[1:], arg) + if name == 'mathbb': + # mathvariant="double-struck" is ignored for Greek letters + # (tested in Firefox 78). Use literal Unicode characters. + arg = mathbb.get(arg, arg) + if arg.isalpha() or arg == u'\u2140': + node = node.append(mi(arg, **attributes)) + return node, remainder + # Wrap in <style> + style = mstyle(**attributes) + node.append(style) + return style, 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': + node = parse_latex_math(node, '\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 SyntaxError(u'Unsupported "\\%s" delimiter "%s"!' + % (name, 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 - elif c == '}': + if delimiter != '.': # '.' stands for "empty delimiter" + node.append(mo(delimiter, **delimiter_attributes)) + if name == 'right' or name.endswith('r'): node = node.close() - elif c == '&': - entry = mtd() - node.close().append(entry) - node = entry + return node, string + + if name == 'not': + arg, string = tex_token(string) + if arg == '{': + return node, '{\\not ' + string + if arg.startswith('\\'): # LaTeX macro + try: + arg = operators[arg[1:]] + except KeyError: + raise SyntaxError(u'\\not: Cannot negate: "%s"!'%arg) + arg = unicodedata.normalize('NFC', arg+u'\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 + part = re.sub('(^ | $)', u'\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'): + 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': + new_node = menclose(notation='box') + 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: - raise SyntaxError(ur'Illegal character: "%s"' % c) - string = string[skip:] - return tree + new_node = msqrt() + node.append(new_node) + return new_node, string + + if name in fractions: + (style_atts, frac_atts) = fractions[name] + if name == 'cfrac': + optarg, string = tex_optarg(string) + optargs = {'l': 'left', 'r': 'right'} + if optarg in optargs: + frac_atts = frac_atts.copy() + frac_atts['numalign'] = optargs[optarg] # "numalign" is deprecated + frac_atts['class'] = 'numalign-' + optargs[optarg] + new_node = frac = mfrac(**frac_atts) + if name.endswith('binom'): + new_node = mrow(mo('('), new_node, mo(')'), CLASS='binom') + new_node.nchildren = 3 + if style_atts: + new_node = mstyle(new_node, **style_atts) + 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: + new_node = mover(mo(accents[name][0], stretchy=False), switch=True) + if name == 'vec': + new_node.children[0]['accent'] = False # scale down arrow but drop i-dot + new_node.tex_cmd = name # for HTML export + 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) + new_node.tex_cmd = name # for HTML export + 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) + new_node.tex_cmd = name # for HTML export + 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.children[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', ... + new_node = mstyle(**layout_styles[name]) + new_node.nchildren = None + if isinstance(node, mrow) and len(node) == 0: + # replace node with new_node + node.parent.children[node.parent.children.index(node)] = new_node + new_node.parent = node.parent + elif node.__class__.__name__ == 'math': + node.append(new_node) + else: + raise SyntaxError(u'Declaration "\\%s" must be first command ' + u'in a group.' % name) + return new_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 -def handle_keyword(name, node, string): - skip = 0 - if len(string) > 0 and string[0] == ' ': - string = string[1:] - skip = 1 if name == 'begin': - if not string.startswith('{matrix}'): - raise SyntaxError(u'Environment not supported! ' - u'Supported environment: "matrix".') - skip += 8 - entry = mtd() - table = mtable(mtr(entry)) - node.append(table) - node = entry - elif name == 'end': - if not string.startswith('{matrix}'): - raise SyntaxError(ur'Expected "\end{matrix}"!') - skip += 8 - node = node.close().close().close() - elif name in ('text', 'mathrm'): - if string[0] != '{': - raise SyntaxError(ur'Expected "\text{...}"!') - i = string.find('}') - if i == -1: - raise SyntaxError(ur'Expected "\text{...}"!') - node = node.append(mtext(string[1:i])) - skip += i + 1 - elif name == 'sqrt': - sqrt = msqrt() - node.append(sqrt) - node = sqrt - elif name == 'frac': - frac = mfrac() - node.append(frac) - node = frac - elif name == 'left': - for par in ['(', '[', '|', '\\{', '\\langle', '.']: - if string.startswith(par): - break - else: - raise SyntaxError(u'Missing left-brace!') - fenced = mfenced(par) - node.append(fenced) - row = mrow() - fenced.append(row) - node = row - skip += len(par) - elif name == 'right': - for par in [')', ']', '|', '\\}', '\\rangle', '.']: - if string.startswith(par): - break + return begin_environment(node, string) + + if name == 'end': + return end_environment(node, string) + + raise SyntaxError(u'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('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('mathrm', math(), '\\alpha') +# (math(mi('α', mathvariant='normal')), '') +# >>> handle_cmd('mathrm', math(), '{out} = 3') +# (math(mi('out', mathvariant='normal')), ' = 3') +# >>> 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), '{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}') + +def handle_script_or_limit(node, c, limits=''): + """Append script or limit element to `node`.""" + child = node.children.pop() + if limits == 'limits': + child['movablelimits'] = False + elif (limits == 'movablelimits' + or getattr(child, 'data', '') in movablelimits): + child['movablelimits'] = True + + if c == '_': + if isinstance(child, mover): + new_node = munderover(*child.children, switch=True) + elif isinstance(child, msup): + new_node = msubsup(*child.children, switch=True) + elif (limits in ('limits', 'movablelimits') + or limits == '' + and child.get('movablelimits', None) == True): + new_node = munder(child) else: - raise SyntaxError(u'Missing right-brace!') - node = node.close() - node.closepar = par - node = node.close() - skip += len(par) - elif name == 'not': - for operator in negatables: - if string.startswith(operator): - break + new_node = msub(child) + elif c == '^': + if isinstance(child, munder): + new_node = munderover(*child.children) + elif isinstance(child, msub): + new_node = msubsup(*child.children) + elif (limits in ('limits', 'movablelimits') + or limits == '' + and child.get('movablelimits', None) == True): + new_node = mover(child) else: - raise SyntaxError(ur'Expected something to negate: "\not ..."!') - node = node.append(mo(negatables[operator])) - skip += len(operator) - elif name == 'mathbf': - style = mstyle(nchildren=1, fontweight='bold') - node.append(style) - node = style - elif name == 'mathbb': - if string[0] != '{' or not string[1].isupper() or string[2] != '}': - raise SyntaxError(ur'Expected something like "\mathbb{A}"!') - node = node.append(mi(mathbb[string[1]])) - skip += 3 - elif name in ('mathscr', 'mathcal'): - if string[0] != '{' or string[2] != '}': - raise SyntaxError(ur'Expected something like "\mathscr{A}"!') - node = node.append(mi(mathscr[string[1]])) - skip += 3 - elif name == 'colon': # "normal" colon, not binary operator - node = node.append(mo(':')) # TODO: add ``lspace="0pt"`` - elif name in Greek: # Greek capitals (upright in "TeX style") - node = node.append(mo(Greek[name])) - # TODO: "ISO style" sets them italic. Could we use a class argument - # to enable styling via CSS? - elif name in letters: - node = node.append(mi(letters[name])) - elif name in special: - node = node.append(mo(special[name])) - elif name in functions: - node = node.append(mo(name)) - elif name in over: - ovr = mover(mo(over[name]), reversed=True) - node.append(ovr) - node = ovr + 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' + node.append(wrapper) + node = wrapper + elif name == 'smallmatrix': + attributes['rowspacing'] = '0.02em' + attributes['columnspacing'] = '0.333em' + wrapper = mstyle(scriptlevel='1') + node.append(wrapper) + node = wrapper + # TODO: aligned, alignedat + # take an optional [t], [b] or the default [c] + entry = mtd() + node.append(mtable(mtr(entry), **attributes)) + node = entry + else: + raise SyntaxError(u'Environment 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 SyntaxError(u'Unknown LaTeX command: ' + name) + raise SyntaxError(u'Environment 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: +def align_attributes(rows): + atts = {'class': 'align', + 'displaystyle': True} + 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': 'align', 'displaystyle': True} +# >>> align_attributes(['a &= b']) +# {'class': 'align', 'displaystyle': True, 'columnalign': 'right left', 'columnspacing': '0'} +# >>> align_attributes(['a &= b & a \in S']) +# {'class': 'align', 'displaystyle': True, 'columnalign': 'right left right', 'columnspacing': '0 2em'} +# >>> align_attributes(['a &= b & c &= d']) +# {'class': 'align', 'displaystyle': True, 'columnalign': 'right left right left', 'columnspacing': '0 2em 0'} - return node, skip def tex2mathml(tex_math, inline=True): - """Return string with MathML code corresponding to `tex_math`. - - `inline`=True is for inline math and `inline`=False for displayed math. - """ - - mathml_tree = parse_latex_math(tex_math, inline=inline) - return ''.join(mathml_tree.xml()) + """Return string with MathML code corresponding to `tex_math`. - + Set `inline` to False for displayed math. + """ + # Set up tree + math_tree = math(xmlns='http://www.w3.org/1998/Math/MathML') + node = math_tree + if not inline: + math_tree['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), + **align_attributes(rows))) + parse_latex_math(node, tex_math) + return math_tree.toprettyxml() + +# >>> print(tex2mathml('3')) +# <math xmlns="http://www.w3.org/1998/Math/MathML"> +# <mn>3</mn> +# </math> +# >>> print(tex2mathml('3', inline=False)) +# <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +# <mn>3</mn> +# </math> +# >>> print(tex2mathml(r'a & b \\ c & d', inline=False)) +# <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +# <mtable class="align" columnalign="right left" columnspacing="0" 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', inline=False)) +# <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> +# <mtable class="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/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py b/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py old mode 100644 new mode 100755 index 1f61e23..ce5fc71 --- a/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py @@ -3,7 +3,7 @@ # math2html: convert LaTeX equations to HTML output. # -# Copyright (C) 2009-2011 Alex Fernández +# 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, @@ -11,19 +11,35 @@ # 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 +# .. _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/ -# --end-- -# Alex 20101110 -# eLyXer standalone formula conversion to HTML. +# 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". +import os.path +import sys +import unicodedata +if sys.version_info >= (3, 0): + from urllib.parse import quote_plus +else: + from urllib import quote_plus +from docutils.utils.math import tex2unichar + +if sys.version_info >= (3, 0): + unicode = str #noqa + basestring = str # noqa + + +__version__ = u'1.3 (2021-06-02)' -import sys class Trace(object): "A tracing class" @@ -62,7 +78,7 @@ class Trace(object): def show(cls, message, channel): "Show a message out of a channel" - if sys.version_info < (3,0): + if sys.version_info < (3, 0): message = message.encode('utf-8') channel.write(message + '\n') @@ -73,922 +89,472 @@ class Trace(object): show = classmethod(show) - - -import os.path -import sys - - -class BibStylesConfig(object): - "Configuration class from elyxer.config file" - - abbrvnat = { - - u'@article':u'$authors. $title. <i>$journal</i>,{ {$volume:}$pages,} $month $year.{ doi: $doi.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'cite':u'$surname($year)', - u'default':u'$authors. <i>$title</i>. $publisher, $year.{ URL <a href="$url">$url</a>.}{ $note.}', - } - - alpha = { - - u'@article':u'$authors. $title.{ <i>$journal</i>{, {$volume}{($number)}}{: $pages}{, $year}.}{ <a href="$url">$url</a>.}{ <a href="$filename">$filename</a>.}{ $note.}', - u'cite':u'$Sur$YY', - u'default':u'$authors. $title.{ <i>$journal</i>,} $year.{ <a href="$url">$url</a>.}{ <a href="$filename">$filename</a>.}{ $note.}', - } - - authordate2 = { - - u'@article':u'$authors. $year. $title. <i>$journal</i>, <b>$volume</b>($number), $pages.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@book':u'$authors. $year. <i>$title</i>. $publisher.{ URL <a href="$url">$url</a>.}{ $note.}', - u'cite':u'$surname, $year', - u'default':u'$authors. $year. <i>$title</i>. $publisher.{ URL <a href="$url">$url</a>.}{ $note.}', - } - - default = { - - u'@article':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@book':u'{$authors: }<i>$title</i>{ ($editor, ed.)}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@booklet':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@conference':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@inbook':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@incollection':u'$authors: <i>$title</i>{ in <i>$booktitle</i>{ ($editor, ed.)}}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@inproceedings':u'$authors: “$title”, <i>$booktitle</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@manual':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@mastersthesis':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@misc':u'$authors: <i>$title</i>.{{ $publisher,}{ $howpublished,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@phdthesis':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@proceedings':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@techreport':u'$authors: <i>$title</i>, $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@unpublished':u'$authors: “$title”, <i>$journal</i>, $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'cite':u'$index', - u'default':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - } - - defaulttags = { - u'YY':u'??', u'authors':u'', u'surname':u'', - } - - ieeetr = { - - u'@article':u'$authors, “$title”, <i>$journal</i>, vol. $volume, no. $number, pp. $pages, $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@book':u'$authors, <i>$title</i>. $publisher, $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'cite':u'$index', - u'default':u'$authors, “$title”. $year.{ URL <a href="$url">$url</a>.}{ $note.}', - } - - plain = { - - u'@article':u'$authors. $title.{ <i>$journal</i>{, {$volume}{($number)}}{:$pages}{, $year}.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@book':u'$authors. <i>$title</i>. $publisher,{ $month} $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@incollection':u'$authors. $title.{ In <i>$booktitle</i> {($editor, ed.)}.} $publisher,{ $month} $year.{ URL <a href="$url">$url</a>.}{ $note.}', - u'@inproceedings':u'$authors. $title. { <i>$booktitle</i>{, {$volume}{($number)}}{:$pages}{, $year}.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'cite':u'$index', - u'default':u'{$authors. }$title.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - } - - vancouver = { - - u'@article':u'$authors. $title. <i>$journal</i>, $year{;{<b>$volume</b>}{($number)}{:$pages}}.{ URL: <a href="$url">$url</a>.}{ $note.}', - u'@book':u'$authors. $title. {$publisher, }$year.{ URL: <a href="$url">$url</a>.}{ $note.}', - u'cite':u'$index', - u'default':u'$authors. $title; {$publisher, }$year.{ $howpublished.}{ URL: <a href="$url">$url</a>.}{ $note.}', - } - -class BibTeXConfig(object): - "Configuration class from elyxer.config file" - - replaced = { - u'--':u'—', u'..':u'.', - } - class ContainerConfig(object): "Configuration class from elyxer.config file" - endings = { - u'Align':u'\\end_layout', u'BarredText':u'\\bar', - u'BoldText':u'\\series', u'Cell':u'</cell', - u'ChangeDeleted':u'\\change_unchanged', - u'ChangeInserted':u'\\change_unchanged', u'ColorText':u'\\color', - u'EmphaticText':u'\\emph', u'Hfill':u'\\hfill', u'Inset':u'\\end_inset', - u'Layout':u'\\end_layout', u'LyXFooter':u'\\end_document', - u'LyXHeader':u'\\end_header', u'Row':u'</row', u'ShapedText':u'\\shape', - u'SizeText':u'\\size', u'StrikeOut':u'\\strikeout', - u'TextFamily':u'\\family', u'VersalitasText':u'\\noun', - } - extracttext = { - u'allowed':[u'StringContainer',u'Constant',u'FormulaConstant',], - u'cloned':[u'',], - u'extracted':[u'PlainLayout',u'TaggedText',u'Align',u'Caption',u'TextFamily',u'EmphaticText',u'VersalitasText',u'BarredText',u'SizeText',u'ColorText',u'LangLine',u'Formula',u'Bracket',u'RawText',u'BibTag',u'FormulaNumber',u'AlphaCommand',u'EmptyCommand',u'OneParamFunction',u'SymbolFunction',u'TextFunction',u'FontFunction',u'CombiningFunction',u'DecoratingFunction',u'FormulaSymbol',u'BracketCommand',u'TeXCode',], - } - - startendings = { - u'\\begin_deeper':u'\\end_deeper', u'\\begin_inset':u'\\end_inset', - u'\\begin_layout':u'\\end_layout', + u'allowed': [u'FormulaConstant',], + u'extracted': [ + u'AlphaCommand', + u'Bracket', + u'BracketCommand', + u'CombiningFunction', + u'EmptyCommand', + u'FontFunction', + u'Formula', + u'FormulaNumber', + u'FormulaSymbol', + u'OneParamFunction', + u'OversetFunction', + u'RawText', + u'SpacedCommand', + u'SymbolFunction', + u'TextFunction', + u'UndersetFunction', + ], } - starts = { - u'':u'StringContainer', u'#LyX':u'BlackBox', u'</lyxtabular':u'BlackBox', - u'<cell':u'Cell', u'<column':u'Column', u'<row':u'Row', - u'\\align':u'Align', u'\\bar':u'BarredText', - u'\\bar default':u'BlackBox', u'\\bar no':u'BlackBox', - u'\\begin_body':u'BlackBox', u'\\begin_deeper':u'DeeperList', - u'\\begin_document':u'BlackBox', u'\\begin_header':u'LyXHeader', - u'\\begin_inset Argument':u'ShortTitle', - u'\\begin_inset Box':u'BoxInset', u'\\begin_inset Branch':u'Branch', - u'\\begin_inset Caption':u'Caption', - u'\\begin_inset CommandInset bibitem':u'BiblioEntry', - u'\\begin_inset CommandInset bibtex':u'BibTeX', - u'\\begin_inset CommandInset citation':u'BiblioCitation', - u'\\begin_inset CommandInset href':u'URL', - u'\\begin_inset CommandInset include':u'IncludeInset', - u'\\begin_inset CommandInset index_print':u'PrintIndex', - u'\\begin_inset CommandInset label':u'Label', - u'\\begin_inset CommandInset line':u'LineInset', - u'\\begin_inset CommandInset nomencl_print':u'PrintNomenclature', - u'\\begin_inset CommandInset nomenclature':u'NomenclatureEntry', - u'\\begin_inset CommandInset ref':u'Reference', - u'\\begin_inset CommandInset toc':u'TableOfContents', - u'\\begin_inset ERT':u'ERT', u'\\begin_inset Flex':u'FlexInset', - u'\\begin_inset Flex Chunkref':u'NewfangledChunkRef', - u'\\begin_inset Flex Marginnote':u'SideNote', - u'\\begin_inset Flex Sidenote':u'SideNote', - u'\\begin_inset Flex URL':u'FlexURL', u'\\begin_inset Float':u'Float', - u'\\begin_inset FloatList':u'ListOf', u'\\begin_inset Foot':u'Footnote', - u'\\begin_inset Formula':u'Formula', - u'\\begin_inset FormulaMacro':u'FormulaMacro', - u'\\begin_inset Graphics':u'Image', - u'\\begin_inset Index':u'IndexReference', - u'\\begin_inset Info':u'InfoInset', - u'\\begin_inset LatexCommand bibitem':u'BiblioEntry', - u'\\begin_inset LatexCommand bibtex':u'BibTeX', - u'\\begin_inset LatexCommand cite':u'BiblioCitation', - u'\\begin_inset LatexCommand citealt':u'BiblioCitation', - u'\\begin_inset LatexCommand citep':u'BiblioCitation', - u'\\begin_inset LatexCommand citet':u'BiblioCitation', - u'\\begin_inset LatexCommand htmlurl':u'URL', - u'\\begin_inset LatexCommand index':u'IndexReference', - u'\\begin_inset LatexCommand label':u'Label', - u'\\begin_inset LatexCommand nomenclature':u'NomenclatureEntry', - u'\\begin_inset LatexCommand prettyref':u'Reference', - u'\\begin_inset LatexCommand printindex':u'PrintIndex', - u'\\begin_inset LatexCommand printnomenclature':u'PrintNomenclature', - u'\\begin_inset LatexCommand ref':u'Reference', - u'\\begin_inset LatexCommand tableofcontents':u'TableOfContents', - u'\\begin_inset LatexCommand url':u'URL', - u'\\begin_inset LatexCommand vref':u'Reference', - u'\\begin_inset Marginal':u'SideNote', - u'\\begin_inset Newline':u'NewlineInset', - u'\\begin_inset Newpage':u'NewPageInset', u'\\begin_inset Note':u'Note', - u'\\begin_inset OptArg':u'ShortTitle', - u'\\begin_inset Phantom':u'PhantomText', - u'\\begin_inset Quotes':u'QuoteContainer', - u'\\begin_inset Tabular':u'Table', u'\\begin_inset Text':u'InsetText', - u'\\begin_inset VSpace':u'VerticalSpace', u'\\begin_inset Wrap':u'Wrap', - u'\\begin_inset listings':u'Listing', - u'\\begin_inset script':u'ScriptInset', u'\\begin_inset space':u'Space', - u'\\begin_layout':u'Layout', u'\\begin_layout Abstract':u'Abstract', - u'\\begin_layout Author':u'Author', - u'\\begin_layout Bibliography':u'Bibliography', - u'\\begin_layout Chunk':u'NewfangledChunk', - u'\\begin_layout Description':u'Description', - u'\\begin_layout Enumerate':u'ListItem', - u'\\begin_layout Itemize':u'ListItem', u'\\begin_layout List':u'List', - u'\\begin_layout LyX-Code':u'LyXCode', - u'\\begin_layout Plain':u'PlainLayout', - u'\\begin_layout Standard':u'StandardLayout', - u'\\begin_layout Title':u'Title', u'\\begin_preamble':u'LyXPreamble', - u'\\change_deleted':u'ChangeDeleted', - u'\\change_inserted':u'ChangeInserted', - u'\\change_unchanged':u'BlackBox', u'\\color':u'ColorText', - u'\\color inherit':u'BlackBox', u'\\color none':u'BlackBox', - u'\\emph default':u'BlackBox', u'\\emph off':u'BlackBox', - u'\\emph on':u'EmphaticText', u'\\emph toggle':u'EmphaticText', - u'\\end_body':u'LyXFooter', u'\\family':u'TextFamily', - u'\\family default':u'BlackBox', u'\\family roman':u'BlackBox', - u'\\hfill':u'Hfill', u'\\labelwidthstring':u'BlackBox', - u'\\lang':u'LangLine', u'\\length':u'InsetLength', - u'\\lyxformat':u'LyXFormat', u'\\lyxline':u'LyXLine', - u'\\newline':u'Newline', u'\\newpage':u'NewPage', - u'\\noindent':u'BlackBox', u'\\noun default':u'BlackBox', - u'\\noun off':u'BlackBox', u'\\noun on':u'VersalitasText', - u'\\paragraph_spacing':u'BlackBox', u'\\series bold':u'BoldText', - u'\\series default':u'BlackBox', u'\\series medium':u'BlackBox', - u'\\shape':u'ShapedText', u'\\shape default':u'BlackBox', - u'\\shape up':u'BlackBox', u'\\size':u'SizeText', - u'\\size normal':u'BlackBox', u'\\start_of_appendix':u'StartAppendix', - u'\\strikeout default':u'BlackBox', u'\\strikeout on':u'StrikeOut', - } - - string = { - u'startcommand':u'\\', - } - - table = { - u'headers':[u'<lyxtabular',u'<features',], - } class EscapeConfig(object): "Configuration class from elyxer.config file" chars = { - u'\n':u'', u' -- ':u' — ', u' --- ':u' — ', u'\'':u'’', u'`':u'‘', - } - - commands = { - u'\\InsetSpace \\space{}':u' ', u'\\InsetSpace \\thinspace{}':u' ', - u'\\InsetSpace ~':u' ', u'\\SpecialChar \\-':u'', - u'\\SpecialChar \\@.':u'.', u'\\SpecialChar \\ldots{}':u'…', - u'\\SpecialChar \\menuseparator':u' ▷ ', - u'\\SpecialChar \\nobreakdash-':u'-', u'\\SpecialChar \\slash{}':u'/', - u'\\SpecialChar \\textcompwordmark{}':u'', u'\\backslash':u'\\', + u'\n': u'', + u"'": u'’', + u'`': u'‘', } entities = { - u'&':u'&', u'<':u'<', u'>':u'>', - } - - html = { - u'/>':u'>', - } - - iso885915 = { - u' ':u' ', u' ':u' ', u' ':u' ', + u'&': u'&', + u'<': u'<', + u'>': u'>', } - nonunicode = { - u' ':u' ', - } class FormulaConfig(object): "Configuration class from elyxer.config file" alphacommands = { - u'\\AA':u'Å', u'\\AE':u'Æ', - u'\\AmS':u'<span class="versalitas">AmS</span>', u'\\Angstroem':u'Å', - u'\\DH':u'Ð', u'\\Koppa':u'Ϟ', u'\\L':u'Ł', u'\\Micro':u'µ', u'\\O':u'Ø', - u'\\OE':u'Œ', u'\\Sampi':u'Ϡ', u'\\Stigma':u'Ϛ', u'\\TH':u'Þ', - u'\\aa':u'å', u'\\ae':u'æ', u'\\alpha':u'α', u'\\beta':u'β', - u'\\delta':u'δ', u'\\dh':u'ð', u'\\digamma':u'ϝ', u'\\epsilon':u'ϵ', - u'\\eta':u'η', u'\\eth':u'ð', u'\\gamma':u'γ', u'\\i':u'ı', - u'\\imath':u'ı', u'\\iota':u'ι', u'\\j':u'ȷ', u'\\jmath':u'ȷ', - u'\\kappa':u'κ', u'\\koppa':u'ϟ', u'\\l':u'ł', u'\\lambda':u'λ', - u'\\mu':u'μ', u'\\nu':u'ν', u'\\o':u'ø', u'\\oe':u'œ', u'\\omega':u'ω', - u'\\phi':u'φ', u'\\pi':u'π', u'\\psi':u'ψ', u'\\rho':u'ρ', - u'\\sampi':u'ϡ', u'\\sigma':u'σ', u'\\ss':u'ß', u'\\stigma':u'ϛ', - u'\\tau':u'τ', u'\\tcohm':u'Ω', u'\\textcrh':u'ħ', u'\\th':u'þ', - u'\\theta':u'θ', u'\\upsilon':u'υ', u'\\varDelta':u'∆', - u'\\varGamma':u'Γ', u'\\varLambda':u'Λ', u'\\varOmega':u'Ω', - u'\\varPhi':u'Φ', u'\\varPi':u'Π', u'\\varPsi':u'Ψ', u'\\varSigma':u'Σ', - u'\\varTheta':u'Θ', u'\\varUpsilon':u'Υ', u'\\varXi':u'Ξ', - u'\\varbeta':u'ϐ', u'\\varepsilon':u'ε', u'\\varkappa':u'ϰ', - u'\\varphi':u'φ', u'\\varpi':u'ϖ', u'\\varrho':u'ϱ', u'\\varsigma':u'ς', - u'\\vartheta':u'ϑ', u'\\xi':u'ξ', u'\\zeta':u'ζ', - } + '\\AmS': u'<span class="textsc">AmS</span>', + '\\AA': u'Å', + '\\AE': u'Æ', + '\\DH': u'Ð', + '\\L': u'Ł', + '\\O': u'Ø', + '\\OE': u'Œ', + '\\TH': u'Þ', + '\\aa': u'å', + '\\ae': u'æ', + '\\dh': u'ð', + '\\i': u'ı', + '\\j': u'ȷ', + '\\l': u'ł', + '\\o': u'ø', + '\\oe': u'œ', + '\\ss': u'ß', + '\\th': u'þ', + } + for key, value in tex2unichar.mathalpha.items(): + alphacommands['\\'+key] = value array = { - u'begin':u'\\begin', u'cellseparator':u'&', u'end':u'\\end', - u'rowseparator':u'\\\\', - } - - bigbrackets = { - u'(':[u'⎛',u'⎜',u'⎝',], u')':[u'⎞',u'⎟',u'⎠',], u'[':[u'⎡',u'⎢',u'⎣',], - u']':[u'⎤',u'⎥',u'⎦',], u'{':[u'⎧',u'⎪',u'⎨',u'⎩',], u'|':[u'|',], - u'}':[u'⎫',u'⎪',u'⎬',u'⎭',], u'∥':[u'∥',], - } - - bigsymbols = { - u'∑':[u'⎲',u'⎳',], u'∫':[u'⌠',u'⌡',], - } + u'begin': u'\\begin', + u'cellseparator': u'&', + u'end': u'\\end', + u'rowseparator': u'\\\\', + } + + bigbrackets = {u'(': [u'⎛', u'⎜', u'⎝',], + u')': [u'⎞', u'⎟', u'⎠',], + u'[': [u'⎡', u'⎢', u'⎣',], + u']': [u'⎤', u'⎥', u'⎦',], + u'{': [u'⎧', u'⎪', u'⎨', u'⎩',], + u'}': [u'⎫', u'⎪', u'⎬', u'⎭',], + # TODO: 2-row brackets with ⎰⎱ (\lmoustache \rmoustache) + u'|': [u'|',], # 007C VERTICAL LINE + # u'|': [u'⎮',], # 23AE INTEGRAL EXTENSION + # u'|': [u'⎪',], # 23AA CURLY BRACKET EXTENSION + u'‖': [u'‖'], # 2016 DOUBLE VERTICAL LINE + # u'∥': [u'∥'], # 2225 PARALLEL TO + } bracketcommands = { - u'\\left':u'span class="symbol"', - u'\\left.':u'<span class="leftdot"></span>', - u'\\middle':u'span class="symbol"', u'\\right':u'span class="symbol"', - u'\\right.':u'<span class="rightdot"></span>', + u'\\left': u'span class="stretchy"', + u'\\left.': u'<span class="leftdot"></span>', + u'\\middle': u'span class="stretchy"', + u'\\right': u'span class="stretchy"', + u'\\right.': u'<span class="rightdot"></span>', } combiningfunctions = { - u'\\"':u'̈', u'\\\'':u'́', u'\\^':u'̂', u'\\`':u'̀', u'\\acute':u'́', - u'\\bar':u'̄', u'\\breve':u'̆', u'\\c':u'̧', u'\\check':u'̌', - u'\\dddot':u'⃛', u'\\ddot':u'̈', u'\\dot':u'̇', u'\\grave':u'̀', - u'\\hat':u'̂', u'\\mathring':u'̊', u'\\overleftarrow':u'⃖', - u'\\overrightarrow':u'⃗', u'\\r':u'̊', u'\\s':u'̩', - u'\\textcircled':u'⃝', u'\\textsubring':u'̥', u'\\tilde':u'̃', - u'\\v':u'̌', u'\\vec':u'⃗', u'\\~':u'̃', - } + u"\\'": u'́', + u'\\"': u'̈', + u'\\^': u'̂', + u'\\`': u'̀', + u'\\~': u'̃', + u'\\c': u'̧', + u'\\r': u'̊', + u'\\s': u'̩', + u'\\textcircled': u'⃝', + u'\\textsubring': u'̥', + u'\\v': u'̌', + } + for key, value in tex2unichar.mathaccent.items(): + combiningfunctions['\\'+key] = value commands = { - u'\\ ':u' ', u'\\!':u'', u'\\#':u'#', u'\\$':u'$', u'\\%':u'%', - u'\\&':u'&', u'\\,':u' ', u'\\:':u' ', u'\\;':u' ', u'\\AC':u'∿', - u'\\APLcomment':u'⍝', u'\\APLdownarrowbox':u'⍗', u'\\APLinput':u'⍞', - u'\\APLinv':u'⌹', u'\\APLleftarrowbox':u'⍇', u'\\APLlog':u'⍟', - u'\\APLrightarrowbox':u'⍈', u'\\APLuparrowbox':u'⍐', u'\\Box':u'□', - u'\\Bumpeq':u'≎', u'\\CIRCLE':u'●', u'\\Cap':u'⋒', - u'\\CapitalDifferentialD':u'ⅅ', u'\\CheckedBox':u'☑', u'\\Circle':u'○', - u'\\Coloneqq':u'⩴', u'\\ComplexI':u'ⅈ', u'\\ComplexJ':u'ⅉ', - u'\\Corresponds':u'≙', u'\\Cup':u'⋓', u'\\Delta':u'Δ', u'\\Diamond':u'◇', - u'\\Diamondblack':u'◆', u'\\Diamonddot':u'⟐', u'\\DifferentialD':u'ⅆ', - u'\\Downarrow':u'⇓', u'\\EUR':u'€', u'\\Euler':u'ℇ', - u'\\ExponetialE':u'ⅇ', u'\\Finv':u'Ⅎ', u'\\Game':u'⅁', u'\\Gamma':u'Γ', - u'\\Im':u'ℑ', u'\\Join':u'⨝', u'\\LEFTCIRCLE':u'◖', u'\\LEFTcircle':u'◐', - u'\\LHD':u'◀', u'\\Lambda':u'Λ', u'\\Lbag':u'⟅', u'\\Leftarrow':u'⇐', - u'\\Lleftarrow':u'⇚', u'\\Longleftarrow':u'⟸', - u'\\Longleftrightarrow':u'⟺', u'\\Longrightarrow':u'⟹', u'\\Lparen':u'⦅', - u'\\Lsh':u'↰', u'\\Mapsfrom':u'⇐|', u'\\Mapsto':u'|⇒', u'\\Omega':u'Ω', - u'\\P':u'¶', u'\\Phi':u'Φ', u'\\Pi':u'Π', u'\\Pr':u'Pr', u'\\Psi':u'Ψ', - u'\\Qoppa':u'Ϙ', u'\\RHD':u'▶', u'\\RIGHTCIRCLE':u'◗', - u'\\RIGHTcircle':u'◑', u'\\Rbag':u'⟆', u'\\Re':u'ℜ', u'\\Rparen':u'⦆', - u'\\Rrightarrow':u'⇛', u'\\Rsh':u'↱', u'\\S':u'§', u'\\Sigma':u'Σ', - u'\\Square':u'☐', u'\\Subset':u'⋐', u'\\Sun':u'☉', u'\\Supset':u'⋑', - u'\\Theta':u'Θ', u'\\Uparrow':u'⇑', u'\\Updownarrow':u'⇕', - u'\\Upsilon':u'Υ', u'\\Vdash':u'⊩', u'\\Vert':u'∥', u'\\Vvdash':u'⊪', - u'\\XBox':u'☒', u'\\Xi':u'Ξ', u'\\Yup':u'⅄', u'\\\\':u'<br/>', - u'\\_':u'_', u'\\aleph':u'ℵ', u'\\amalg':u'∐', u'\\anchor':u'⚓', - u'\\angle':u'∠', u'\\aquarius':u'♒', u'\\arccos':u'arccos', - u'\\arcsin':u'arcsin', u'\\arctan':u'arctan', u'\\arg':u'arg', - u'\\aries':u'♈', u'\\arrowbullet':u'➢', u'\\ast':u'∗', u'\\asymp':u'≍', - u'\\backepsilon':u'∍', u'\\backprime':u'‵', u'\\backsimeq':u'⋍', - u'\\backslash':u'\\', u'\\ballotx':u'✗', u'\\barwedge':u'⊼', - u'\\because':u'∵', u'\\beth':u'ℶ', u'\\between':u'≬', u'\\bigcap':u'∩', - u'\\bigcirc':u'○', u'\\bigcup':u'∪', u'\\bigodot':u'⊙', - u'\\bigoplus':u'⊕', u'\\bigotimes':u'⊗', u'\\bigsqcup':u'⊔', - u'\\bigstar':u'★', u'\\bigtriangledown':u'▽', u'\\bigtriangleup':u'△', - u'\\biguplus':u'⊎', u'\\bigvee':u'∨', u'\\bigwedge':u'∧', - u'\\biohazard':u'☣', u'\\blacklozenge':u'⧫', u'\\blacksmiley':u'☻', - u'\\blacksquare':u'■', u'\\blacktriangle':u'▲', - u'\\blacktriangledown':u'▼', u'\\blacktriangleleft':u'◂', - u'\\blacktriangleright':u'▶', u'\\blacktriangleup':u'▴', u'\\bot':u'⊥', - u'\\bowtie':u'⋈', u'\\box':u'▫', u'\\boxast':u'⧆', u'\\boxbar':u'◫', - u'\\boxbox':u'⧈', u'\\boxbslash':u'⧅', u'\\boxcircle':u'⧇', - u'\\boxdot':u'⊡', u'\\boxminus':u'⊟', u'\\boxplus':u'⊞', - u'\\boxslash':u'⧄', u'\\boxtimes':u'⊠', u'\\bullet':u'•', - u'\\bumpeq':u'≏', u'\\cancer':u'♋', u'\\cap':u'∩', u'\\capricornus':u'♑', - u'\\cat':u'⁀', u'\\cdot':u'⋅', u'\\cdots':u'⋯', u'\\cent':u'¢', - u'\\centerdot':u'∙', u'\\checkmark':u'✓', u'\\chi':u'χ', u'\\circ':u'∘', - u'\\circeq':u'≗', u'\\circlearrowleft':u'↺', u'\\circlearrowright':u'↻', - u'\\circledR':u'®', u'\\circledast':u'⊛', u'\\circledbslash':u'⦸', - u'\\circledcirc':u'⊚', u'\\circleddash':u'⊝', u'\\circledgtr':u'⧁', - u'\\circledless':u'⧀', u'\\clubsuit':u'♣', u'\\colon':u': ', u'\\coloneqq':u'≔', - u'\\complement':u'∁', u'\\cong':u'≅', u'\\coprod':u'∐', - u'\\copyright':u'©', u'\\cos':u'cos', u'\\cosh':u'cosh', u'\\cot':u'cot', - u'\\coth':u'coth', u'\\csc':u'csc', u'\\cup':u'∪', u'\\curlyvee':u'⋎', - u'\\curlywedge':u'⋏', u'\\curvearrowleft':u'↶', - u'\\curvearrowright':u'↷', u'\\dag':u'†', u'\\dagger':u'†', - u'\\daleth':u'ℸ', u'\\dashleftarrow':u'⇠', u'\\dashv':u'⊣', - u'\\ddag':u'‡', u'\\ddagger':u'‡', u'\\ddots':u'⋱', u'\\deg':u'deg', - u'\\det':u'det', u'\\diagdown':u'╲', u'\\diagup':u'╱', - u'\\diameter':u'⌀', u'\\diamond':u'◇', u'\\diamondsuit':u'♦', - u'\\dim':u'dim', u'\\div':u'÷', u'\\divideontimes':u'⋇', - u'\\dotdiv':u'∸', u'\\doteq':u'≐', u'\\doteqdot':u'≑', u'\\dotplus':u'∔', - u'\\dots':u'…', u'\\doublebarwedge':u'⌆', u'\\downarrow':u'↓', - u'\\downdownarrows':u'⇊', u'\\downharpoonleft':u'⇃', - u'\\downharpoonright':u'⇂', u'\\dsub':u'⩤', u'\\earth':u'♁', - u'\\eighthnote':u'♪', u'\\ell':u'ℓ', u'\\emptyset':u'∅', - u'\\eqcirc':u'≖', u'\\eqcolon':u'≕', u'\\eqsim':u'≂', u'\\euro':u'€', - u'\\exists':u'∃', u'\\exp':u'exp', u'\\fallingdotseq':u'≒', - u'\\fcmp':u'⨾', u'\\female':u'♀', u'\\flat':u'♭', u'\\forall':u'∀', - u'\\fourth':u'⁗', u'\\frown':u'⌢', u'\\frownie':u'☹', u'\\gcd':u'gcd', - u'\\gemini':u'♊', u'\\geq)':u'≥', u'\\geqq':u'≧', u'\\geqslant':u'≥', - u'\\gets':u'←', u'\\gg':u'≫', u'\\ggg':u'⋙', u'\\gimel':u'ℷ', - u'\\gneqq':u'≩', u'\\gnsim':u'⋧', u'\\gtrdot':u'⋗', u'\\gtreqless':u'⋚', - u'\\gtreqqless':u'⪌', u'\\gtrless':u'≷', u'\\gtrsim':u'≳', - u'\\guillemotleft':u'«', u'\\guillemotright':u'»', u'\\hbar':u'ℏ', - u'\\heartsuit':u'♥', u'\\hfill':u'<span class="hfill"> </span>', - u'\\hom':u'hom', u'\\hookleftarrow':u'↩', u'\\hookrightarrow':u'↪', - u'\\hslash':u'ℏ', u'\\idotsint':u'<span class="bigsymbol">∫⋯∫</span>', - u'\\iiint':u'<span class="bigsymbol">∭</span>', - u'\\iint':u'<span class="bigsymbol">∬</span>', u'\\imath':u'ı', - u'\\inf':u'inf', u'\\infty':u'∞', u'\\intercal':u'⊺', - u'\\interleave':u'⫴', u'\\invamp':u'⅋', u'\\invneg':u'⌐', - u'\\jmath':u'ȷ', u'\\jupiter':u'♃', u'\\ker':u'ker', u'\\land':u'∧', - u'\\landupint':u'<span class="bigsymbol">∱</span>', u'\\lang':u'⟪', - u'\\langle':u'⟨', u'\\lblot':u'⦉', u'\\lbrace':u'{', u'\\lbrace)':u'{', - u'\\lbrack':u'[', u'\\lceil':u'⌈', u'\\ldots':u'…', u'\\leadsto':u'⇝', - u'\\leftarrow)':u'←', u'\\leftarrowtail':u'↢', u'\\leftarrowtobar':u'⇤', - u'\\leftharpoondown':u'↽', u'\\leftharpoonup':u'↼', - u'\\leftleftarrows':u'⇇', u'\\leftleftharpoons':u'⥢', u'\\leftmoon':u'☾', - u'\\leftrightarrow':u'↔', u'\\leftrightarrows':u'⇆', - u'\\leftrightharpoons':u'⇋', u'\\leftthreetimes':u'⋋', u'\\leo':u'♌', - u'\\leq)':u'≤', u'\\leqq':u'≦', u'\\leqslant':u'≤', u'\\lessdot':u'⋖', - u'\\lesseqgtr':u'⋛', u'\\lesseqqgtr':u'⪋', u'\\lessgtr':u'≶', - u'\\lesssim':u'≲', u'\\lfloor':u'⌊', u'\\lg':u'lg', u'\\lgroup':u'⟮', - u'\\lhd':u'⊲', u'\\libra':u'♎', u'\\lightning':u'↯', u'\\limg':u'⦇', - u'\\liminf':u'liminf', u'\\limsup':u'limsup', u'\\ll':u'≪', - u'\\llbracket':u'⟦', u'\\llcorner':u'⌞', u'\\lll':u'⋘', u'\\ln':u'ln', - u'\\lneqq':u'≨', u'\\lnot':u'¬', u'\\lnsim':u'⋦', u'\\log':u'log', - u'\\longleftarrow':u'⟵', u'\\longleftrightarrow':u'⟷', - u'\\longmapsto':u'⟼', u'\\longrightarrow':u'⟶', u'\\looparrowleft':u'↫', - u'\\looparrowright':u'↬', u'\\lor':u'∨', u'\\lozenge':u'◊', - u'\\lrcorner':u'⌟', u'\\ltimes':u'⋉', u'\\lyxlock':u'', u'\\male':u'♂', - u'\\maltese':u'✠', u'\\mapsfrom':u'↤', u'\\mapsto':u'↦', - u'\\mathcircumflex':u'^', u'\\max':u'max', u'\\measuredangle':u'∡', - u'\\medbullet':u'⚫', u'\\medcirc':u'⚪', u'\\mercury':u'☿', u'\\mho':u'℧', - u'\\mid':u'∣', u'\\min':u'min', u'\\models':u'⊨', u'\\mp':u'∓', - u'\\multimap':u'⊸', u'\\nLeftarrow':u'⇍', u'\\nLeftrightarrow':u'⇎', - u'\\nRightarrow':u'⇏', u'\\nVDash':u'⊯', u'\\nabla':u'∇', - u'\\napprox':u'≉', u'\\natural':u'♮', u'\\ncong':u'≇', u'\\nearrow':u'↗', - u'\\neg':u'¬', u'\\neg)':u'¬', u'\\neptune':u'♆', u'\\nequiv':u'≢', - u'\\newline':u'<br/>', u'\\nexists':u'∄', u'\\ngeqslant':u'≱', - u'\\ngtr':u'≯', u'\\ngtrless':u'≹', u'\\ni':u'∋', u'\\ni)':u'∋', - u'\\nleftarrow':u'↚', u'\\nleftrightarrow':u'↮', u'\\nleqslant':u'≰', - u'\\nless':u'≮', u'\\nlessgtr':u'≸', u'\\nmid':u'∤', u'\\nolimits':u'', - u'\\nonumber':u'', u'\\not':u'¬', u'\\not<':u'≮', u'\\not=':u'≠', - u'\\not>':u'≯', u'\\notbackslash':u'⍀', u'\\notin':u'∉', u'\\notni':u'∌', - u'\\notslash':u'⌿', u'\\nparallel':u'∦', u'\\nprec':u'⊀', - u'\\nrightarrow':u'↛', u'\\nsim':u'≁', u'\\nsimeq':u'≄', - u'\\nsqsubset':u'⊏̸', u'\\nsubseteq':u'⊈', u'\\nsucc':u'⊁', - u'\\nsucccurlyeq':u'⋡', u'\\nsupset':u'⊅', u'\\nsupseteq':u'⊉', - u'\\ntriangleleft':u'⋪', u'\\ntrianglelefteq':u'⋬', - u'\\ntriangleright':u'⋫', u'\\ntrianglerighteq':u'⋭', u'\\nvDash':u'⊭', - u'\\nvdash':u'⊬', u'\\nwarrow':u'↖', u'\\odot':u'⊙', - u'\\officialeuro':u'€', u'\\oiiint':u'<span class="bigsymbol">∰</span>', - u'\\oiint':u'<span class="bigsymbol">∯</span>', - u'\\oint':u'<span class="bigsymbol">∮</span>', - u'\\ointclockwise':u'<span class="bigsymbol">∲</span>', - u'\\ointctrclockwise':u'<span class="bigsymbol">∳</span>', - u'\\ominus':u'⊖', u'\\oplus':u'⊕', u'\\oslash':u'⊘', u'\\otimes':u'⊗', - u'\\owns':u'∋', u'\\parallel':u'∥', u'\\partial':u'∂', u'\\pencil':u'✎', - u'\\perp':u'⊥', u'\\pisces':u'♓', u'\\pitchfork':u'⋔', u'\\pluto':u'♇', - u'\\pm':u'±', u'\\pointer':u'➪', u'\\pointright':u'☞', u'\\pounds':u'£', - u'\\prec':u'≺', u'\\preccurlyeq':u'≼', u'\\preceq':u'≼', - u'\\precsim':u'≾', u'\\prime':u'′', u'\\prompto':u'∝', u'\\qoppa':u'ϙ', - u'\\qquad':u' ', u'\\quad':u' ', u'\\quarternote':u'♩', - u'\\radiation':u'☢', u'\\rang':u'⟫', u'\\rangle':u'⟩', u'\\rblot':u'⦊', - u'\\rbrace':u'}', u'\\rbrace)':u'}', u'\\rbrack':u']', u'\\rceil':u'⌉', - u'\\recycle':u'♻', u'\\rfloor':u'⌋', u'\\rgroup':u'⟯', u'\\rhd':u'⊳', - u'\\rightangle':u'∟', u'\\rightarrow)':u'→', u'\\rightarrowtail':u'↣', - u'\\rightarrowtobar':u'⇥', u'\\rightharpoondown':u'⇁', - u'\\rightharpoonup':u'⇀', u'\\rightharpooondown':u'⇁', - u'\\rightharpooonup':u'⇀', u'\\rightleftarrows':u'⇄', - u'\\rightleftharpoons':u'⇌', u'\\rightmoon':u'☽', - u'\\rightrightarrows':u'⇉', u'\\rightrightharpoons':u'⥤', - u'\\rightthreetimes':u'⋌', u'\\rimg':u'⦈', u'\\risingdotseq':u'≓', - u'\\rrbracket':u'⟧', u'\\rsub':u'⩥', u'\\rtimes':u'⋊', - u'\\sagittarius':u'♐', u'\\saturn':u'♄', u'\\scorpio':u'♏', - u'\\searrow':u'↘', u'\\sec':u'sec', u'\\second':u'″', u'\\setminus':u'∖', - u'\\sharp':u'♯', u'\\simeq':u'≃', u'\\sin':u'sin', u'\\sinh':u'sinh', - u'\\sixteenthnote':u'♬', u'\\skull':u'☠', u'\\slash':u'∕', - u'\\smallsetminus':u'∖', u'\\smalltriangledown':u'▿', - u'\\smalltriangleleft':u'◃', u'\\smalltriangleright':u'▹', - u'\\smalltriangleup':u'▵', u'\\smile':u'⌣', u'\\smiley':u'☺', - u'\\spadesuit':u'♠', u'\\spddot':u'¨', u'\\sphat':u'', - u'\\sphericalangle':u'∢', u'\\spot':u'⦁', u'\\sptilde':u'~', - u'\\sqcap':u'⊓', u'\\sqcup':u'⊔', u'\\sqsubset':u'⊏', - u'\\sqsubseteq':u'⊑', u'\\sqsupset':u'⊐', u'\\sqsupseteq':u'⊒', - u'\\square':u'□', u'\\sslash':u'⫽', u'\\star':u'⋆', u'\\steaming':u'☕', - u'\\subseteqq':u'⫅', u'\\subsetneqq':u'⫋', u'\\succ':u'≻', - u'\\succcurlyeq':u'≽', u'\\succeq':u'≽', u'\\succnsim':u'⋩', - u'\\succsim':u'≿', u'\\sun':u'☼', u'\\sup':u'sup', u'\\supseteqq':u'⫆', - u'\\supsetneqq':u'⫌', u'\\surd':u'√', u'\\swarrow':u'↙', - u'\\swords':u'⚔', u'\\talloblong':u'⫾', u'\\tan':u'tan', - u'\\tanh':u'tanh', u'\\taurus':u'♉', u'\\textasciicircum':u'^', - u'\\textasciitilde':u'~', u'\\textbackslash':u'\\', - u'\\textcopyright':u'©\'', u'\\textdegree':u'°', u'\\textellipsis':u'…', - u'\\textemdash':u'—', u'\\textendash':u'—', u'\\texteuro':u'€', - u'\\textgreater':u'>', u'\\textless':u'<', u'\\textordfeminine':u'ª', - u'\\textordmasculine':u'º', u'\\textquotedblleft':u'“', - u'\\textquotedblright':u'”', u'\\textquoteright':u'’', - u'\\textregistered':u'®', u'\\textrightarrow':u'→', - u'\\textsection':u'§', u'\\texttrademark':u'™', - u'\\texttwosuperior':u'²', u'\\textvisiblespace':u' ', - u'\\therefore':u'∴', u'\\third':u'‴', u'\\top':u'⊤', u'\\triangle':u'△', - u'\\triangleleft':u'⊲', u'\\trianglelefteq':u'⊴', u'\\triangleq':u'≜', - u'\\triangleright':u'▷', u'\\trianglerighteq':u'⊵', - u'\\twoheadleftarrow':u'↞', u'\\twoheadrightarrow':u'↠', - u'\\twonotes':u'♫', u'\\udot':u'⊍', u'\\ulcorner':u'⌜', u'\\unlhd':u'⊴', - u'\\unrhd':u'⊵', u'\\unrhl':u'⊵', u'\\uparrow':u'↑', - u'\\updownarrow':u'↕', u'\\upharpoonleft':u'↿', u'\\upharpoonright':u'↾', - u'\\uplus':u'⊎', u'\\upuparrows':u'⇈', u'\\uranus':u'♅', - u'\\urcorner':u'⌝', u'\\vDash':u'⊨', u'\\varclubsuit':u'♧', - u'\\vardiamondsuit':u'♦', u'\\varheartsuit':u'♥', u'\\varnothing':u'∅', - u'\\varspadesuit':u'♤', u'\\vdash':u'⊢', u'\\vdots':u'⋮', u'\\vee':u'∨', - u'\\vee)':u'∨', u'\\veebar':u'⊻', u'\\vert':u'∣', u'\\virgo':u'♍', - u'\\warning':u'⚠', u'\\wasylozenge':u'⌑', u'\\wedge':u'∧', - u'\\wedge)':u'∧', u'\\wp':u'℘', u'\\wr':u'≀', u'\\yen':u'¥', - u'\\yinyang':u'☯', u'\\{':u'{', u'\\|':u'∥', u'\\}':u'}', - } - - decoratedcommand = { - - } - - decoratingfunctions = { - u'\\overleftarrow':u'⟵', u'\\overrightarrow':u'⟶', u'\\widehat':u'^', - } + '\\\\': u'<br/>', + '\\\n': u' ', # escaped whitespace + '\\\t': u' ', # escaped whitespace + '\\centerdot': u'\u2B1D', # BLACK VERY SMALL SQUARE, mathbin + '\\colon': u': ', + '\\copyright': u'©', + '\\dotminus': u'∸', + '\\dots': u'…', + '\\dotsb': u'⋯', + '\\dotsc': u'…', + '\\dotsi': u'⋯', + '\\dotsm': u'⋯', + '\\dotso': u'…', + '\\euro': u'€', + '\\guillemotleft': u'«', + '\\guillemotright': u'»', + '\\hbar': u'<i>\u0127</i>', # ħ LATIN SMALL LETTER H WITH STROKE + '\\lVert': u'‖', + '\\Arrowvert': u'\u2016', # ‖ + '\\lvert': u'|', + '\\newline': u'<br/>', + '\\nobreakspace': u' ', + '\\nolimits': u'', + '\\nonumber': u'', + '\\qquad': u' ', + '\\rVert': u'‖', + '\\rvert': u'|', + '\\textasciicircum': u'^', + '\\textasciitilde': u'~', + '\\textbackslash': u'\\', + '\\textcopyright': u'©', + '\\textdegree': u'°', + '\\textellipsis': u'…', + '\\textemdash': u'—', + '\\textendash': u'—', + '\\texteuro': u'€', + '\\textgreater': u'>', + '\\textless': u'<', + '\\textordfeminine': u'ª', + '\\textordmasculine': u'º', + '\\textquotedblleft': u'“', + '\\textquotedblright': u'”', + '\\textquoteright': u'’', + '\\textregistered': u'®', + '\\textrightarrow': u'→', + '\\textsection': u'§', + '\\texttrademark': u'™', + '\\texttwosuperior': u'²', + '\\textvisiblespace': u' ', + '\\thickspace': u'<span class="thickspace"> </span>', # 5/13 em + '\\;': u'<span class="thickspace"> </span>', # 5/13 em + '\\triangle': u'\u25B3', # WHITE UP-POINTING TRIANGLE, mathord + '\\triangledown': u'\u25BD', # WHITE DOWN-POINTING TRIANGLE, mathord + '\\varnothing': u'\u2300', # ⌀ DIAMETER SIGN + # functions + '\\Pr': u'Pr', + '\\arccos': u'arccos', + '\\arcsin': u'arcsin', + '\\arctan': u'arctan', + '\\arg': u'arg', + '\\cos': u'cos', + '\\cosh': u'cosh', + '\\cot': u'cot', + '\\coth': u'coth', + '\\csc': u'csc', + '\\deg': u'deg', + '\\det': u'det', + '\\dim': u'dim', + '\\exp': u'exp', + '\\gcd': u'gcd', + '\\hom': u'hom', + '\\injlim': u'inj lim', + '\\ker': u'ker', + '\\lg': u'lg', + '\\liminf': u'lim inf', + '\\limsup': u'lim sup', + '\\ln': u'ln', + '\\log': u'log', + '\\projlim': u'proj lim', + '\\sec': u'sec', + '\\sin': u'sin', + '\\sinh': u'sinh', + '\\tan': u'tan', + '\\tanh': u'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': u'´', + '\\bar': u'‒', # FIGURE DASH + # '\\breve': u'˘', + # '\\check': u'ˇ', + '\\dddot': u'<span class="smallsymbol">⋯</span>', + # '\\ddot': u'··', # ¨ too high + # '\\dot': u'·', + # '\\grave': u'`', + # '\\hat': u'^', + # '\\mathring': u'˚', + # '\\tilde': u'~', + '\\vec': u'<span class="smallsymbol">→</span>', + # embellishments + '\\overleftarrow': u'⟵', + '\\overleftrightarrow': u'⟷', + '\\overrightarrow': u'⟶', + '\\widehat': u'^', + '\\widetilde': u'~', + } + + undersetfunctions = { + '\\underleftarrow': u'⟵', + '\\underleftrightarrow': u'⟷', + '\\underrightarrow': u'⟶', + } endings = { - u'bracket':u'}', u'complex':u'\\]', u'endafter':u'}', - u'endbefore':u'\\end{', u'squarebracket':u']', + u'bracket': u'}', + u'complex': u'\\]', + u'endafter': u'}', + u'endbefore': u'\\end{', + u'squarebracket': u']', } environments = { - u'align':[u'r',u'l',], u'eqnarray':[u'r',u'c',u'l',], - u'gathered':[u'l',u'l',], + u'align': [u'r', u'l',], + u'eqnarray': [u'r', u'c', u'l',], + u'gathered': [u'l', u'l',], + u'smallmatrix': [u'c', u'c',], } fontfunctions = { - u'\\boldsymbol':u'b', u'\\mathbb':u'span class="blackboard"', - u'\\mathbb{A}':u'𝔸', u'\\mathbb{B}':u'𝔹', u'\\mathbb{C}':u'ℂ', - u'\\mathbb{D}':u'𝔻', u'\\mathbb{E}':u'𝔼', u'\\mathbb{F}':u'𝔽', - u'\\mathbb{G}':u'𝔾', u'\\mathbb{H}':u'ℍ', u'\\mathbb{J}':u'𝕁', - u'\\mathbb{K}':u'𝕂', u'\\mathbb{L}':u'𝕃', u'\\mathbb{N}':u'ℕ', - u'\\mathbb{O}':u'𝕆', u'\\mathbb{P}':u'ℙ', u'\\mathbb{Q}':u'ℚ', - u'\\mathbb{R}':u'ℝ', u'\\mathbb{S}':u'𝕊', u'\\mathbb{T}':u'𝕋', - u'\\mathbb{W}':u'𝕎', u'\\mathbb{Z}':u'ℤ', u'\\mathbf':u'b', - u'\\mathcal':u'span class="scriptfont"', u'\\mathcal{B}':u'ℬ', - u'\\mathcal{E}':u'ℰ', u'\\mathcal{F}':u'ℱ', u'\\mathcal{H}':u'ℋ', - u'\\mathcal{I}':u'ℐ', u'\\mathcal{L}':u'ℒ', u'\\mathcal{M}':u'ℳ', - u'\\mathcal{R}':u'ℛ', u'\\mathfrak':u'span class="fraktur"', - u'\\mathfrak{C}':u'ℭ', u'\\mathfrak{F}':u'𝔉', u'\\mathfrak{H}':u'ℌ', - u'\\mathfrak{I}':u'ℑ', u'\\mathfrak{R}':u'ℜ', u'\\mathfrak{Z}':u'ℨ', - u'\\mathit':u'i', u'\\mathring{A}':u'Å', u'\\mathring{U}':u'Ů', - u'\\mathring{a}':u'å', u'\\mathring{u}':u'ů', u'\\mathring{w}':u'ẘ', - u'\\mathring{y}':u'ẙ', u'\\mathrm':u'span class="mathrm"', - u'\\mathscr':u'span class="scriptfont"', u'\\mathscr{B}':u'ℬ', - u'\\mathscr{E}':u'ℰ', u'\\mathscr{F}':u'ℱ', u'\\mathscr{H}':u'ℋ', - u'\\mathscr{I}':u'ℐ', u'\\mathscr{L}':u'ℒ', u'\\mathscr{M}':u'ℳ', - u'\\mathscr{R}':u'ℛ', u'\\mathsf':u'span class="mathsf"', - u'\\mathtt':u'tt', + u'\\boldsymbol': u'b', u'\\mathbb': u'span class="blackboard"', + u'\\mathbb{A}': u'𝔸', u'\\mathbb{B}': u'𝔹', u'\\mathbb{C}': u'ℂ', + u'\\mathbb{D}': u'𝔻', u'\\mathbb{E}': u'𝔼', u'\\mathbb{F}': u'𝔽', + u'\\mathbb{G}': u'𝔾', u'\\mathbb{H}': u'ℍ', u'\\mathbb{J}': u'𝕁', + u'\\mathbb{K}': u'𝕂', u'\\mathbb{L}': u'𝕃', u'\\mathbb{N}': u'ℕ', + u'\\mathbb{O}': u'𝕆', u'\\mathbb{P}': u'ℙ', u'\\mathbb{Q}': u'ℚ', + u'\\mathbb{R}': u'ℝ', u'\\mathbb{S}': u'𝕊', u'\\mathbb{T}': u'𝕋', + u'\\mathbb{W}': u'𝕎', u'\\mathbb{Z}': u'ℤ', u'\\mathbf': u'b', + u'\\mathcal': u'span class="scriptfont"', + u'\\mathcal{B}': u'ℬ', u'\\mathcal{E}': u'ℰ', u'\\mathcal{F}': + u'ℱ', u'\\mathcal{H}': u'ℋ', u'\\mathcal{I}': u'ℐ', + u'\\mathcal{L}': u'ℒ', u'\\mathcal{M}': u'ℳ', u'\\mathcal{R}': u'ℛ', + u'\\mathfrak': u'span class="fraktur"', + u'\\mathfrak{C}': u'ℭ', u'\\mathfrak{F}': u'𝔉', u'\\mathfrak{H}': u'ℌ', + u'\\mathfrak{I}': u'ℑ', u'\\mathfrak{R}': u'ℜ', u'\\mathfrak{Z}': u'ℨ', + u'\\mathit': u'i', + u'\\mathring{A}': u'Å', u'\\mathring{U}': u'Ů', + u'\\mathring{a}': u'å', u'\\mathring{u}': u'ů', u'\\mathring{w}': u'ẘ', + u'\\mathring{y}': u'ẙ', + u'\\mathrm': u'span class="mathrm"', + u'\\mathscr': u'span class="mathscr"', + u'\\mathscr{B}': u'ℬ', u'\\mathscr{E}': u'ℰ', u'\\mathscr{F}': u'ℱ', + u'\\mathscr{H}': u'ℋ', u'\\mathscr{I}': u'ℐ', u'\\mathscr{L}': u'ℒ', + u'\\mathscr{M}': u'ℳ', u'\\mathscr{R}': u'ℛ', + u'\\mathsf': u'span class="mathsf"', + u'\\mathtt': u'span class="mathtt"', + u'\\operatorname': u'span class="mathrm"', } hybridfunctions = { - u'\\addcontentsline':[u'{$p!}{$q!}{$r!}',u'f0{}',u'ignored',], - u'\\addtocontents':[u'{$p!}{$q!}',u'f0{}',u'ignored',], - u'\\backmatter':[u'',u'f0{}',u'ignored',], - u'\\binom':[u'{$1}{$2}',u'f2{(}f0{f1{$1}f1{$2}}f2{)}',u'span class="binom"',u'span class="binomstack"',u'span class="bigsymbol"',], - u'\\boxed':[u'{$1}',u'f0{$1}',u'span class="boxed"',], - u'\\cfrac':[u'[$p!]{$1}{$2}',u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u'span class="fullfraction"',u'span class="numerator align-$p"',u'span class="denominator"',u'span class="ignored"',], - u'\\color':[u'{$p!}{$1}',u'f0{$1}',u'span style="color: $p;"',], - u'\\colorbox':[u'{$p!}{$1}',u'f0{$1}',u'span class="colorbox" style="background: $p;"',], - u'\\dbinom':[u'{$1}{$2}',u'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})',u'span class="binomial"',u'span class="binomrow"',u'span class="binomcell"',], - u'\\dfrac':[u'{$1}{$2}',u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u'span class="fullfraction"',u'span class="numerator"',u'span class="denominator"',u'span class="ignored"',], - u'\\displaystyle':[u'{$1}',u'f0{$1}',u'span class="displaystyle"',], - u'\\fancyfoot':[u'[$p!]{$q!}',u'f0{}',u'ignored',], - u'\\fancyhead':[u'[$p!]{$q!}',u'f0{}',u'ignored',], - u'\\fbox':[u'{$1}',u'f0{$1}',u'span class="fbox"',], - u'\\fboxrule':[u'{$p!}',u'f0{}',u'ignored',], - u'\\fboxsep':[u'{$p!}',u'f0{}',u'ignored',], - u'\\fcolorbox':[u'{$p!}{$q!}{$1}',u'f0{$1}',u'span class="boxed" style="border-color: $p; background: $q;"',], - u'\\frac':[u'{$1}{$2}',u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u'span class="fraction"',u'span class="numerator"',u'span class="denominator"',u'span class="ignored"',], - u'\\framebox':[u'[$p!][$q!]{$1}',u'f0{$1}',u'span class="framebox align-$q" style="width: $p;"',], - u'\\frontmatter':[u'',u'f0{}',u'ignored',], - u'\\href':[u'[$o]{$u!}{$t!}',u'f0{$t}',u'a href="$u"',], - u'\\hspace':[u'{$p!}',u'f0{ }',u'span class="hspace" style="width: $p;"',], - u'\\leftroot':[u'{$p!}',u'f0{ }',u'span class="leftroot" style="width: $p;px"',], - u'\\mainmatter':[u'',u'f0{}',u'ignored',], - u'\\markboth':[u'{$p!}{$q!}',u'f0{}',u'ignored',], - u'\\markright':[u'{$p!}',u'f0{}',u'ignored',], - u'\\nicefrac':[u'{$1}{$2}',u'f0{f1{$1}⁄f2{$2}}',u'span class="fraction"',u'sup class="numerator"',u'sub class="denominator"',u'span class="ignored"',], - u'\\parbox':[u'[$p!]{$w!}{$1}',u'f0{1}',u'div class="Boxed" style="width: $w;"',], - u'\\raisebox':[u'{$p!}{$1}',u'f0{$1.font}',u'span class="raisebox" style="vertical-align: $p;"',], - u'\\renewenvironment':[u'{$1!}{$2!}{$3!}',u'',], - u'\\rule':[u'[$v!]{$w!}{$h!}',u'f0/',u'hr class="line" style="width: $w; height: $h;"',], - u'\\scriptscriptstyle':[u'{$1}',u'f0{$1}',u'span class="scriptscriptstyle"',], - u'\\scriptstyle':[u'{$1}',u'f0{$1}',u'span class="scriptstyle"',], - u'\\sqrt':[u'[$0]{$1}',u'f0{f1{$0}f2{√}f4{(}f3{$1}f4{)}}',u'span class="sqrt"',u'sup class="root"',u'span class="radical"',u'span class="root"',u'span class="ignored"',], - u'\\stackrel':[u'{$1}{$2}',u'f0{f1{$1}f2{$2}}',u'span class="stackrel"',u'span class="upstackrel"',u'span class="downstackrel"',], - u'\\tbinom':[u'{$1}{$2}',u'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})',u'span class="binomial"',u'span class="binomrow"',u'span class="binomcell"',], - u'\\textcolor':[u'{$p!}{$1}',u'f0{$1}',u'span style="color: $p;"',], - u'\\textstyle':[u'{$1}',u'f0{$1}',u'span class="textstyle"',], - u'\\thispagestyle':[u'{$p!}',u'f0{}',u'ignored',], - u'\\unit':[u'[$0]{$1}',u'$0f0{$1.font}',u'span class="unit"',], - u'\\unitfrac':[u'[$0]{$1}{$2}',u'$0f0{f1{$1.font}⁄f2{$2.font}}',u'span class="fraction"',u'sup class="unit"',u'sub class="unit"',], - u'\\uproot':[u'{$p!}',u'f0{ }',u'span class="uproot" style="width: $p;px"',], - u'\\url':[u'{$u!}',u'f0{$u}',u'a href="$u"',], - u'\\vspace':[u'{$p!}',u'f0{ }',u'span class="vspace" style="height: $p;"',], + u'\\addcontentsline': [u'{$p!}{$q!}{$r!}', u'f0{}', u'ignored',], + u'\\addtocontents': [u'{$p!}{$q!}', u'f0{}', u'ignored',], + u'\\backmatter': [u'', u'f0{}', u'ignored',], + u'\\binom': [u'{$1}{$2}', u'f2{(}f0{f1{$1}f1{$2}}f2{)}', u'span class="binom"', u'span class="binomstack"', u'span class="bigdelimiter size2"',], + u'\\boxed': [u'{$1}', u'f0{$1}', u'span class="boxed"',], + u'\\cfrac': [u'[$p!]{$1}{$2}', u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', u'span class="fullfraction"', u'span class="numerator align-$p"', u'span class="denominator"', u'span class="ignored"',], + u'\\color': [u'{$p!}{$1}', u'f0{$1}', u'span style="color: $p;"',], + u'\\colorbox': [u'{$p!}{$1}', u'f0{$1}', u'span class="colorbox" style="background: $p;"',], + u'\\dbinom': [u'{$1}{$2}', u'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})', u'span class="binomial"', u'span class="binomrow"', u'span class="binomcell"',], + u'\\dfrac': [u'{$1}{$2}', u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', u'span class="fullfraction"', u'span class="numerator"', u'span class="denominator"', u'span class="ignored"',], + u'\\displaystyle': [u'{$1}', u'f0{$1}', u'span class="displaystyle"',], + u'\\fancyfoot': [u'[$p!]{$q!}', u'f0{}', u'ignored',], + u'\\fancyhead': [u'[$p!]{$q!}', u'f0{}', u'ignored',], + u'\\fbox': [u'{$1}', u'f0{$1}', u'span class="fbox"',], + u'\\fboxrule': [u'{$p!}', u'f0{}', u'ignored',], + u'\\fboxsep': [u'{$p!}', u'f0{}', u'ignored',], + u'\\fcolorbox': [u'{$p!}{$q!}{$1}', u'f0{$1}', u'span class="boxed" style="border-color: $p; background: $q;"',], + u'\\frac': [u'{$1}{$2}', u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', u'span class="fraction"', u'span class="numerator"', u'span class="denominator"', u'span class="ignored"',], + u'\\framebox': [u'[$p!][$q!]{$1}', u'f0{$1}', u'span class="framebox align-$q" style="width: $p;"',], + u'\\frontmatter': [u'', u'f0{}', u'ignored',], + u'\\href': [u'[$o]{$u!}{$t!}', u'f0{$t}', u'a href="$u"',], + u'\\hspace': [u'{$p!}', u'f0{ }', u'span class="hspace" style="width: $p;"',], + u'\\leftroot': [u'{$p!}', u'f0{ }', u'span class="leftroot" style="width: $p;px"',], + # TODO: convert 1 mu to 1/18 em + # u'\\mspace': [u'{$p!}', u'f0{ }', u'span class="hspace" style="width: $p;"',], + u'\\nicefrac': [u'{$1}{$2}', u'f0{f1{$1}⁄f2{$2}}', u'span class="fraction"', u'sup class="numerator"', u'sub class="denominator"', u'span class="ignored"',], + u'\\parbox': [u'[$p!]{$w!}{$1}', u'f0{1}', u'div class="Boxed" style="width: $w;"',], + u'\\raisebox': [u'{$p!}{$1}', u'f0{$1.font}', u'span class="raisebox" style="vertical-align: $p;"',], + u'\\renewenvironment': [u'{$1!}{$2!}{$3!}', u'',], + u'\\rule': [u'[$v!]{$w!}{$h!}', u'f0/', u'hr class="line" style="width: $w; height: $h;"',], + u'\\scriptscriptstyle': [u'{$1}', u'f0{$1}', u'span class="scriptscriptstyle"',], + u'\\scriptstyle': [u'{$1}', u'f0{$1}', u'span class="scriptstyle"',], + # TODO: increase √-size with argument (\frac in display mode, ...) + u'\\sqrt': [u'[$0]{$1}', u'f0{f1{$0}f2{√}f4{(}f3{$1}f4{)}}', u'span class="sqrt"', u'sup class="root"', u'span class="radical"', u'span class="root"', u'span class="ignored"',], + u'\\stackrel': [u'{$1}{$2}', u'f0{f1{$1}f2{$2}}', u'span class="stackrel"', u'span class="upstackrel"', u'span class="downstackrel"',], + u'\\tbinom': [u'{$1}{$2}', u'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})', u'span class="binomial"', u'span class="binomrow"', u'span class="binomcell"',], + u'\\tfrac': [u'{$1}{$2}', u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}', u'span class="textfraction"', u'span class="numerator"', u'span class="denominator"', u'span class="ignored"',], + u'\\textcolor': [u'{$p!}{$1}', u'f0{$1}', u'span style="color: $p;"',], + u'\\textstyle': [u'{$1}', u'f0{$1}', u'span class="textstyle"',], + u'\\thispagestyle': [u'{$p!}', u'f0{}', u'ignored',], + u'\\unit': [u'[$0]{$1}', u'$0f0{$1.font}', u'span class="unit"',], + u'\\unitfrac': [u'[$0]{$1}{$2}', u'$0f0{f1{$1.font}⁄f2{$2.font}}', u'span class="fraction"', u'sup class="unit"', u'sub class="unit"',], + u'\\uproot': [u'{$p!}', u'f0{ }', u'span class="uproot" style="width: $p;px"',], + u'\\url': [u'{$u!}', u'f0{$u}', u'a href="$u"',], + u'\\vspace': [u'{$p!}', u'f0{ }', u'span class="vspace" style="height: $p;"',], } hybridsizes = { - u'\\binom':u'$1+$2', u'\\cfrac':u'$1+$2', u'\\dbinom':u'$1+$2+1', - u'\\dfrac':u'$1+$2', u'\\frac':u'$1+$2', u'\\tbinom':u'$1+$2+1', + u'\\binom': u'$1+$2', u'\\cfrac': u'$1+$2', u'\\dbinom': u'$1+$2+1', + u'\\dfrac': u'$1+$2', u'\\frac': u'$1+$2', u'\\tbinom': u'$1+$2+1', } labelfunctions = { - u'\\label':u'a name="#"', + '\\label': u'a name="#"', } limitcommands = { - u'\\biginterleave':u'⫼', u'\\bigsqcap':u'⨅', u'\\fint':u'⨏', - u'\\iiiint':u'⨌', u'\\int':u'∫', u'\\intop':u'∫', u'\\lim':u'lim', - u'\\prod':u'∏', u'\\smallint':u'∫', u'\\sqint':u'⨖', u'\\sum':u'∑', - u'\\varointclockwise':u'∲', u'\\varprod':u'⨉', u'\\zcmp':u'⨟', - u'\\zhide':u'⧹', u'\\zpipe':u'⨠', u'\\zproject':u'⨡', - } - - misccommands = { - u'\\limits':u'LimitPreviousCommand', u'\\newcommand':u'MacroDefinition', - u'\\renewcommand':u'MacroDefinition', - u'\\setcounter':u'SetCounterFunction', u'\\tag':u'FormulaTag', - u'\\tag*':u'FormulaTag', u'\\today':u'TodayCommand', + '\\biginterleave': u'⫼', + '\\inf': u'inf', + '\\lim': u'lim', + '\\max': u'max', + '\\min': u'min', + '\\sup': u'sup', + '\\ointop': u'<span class="bigoperator integral">∮</span>', + '\\bigcap': u'<span class="bigoperator">⋂</span>', + '\\bigcup': u'<span class="bigoperator">⋃</span>', + '\\bigodot': u'<span class="bigoperator">⨀</span>', + '\\bigoplus': u'<span class="bigoperator">⨁</span>', + '\\bigotimes': u'<span class="bigoperator">⨂</span>', + '\\bigsqcap': u'<span class="bigoperator">⨅</span>', + '\\bigsqcup': u'<span class="bigoperator">⨆</span>', + '\\biguplus': u'<span class="bigoperator">⨄</span>', + '\\bigvee': u'<span class="bigoperator">⋁</span>', + '\\bigwedge': u'<span class="bigoperator">⋀</span>', + '\\coprod': u'<span class="bigoperator">∐</span>', + '\\intop': u'<span class="bigoperator integral">∫</span>', + '\\prod': u'<span class="bigoperator">∏</span>', + '\\sum': u'<span class="bigoperator">∑</span>', + '\\varprod': u'<span class="bigoperator">⨉</span>', + '\\zcmp': u'⨟', '\\zhide': u'⧹', '\\zpipe': u'⨠', '\\zproject': u'⨡', + # integrals have limits in index position with LaTeX default settings + # TODO: move to commands? + '\\int': u'<span class="bigoperator integral">∫</span>', + '\\iint': u'<span class="bigoperator integral">∬</span>', + '\\iiint': u'<span class="bigoperator integral">∭</span>', + '\\iiiint': u'<span class="bigoperator integral">⨌</span>', + '\\fint': u'<span class="bigoperator integral">⨏</span>', + '\\idotsint': u'<span class="bigoperator integral">∫⋯∫</span>', + '\\oint': u'<span class="bigoperator integral">∮</span>', + '\\oiint': u'<span class="bigoperator integral">∯</span>', + '\\oiiint': u'<span class="bigoperator integral">∰</span>', + '\\ointclockwise': u'<span class="bigoperator integral">∲</span>', + '\\ointctrclockwise': u'<span class="bigoperator integral">∳</span>', + '\\smallint': u'<span class="smallsymbol integral">∫</span>', + '\\sqint': u'<span class="bigoperator integral">⨖</span>', + '\\varointclockwise': u'<span class="bigoperator integral">∲</span>', } modified = { - u'\n':u'', u' ':u'', u'$':u'', u'&':u' ', u'\'':u'’', u'+':u' + ', - u',':u', ', u'-':u' − ', u'/':u' ⁄ ', u':':u' : ', u'<':u' < ', - u'=':u' = ', u'>':u' > ', u'@':u'', u'~':u'', + u'\n': u'', u' ': u'', u'$': u'', u'&': u' ', u'\'': u'’', u'+': u'\u2009+\u2009', + u',': u',\u2009', u'-': u'\u2009−\u2009', u'/': u'\u2009⁄\u2009', u':': u' : ', u'<': u'\u2009<\u2009', + u'=': u'\u2009=\u2009', u'>': u'\u2009>\u2009', u'@': u'', u'~': u'\u00a0', } onefunctions = { - u'\\Big':u'span class="bigsymbol"', u'\\Bigg':u'span class="hugesymbol"', - u'\\bar':u'span class="bar"', u'\\begin{array}':u'span class="arraydef"', - u'\\big':u'span class="symbol"', u'\\bigg':u'span class="largesymbol"', - u'\\bigl':u'span class="bigsymbol"', u'\\bigr':u'span class="bigsymbol"', - u'\\centering':u'span class="align-center"', - u'\\ensuremath':u'span class="ensuremath"', - u'\\hphantom':u'span class="phantom"', - u'\\noindent':u'span class="noindent"', - u'\\overbrace':u'span class="overbrace"', - u'\\overline':u'span class="overline"', - u'\\phantom':u'span class="phantom"', - u'\\underbrace':u'span class="underbrace"', u'\\underline':u'u', - u'\\vphantom':u'span class="phantom"', - } - + '\\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': 'u', + '\\vphantom': 'span class="phantom"', + } + + # relations (put additional space before and after the symbol) spacedcommands = { - u'\\Bot':u'⫫', u'\\Doteq':u'≑', u'\\DownArrowBar':u'⤓', - u'\\DownLeftTeeVector':u'⥞', u'\\DownLeftVectorBar':u'⥖', - u'\\DownRightTeeVector':u'⥟', u'\\DownRightVectorBar':u'⥗', - u'\\Equal':u'⩵', u'\\LeftArrowBar':u'⇤', u'\\LeftDownTeeVector':u'⥡', - u'\\LeftDownVectorBar':u'⥙', u'\\LeftTeeVector':u'⥚', - u'\\LeftTriangleBar':u'⧏', u'\\LeftUpTeeVector':u'⥠', - u'\\LeftUpVectorBar':u'⥘', u'\\LeftVectorBar':u'⥒', - u'\\Leftrightarrow':u'⇔', u'\\Longmapsfrom':u'⟽', u'\\Longmapsto':u'⟾', - u'\\MapsDown':u'↧', u'\\MapsUp':u'↥', u'\\Nearrow':u'⇗', - u'\\NestedGreaterGreater':u'⪢', u'\\NestedLessLess':u'⪡', - u'\\NotGreaterLess':u'≹', u'\\NotGreaterTilde':u'≵', - u'\\NotLessTilde':u'≴', u'\\Nwarrow':u'⇖', u'\\Proportion':u'∷', - u'\\RightArrowBar':u'⇥', u'\\RightDownTeeVector':u'⥝', - u'\\RightDownVectorBar':u'⥕', u'\\RightTeeVector':u'⥛', - u'\\RightTriangleBar':u'⧐', u'\\RightUpTeeVector':u'⥜', - u'\\RightUpVectorBar':u'⥔', u'\\RightVectorBar':u'⥓', - u'\\Rightarrow':u'⇒', u'\\Same':u'⩶', u'\\Searrow':u'⇘', - u'\\Swarrow':u'⇙', u'\\Top':u'⫪', u'\\UpArrowBar':u'⤒', u'\\VDash':u'⊫', - u'\\approx':u'≈', u'\\approxeq':u'≊', u'\\backsim':u'∽', u'\\barin':u'⋶', - u'\\barleftharpoon':u'⥫', u'\\barrightharpoon':u'⥭', u'\\bij':u'⤖', - u'\\coloneq':u'≔', u'\\corresponds':u'≙', u'\\curlyeqprec':u'⋞', - u'\\curlyeqsucc':u'⋟', u'\\dashrightarrow':u'⇢', u'\\dlsh':u'↲', - u'\\downdownharpoons':u'⥥', u'\\downuparrows':u'⇵', - u'\\downupharpoons':u'⥯', u'\\drsh':u'↳', u'\\eqslantgtr':u'⪖', - u'\\eqslantless':u'⪕', u'\\equiv':u'≡', u'\\ffun':u'⇻', u'\\finj':u'⤕', - u'\\ge':u'≥', u'\\geq':u'≥', u'\\ggcurly':u'⪼', u'\\gnapprox':u'⪊', - u'\\gneq':u'⪈', u'\\gtrapprox':u'⪆', u'\\hash':u'⋕', u'\\iddots':u'⋰', - u'\\implies':u' ⇒ ', u'\\in':u'∈', u'\\le':u'≤', u'\\leftarrow':u'←', - u'\\leftarrowtriangle':u'⇽', u'\\leftbarharpoon':u'⥪', - u'\\leftrightarrowtriangle':u'⇿', u'\\leftrightharpoon':u'⥊', - u'\\leftrightharpoondown':u'⥐', u'\\leftrightharpoonup':u'⥎', - u'\\leftrightsquigarrow':u'↭', u'\\leftslice':u'⪦', - u'\\leftsquigarrow':u'⇜', u'\\leftupdownharpoon':u'⥑', u'\\leq':u'≤', - u'\\lessapprox':u'⪅', u'\\llcurly':u'⪻', u'\\lnapprox':u'⪉', - u'\\lneq':u'⪇', u'\\longmapsfrom':u'⟻', u'\\multimapboth':u'⧟', - u'\\multimapdotbothA':u'⊶', u'\\multimapdotbothB':u'⊷', - u'\\multimapinv':u'⟜', u'\\nVdash':u'⊮', u'\\ne':u'≠', u'\\neq':u'≠', - u'\\ngeq':u'≱', u'\\nleq':u'≰', u'\\nni':u'∌', u'\\not\\in':u'∉', - u'\\notasymp':u'≭', u'\\npreceq':u'⋠', u'\\nsqsubseteq':u'⋢', - u'\\nsqsupseteq':u'⋣', u'\\nsubset':u'⊄', u'\\nsucceq':u'⋡', - u'\\pfun':u'⇸', u'\\pinj':u'⤔', u'\\precapprox':u'⪷', u'\\preceqq':u'⪳', - u'\\precnapprox':u'⪹', u'\\precnsim':u'⋨', u'\\propto':u'∝', - u'\\psur':u'⤀', u'\\rightarrow':u'→', u'\\rightarrowtriangle':u'⇾', - u'\\rightbarharpoon':u'⥬', u'\\rightleftharpoon':u'⥋', - u'\\rightslice':u'⪧', u'\\rightsquigarrow':u'⇝', - u'\\rightupdownharpoon':u'⥏', u'\\sim':u'~', u'\\strictfi':u'⥼', - u'\\strictif':u'⥽', u'\\subset':u'⊂', u'\\subseteq':u'⊆', - u'\\subsetneq':u'⊊', u'\\succapprox':u'⪸', u'\\succeqq':u'⪴', - u'\\succnapprox':u'⪺', u'\\supset':u'⊃', u'\\supseteq':u'⊇', - u'\\supsetneq':u'⊋', u'\\times':u'×', u'\\to':u'→', - u'\\updownarrows':u'⇅', u'\\updownharpoons':u'⥮', u'\\upupharpoons':u'⥣', - u'\\vartriangleleft':u'⊲', u'\\vartriangleright':u'⊳', - } - + # negated symbols without pre-composed Unicode character + '\\nleqq': u'\u2266\u0338', # ≦̸ + '\\ngeqq': u'\u2267\u0338', # ≧̸ + '\\nleqslant': u'\u2a7d\u0338', # ⩽̸ + '\\ngeqslant': u'\u2a7e\u0338', # ⩾̸ + '\\nsubseteqq': u'\u2AC5\u0338', # ⫅̸ + '\\nsupseteqq': u'\u2AC6\u0338', # ⫆̸ + '\\nsqsubset': u'\u2276\u228F', # ⊏̸ + # modified glyphs + '\\shortmid': u'<span class="smallsymbol">∣</span>', + '\\shortparallel': u'<span class="smallsymbol">∥</span>', + '\\nshortmid': u'<span class="smallsymbol">∤</span>', + '\\nshortparallel': u'<span class="smallsymbol">∦</span>', + '\\smallfrown': u'<span class="smallsymbol">⌢</span>', + '\\smallsmile': u'<span class="smallsymbol">⌣</span>', + '\\thickapprox': u'<span class="boldsymbol">≈</span>', + '\\thicksim': u'<span class="boldsymbol">∼</span>', + '\\varpropto': u'<span class="mathsf">\u221d</span>', # ∝ PROPORTIONAL TO + } + for key, value in tex2unichar.mathrel.items(): + spacedcommands['\\'+key] = value starts = { - u'beginafter':u'}', u'beginbefore':u'\\begin{', u'bracket':u'{', - u'command':u'\\', u'comment':u'%', u'complex':u'\\[', u'simple':u'$', - u'squarebracket':u'[', u'unnumbered':u'*', + u'beginafter': u'}', u'beginbefore': u'\\begin{', u'bracket': u'{', + u'command': u'\\', u'comment': u'%', u'complex': u'\\[', u'simple': u'$', + u'squarebracket': u'[', u'unnumbered': u'*', } symbolfunctions = { - u'^':u'sup', u'_':u'sub', + u'^': u'sup', u'_': u'sub', } textfunctions = { - u'\\mbox':u'span class="mbox"', u'\\text':u'span class="text"', - u'\\textbf':u'b', u'\\textipa':u'span class="textipa"', u'\\textit':u'i', - u'\\textnormal':u'span class="textnormal"', - u'\\textrm':u'span class="textrm"', - u'\\textsc':u'span class="versalitas"', - u'\\textsf':u'span class="textsf"', u'\\textsl':u'i', u'\\texttt':u'tt', - u'\\textup':u'span class="normal"', + u'\\mbox': u'span class="mbox"', + u'\\text': u'span class="text"', + u'\\textbf': u'span class="textbf"', + u'\\textit': u'span class="textit"', + u'\\textnormal': u'span class="textnormal"', + u'\\textrm': u'span class="textrm"', + u'\\textsc': u'span class="textsc"', + u'\\textsf': u'span class="textsf"', + u'\\textsl': u'span class="textsl"', + u'\\texttt': u'span class="texttt"', + u'\\textup': u'span class="normal"', } unmodified = { - - u'characters':[u'.',u'*',u'€',u'(',u')',u'[',u']',u'·',u'!',u';',u'|',u'§',u'"',], - } - - urls = { - u'googlecharts':u'http://chart.googleapis.com/chart?cht=tx&chl=', - } - -class GeneralConfig(object): - "Configuration class from elyxer.config file" - - version = { - u'date':u'2015-02-26', u'lyxformat':u'413', u'number':u'1.2.5', - } - -class HeaderConfig(object): - "Configuration class from elyxer.config file" - - parameters = { - u'beginpreamble':u'\\begin_preamble', u'branch':u'\\branch', - u'documentclass':u'\\textclass', u'endbranch':u'\\end_branch', - u'endpreamble':u'\\end_preamble', u'language':u'\\language', - u'lstset':u'\\lstset', u'outputchanges':u'\\output_changes', - u'paragraphseparation':u'\\paragraph_separation', - u'pdftitle':u'\\pdf_title', u'secnumdepth':u'\\secnumdepth', - u'tocdepth':u'\\tocdepth', - } - - styles = { - - u'article':[u'article',u'aastex',u'aapaper',u'acmsiggraph',u'sigplanconf',u'achemso',u'amsart',u'apa',u'arab-article',u'armenian-article',u'article-beamer',u'chess',u'dtk',u'elsarticle',u'heb-article',u'IEEEtran',u'iopart',u'kluwer',u'scrarticle-beamer',u'scrartcl',u'extarticle',u'paper',u'mwart',u'revtex4',u'spie',u'svglobal3',u'ltugboat',u'agu-dtd',u'jgrga',u'agums',u'entcs',u'egs',u'ijmpc',u'ijmpd',u'singlecol-new',u'doublecol-new',u'isprs',u'tarticle',u'jsarticle',u'jarticle',u [...] - u'book':[u'book',u'amsbook',u'scrbook',u'extbook',u'tufte-book',u'report',u'extreport',u'scrreprt',u'memoir',u'tbook',u'jsbook',u'jbook',u'mwbk',u'svmono',u'svmult',u'treport',u'jreport',u'mwrep',], - } - -class ImageConfig(object): - "Configuration class from elyxer.config file" - - converters = { - - u'imagemagick':u'convert[ -density $scale][ -define $format:use-cropbox=true] "$input" "$output"', - u'inkscape':u'inkscape "$input" --export-png="$output"', - u'lyx':u'lyx -C "$input" "$output"', - } - - cropboxformats = { - u'.eps':u'ps', u'.pdf':u'pdf', u'.ps':u'ps', - } - - formats = { - u'default':u'.png', u'vector':[u'.svg',u'.eps',], - } - -class LayoutConfig(object): - "Configuration class from elyxer.config file" - - groupable = { - - u'allowed':[u'StringContainer',u'Constant',u'TaggedText',u'Align',u'TextFamily',u'EmphaticText',u'VersalitasText',u'BarredText',u'SizeText',u'ColorText',u'LangLine',u'Formula',], - } - -class NewfangleConfig(object): - "Configuration class from elyxer.config file" - - constants = { - u'chunkref':u'chunkref{', u'endcommand':u'}', u'endmark':u'>', - u'startcommand':u'\\', u'startmark':u'=<', - } - -class NumberingConfig(object): - "Configuration class from elyxer.config file" - - layouts = { - - u'ordered':[u'Chapter',u'Section',u'Subsection',u'Subsubsection',u'Paragraph',], - u'roman':[u'Part',u'Book',], - } - - sequence = { - u'symbols':[u'*',u'**',u'†',u'‡',u'§',u'§§',u'¶',u'¶¶',u'#',u'##',], - } - -class StyleConfig(object): - "Configuration class from elyxer.config file" - - hspaces = { - u'\\enskip{}':u' ', u'\\hfill{}':u'<span class="hfill"> </span>', - u'\\hspace*{\\fill}':u' ', u'\\hspace*{}':u'', u'\\hspace{}':u' ', - u'\\negthinspace{}':u'', u'\\qquad{}':u' ', u'\\quad{}':u' ', - u'\\space{}':u' ', u'\\thinspace{}':u' ', u'~':u' ', - } - - quotes = { - u'ald':u'»', u'als':u'›', u'ard':u'«', u'ars':u'‹', u'eld':u'“', - u'els':u'‘', u'erd':u'”', u'ers':u'’', u'fld':u'«', - u'fls':u'‹', u'frd':u'»', u'frs':u'›', u'gld':u'„', u'gls':u'‚', - u'grd':u'“', u'grs':u'‘', u'pld':u'„', u'pls':u'‚', u'prd':u'”', - u'prs':u'’', u'sld':u'”', u'srd':u'”', - } - - referenceformats = { - u'eqref':u'(@↕)', u'formatted':u'¶↕', u'nameref':u'$↕', u'pageref':u'#↕', - u'ref':u'@↕', u'vpageref':u'on-page#↕', u'vref':u'@on-page#↕', - } - - size = { - u'ignoredtexts':[u'col',u'text',u'line',u'page',u'theight',u'pheight',], - } - - vspaces = { - u'bigskip':u'<div class="bigskip"> </div>', - u'defskip':u'<div class="defskip"> </div>', - u'medskip':u'<div class="medskip"> </div>', - u'smallskip':u'<div class="smallskip"> </div>', - u'vfill':u'<div class="vfill"> </div>', - } - -class TOCConfig(object): - "Configuration class from elyxer.config file" - - extractplain = { - - u'allowed':[u'StringContainer',u'Constant',u'TaggedText',u'Align',u'TextFamily',u'EmphaticText',u'VersalitasText',u'BarredText',u'SizeText',u'ColorText',u'LangLine',u'Formula',], - u'cloned':[u'',], u'extracted':[u'',], - } - - extracttitle = { - u'allowed':[u'StringContainer',u'Constant',u'Space',], - u'cloned':[u'TextFamily',u'EmphaticText',u'VersalitasText',u'BarredText',u'SizeText',u'ColorText',u'LangLine',u'Formula',], - u'extracted':[u'PlainLayout',u'TaggedText',u'Align',u'Caption',u'StandardLayout',u'FlexInset',], - } - -class TagConfig(object): - "Configuration class from elyxer.config file" - - barred = { - u'under':u'u', + u'characters': [u'.', u'*', u'€', u'(', u')', u'[', u']', + u'·', u'!', u';', u'|', u'§', u'"', u'?'], } - family = { - u'sans':u'span class="sans"', u'typewriter':u'tt', - } - - flex = { - u'CharStyle:Code':u'span class="code"', - u'CharStyle:MenuItem':u'span class="menuitem"', - u'Code':u'span class="code"', u'MenuItem':u'span class="menuitem"', - u'Noun':u'span class="noun"', u'Strong':u'span class="strong"', - } - - group = { - u'layouts':[u'Quotation',u'Quote',], - } - - layouts = { - u'Center':u'div', u'Chapter':u'h?', u'Date':u'h2', u'Paragraph':u'div', - u'Part':u'h1', u'Quotation':u'blockquote', u'Quote':u'blockquote', - u'Section':u'h?', u'Subsection':u'h?', u'Subsubsection':u'h?', - } - - listitems = { - u'Enumerate':u'ol', u'Itemize':u'ul', - } - - notes = { - u'Comment':u'', u'Greyedout':u'span class="greyedout"', u'Note':u'', - } - - script = { - u'subscript':u'sub', u'superscript':u'sup', - } - - shaped = { - u'italic':u'i', u'slanted':u'i', u'smallcaps':u'span class="versalitas"', - } - -class TranslationConfig(object): - "Configuration class from elyxer.config file" - - constants = { - u'Appendix':u'Appendix', u'Book':u'Book', u'Chapter':u'Chapter', - u'Paragraph':u'Paragraph', u'Part':u'Part', u'Section':u'Section', - u'Subsection':u'Subsection', u'Subsubsection':u'Subsubsection', - u'abstract':u'Abstract', u'bibliography':u'Bibliography', - u'figure':u'figure', u'float-algorithm':u'Algorithm ', - u'float-figure':u'Figure ', u'float-listing':u'Listing ', - u'float-table':u'Table ', u'float-tableau':u'Tableau ', - u'footnotes':u'Footnotes', u'generated-by':u'Document generated by ', - u'generated-on':u' on ', u'index':u'Index', - u'jsmath-enable':u'Please enable JavaScript on your browser.', - u'jsmath-requires':u' requires JavaScript to correctly process the mathematics on this page. ', - u'jsmath-warning':u'Warning: ', u'list-algorithm':u'List of Algorithms', - u'list-figure':u'List of Figures', u'list-table':u'List of Tables', - u'list-tableau':u'List of Tableaux', u'main-page':u'Main page', - u'next':u'Next', u'nomenclature':u'Nomenclature', - u'on-page':u' on page ', u'prev':u'Prev', u'references':u'References', - u'toc':u'Table of Contents', u'toc-for':u'Contents for ', u'up':u'Up', - } - - languages = { - u'american':u'en', u'british':u'en', u'deutsch':u'de', u'dutch':u'nl', - u'english':u'en', u'french':u'fr', u'ngerman':u'de', u'russian':u'ru', - u'spanish':u'es', - } - - - - - class CommandLineParser(object): "A parser for runtime options" @@ -1058,62 +624,17 @@ class CommandLineParser(object): return key - class Options(object): "A set of runtime options" - instance = None - location = None - nocopy = False - copyright = False + debug = False quiet = False version = False - hardversion = False - versiondate = False - html = False help = False - showlines = True - unicode = False - iso885915 = False - css = [] - favicon = '' - title = None - directory = None - destdirectory = None - toc = False - toctarget = '' - tocfor = None - forceformat = None - lyxformat = False - target = None - splitpart = None - memory = True - lowmem = False - nobib = False - converter = 'imagemagick' - raw = False - jsmath = None - mathjax = None - nofooter = False simplemath = False - template = None - noconvert = False - notoclabels = False - letterfoot = True - numberfoot = False - symbolfoot = False - hoverfoot = True - marginfoot = False - endfoot = False - supfoot = True - alignfoot = False - footnotes = None - imageformat = None - copyimages = False - googlecharts = False - embedcss = [] + showlines = True branches = dict() @@ -1134,41 +655,6 @@ class Options(object): self.usage() if Options.version: self.showversion() - if Options.hardversion: - self.showhardversion() - if Options.versiondate: - self.showversiondate() - if Options.lyxformat: - self.showlyxformat() - if Options.splitpart: - try: - Options.splitpart = int(Options.splitpart) - if Options.splitpart <= 0: - Trace.error('--splitpart requires a number bigger than zero') - self.usage() - except: - Trace.error('--splitpart needs a numeric argument, not ' + Options.splitpart) - self.usage() - if Options.lowmem or Options.toc or Options.tocfor: - Options.memory = False - self.parsefootnotes() - if Options.forceformat and not Options.imageformat: - Options.imageformat = Options.forceformat - if Options.imageformat == 'copy': - Options.copyimages = True - if Options.css == []: - Options.css = ['http://elyxer.nongnu.org/lyx.css'] - if Options.favicon == '': - pass # no default favicon - if Options.html: - Options.simplemath = True - if Options.toc and not Options.tocfor: - Trace.error('Option --toc is deprecated; use --tocfor "page" instead') - Options.tocfor = Options.toctarget - if Options.nocopy: - Trace.error('Option --nocopy is deprecated; it is no longer needed') - if Options.jsmath: - Trace.error('Option --jsmath is deprecated; use --mathjax instead') # set in Trace if necessary for param in dir(Trace): if param.endswith('mode'): @@ -1176,145 +662,25 @@ class Options(object): def usage(self): "Show correct usage" - Trace.error('Usage: ' + os.path.basename(Options.location) + ' [options] [filein] [fileout]') - Trace.error('Convert LyX input file "filein" to HTML file "fileout".') - Trace.error('If filein (or fileout) is not given use standard input (or output).') - Trace.error('Main program of the eLyXer package (http://elyxer.nongnu.org/).') + Trace.error('Usage: ' + os.path.basename(Options.location) + + ' [options] "input string"') + Trace.error('Convert input string with LaTeX math to MathML') self.showoptions() - def parsefootnotes(self): - "Parse footnotes options." - if not Options.footnotes: - return - Options.marginfoot = False - Options.letterfoot = False - Options.hoverfoot = False - options = Options.footnotes.split(',') - for option in options: - footoption = option + 'foot' - if hasattr(Options, footoption): - setattr(Options, footoption, True) - else: - Trace.error('Unknown footnotes option: ' + option) - if not Options.endfoot and not Options.marginfoot and not Options.hoverfoot: - Options.hoverfoot = True - if not Options.numberfoot and not Options.symbolfoot: - Options.letterfoot = True - def showoptions(self): "Show all possible options" - Trace.error(' Common options:') Trace.error(' --help: show this online help') Trace.error(' --quiet: disables all runtime messages') - Trace.error('') - Trace.error(' Advanced options:') Trace.error(' --debug: enable debugging messages (for developers)') Trace.error(' --version: show version number and release date') - Trace.error(' --lyxformat: return the highest LyX version supported') - Trace.error(' Options for HTML output:') - Trace.error(' --title "title": set the generated page title') - Trace.error(' --css "file.css": use a custom CSS file') - Trace.error(' --embedcss "file.css": embed styles from a CSS file into the output') - Trace.error(' --favicon "icon.ico": insert the specified favicon in the header.') - Trace.error(' --html: output HTML 4.0 instead of the default XHTML') - Trace.error(' --unicode: full Unicode output') - Trace.error(' --iso885915: output a document with ISO-8859-15 encoding') - Trace.error(' --nofooter: remove the footer "generated by eLyXer"') Trace.error(' --simplemath: do not generate fancy math constructions') - Trace.error(' Options for image output:') - Trace.error(' --directory "img_dir": look for images in the specified directory') - Trace.error(' --destdirectory "dest": put converted images into this directory') - Trace.error(' --imageformat ".ext": image output format, or "copy" to copy images') - Trace.error(' --noconvert: do not convert images, use in original locations') - Trace.error(' --converter "inkscape": use an alternative program to convert images') - Trace.error(' Options for footnote display:') - Trace.error(' --numberfoot: mark footnotes with numbers instead of letters') - Trace.error(' --symbolfoot: mark footnotes with symbols (*, **...)') - Trace.error(' --hoverfoot: show footnotes as hovering text (default)') - Trace.error(' --marginfoot: show footnotes on the page margin') - Trace.error(' --endfoot: show footnotes at the end of the page') - Trace.error(' --supfoot: use superscript for footnote markers (default)') - Trace.error(' --alignfoot: use aligned text for footnote markers') - Trace.error(' --footnotes "options": specify several comma-separated footnotes options') - Trace.error(' Available options are: "number", "symbol", "hover", "margin", "end",') - Trace.error(' "sup", "align"') - Trace.error(' Advanced output options:') - Trace.error(' --splitpart "depth": split the resulting webpage at the given depth') - Trace.error(' --tocfor "page": generate a TOC that points to the given page') - Trace.error(' --target "frame": make all links point to the given frame') - Trace.error(' --notoclabels: omit the part labels in the TOC, such as Chapter') - Trace.error(' --lowmem: do the conversion on the fly (conserve memory)') - Trace.error(' --raw: generate HTML without header or footer.') - Trace.error(' --mathjax remote: use MathJax remotely to display equations') - Trace.error(' --mathjax "URL": use MathJax from the given URL to display equations') - Trace.error(' --googlecharts: use Google Charts to generate formula images') - Trace.error(' --template "file": use a template, put everything in <!--$content-->') - Trace.error(' --copyright: add a copyright notice at the bottom') - Trace.error(' Deprecated options:') - Trace.error(' --toc: (deprecated) create a table of contents') - Trace.error(' --toctarget "page": (deprecated) generate a TOC for the given page') - Trace.error(' --nocopy: (deprecated) maintained for backwards compatibility') - Trace.error(' --jsmath "URL": use jsMath from the given URL to display equations') sys.exit() def showversion(self): "Return the current eLyXer version string" - string = 'eLyXer version ' + GeneralConfig.version['number'] - string += ' (' + GeneralConfig.version['date'] + ')' - Trace.error(string) - sys.exit() - - def showhardversion(self): - "Return just the version string" - Trace.message(GeneralConfig.version['number']) - sys.exit() - - def showversiondate(self): - "Return just the version dte" - Trace.message(GeneralConfig.version['date']) - sys.exit() - - def showlyxformat(self): - "Return just the lyxformat parameter" - Trace.message(GeneralConfig.version['lyxformat']) + Trace.error('math2html '+__version__) sys.exit() -class BranchOptions(object): - "A set of options for a branch" - - def __init__(self, name): - self.name = name - self.options = {'color':'#ffffff'} - - def set(self, key, value): - "Set a branch option" - if not key.startswith(ContainerConfig.string['startcommand']): - Trace.error('Invalid branch option ' + key) - return - key = key.replace(ContainerConfig.string['startcommand'], '') - self.options[key] = value - - def isselected(self): - "Return if the branch is selected" - if not 'selected' in self.options: - return False - return self.options['selected'] == '1' - - def __unicode__(self): - "String representation" - return 'options for ' + self.name + ': ' + unicode(self.options) - - - - -import urllib - - - - - - - class Cloner(object): "An object used to clone other objects." @@ -1334,35 +700,35 @@ class Cloner(object): create = classmethod(create) class ContainerExtractor(object): - "A class to extract certain containers." + """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): - "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." self.allowed = config['allowed'] - self.cloned = config['cloned'] self.extracted = config['extracted'] def extract(self, container): - "Extract a group of selected containers from elyxer.a container." + "Extract a group of selected containers from a container." list = [] - locate = lambda c: c.__class__.__name__ in self.allowed + self.cloned + 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, clone cloned containers and add the clone." + "Add allowed containers." name = container.__class__.__name__ if name in self.allowed: list.append(container) - elif name in self.cloned: - list.append(self.safeclone(container)) else: Trace.error('Unknown container class ' + name) @@ -1374,10 +740,6 @@ class ContainerExtractor(object): return clone - - - - class Parser(object): "A generic parser" @@ -1394,10 +756,6 @@ class Parser(object): def parseparameter(self, reader): "Parse a parameter" - if reader.currentline().strip().startswith('<'): - key, value = self.parsexml(reader) - self.parameters[key] = value - return split = reader.currentline().strip().split(' ', 1) reader.nextline() if len(split) == 0: @@ -1412,31 +770,6 @@ class Parser(object): doublesplit = split[1].split('"') self.parameters[key] = doublesplit[1] - def parsexml(self, reader): - "Parse a parameter in xml form: <param attr1=value...>" - strip = reader.currentline().strip() - reader.nextline() - if not strip.endswith('>'): - Trace.error('XML parameter ' + strip + ' should be <...>') - split = strip[1:-1].split() - if len(split) == 0: - Trace.error('Empty XML parameter <>') - return None, None - key = split[0] - del split[0] - if len(split) == 0: - return key, dict() - attrs = dict() - for attr in split: - if not '=' in attr: - Trace.error('Erroneous attribute for ' + key + ': ' + attr) - attr += '="0"' - parts = attr.split('=') - attrkey = parts[0] - value = parts[1].split('"')[1] - attrs[attrkey] = value - return key, attrs - def parseending(self, reader, process): "Parse until the current ending is found" if not self.ending: @@ -1455,10 +788,14 @@ class Parser(object): "Return a description" return self.__class__.__name__ + ' (' + unicode(self.begin) + ')' + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class LoneCommand(Parser): "A parser for just one command line" - def parse(self,reader): + def parse(self, reader): "Read nothing" return [] @@ -1540,19 +877,6 @@ class StringParser(Parser): reader.nextline() return contents -class InsetParser(BoundedParser): - "Parses a LyX inset" - - def parse(self, reader): - "Parse inset parameters into a dictionary" - startcommand = ContainerConfig.string['startcommand'] - while reader.currentline() != '' and not reader.currentline().startswith(startcommand): - self.parseparameter(reader) - return BoundedParser.parse(self, reader) - - - - class ContainerOutput(object): @@ -1630,7 +954,7 @@ class TaggedOutput(ContentsOutput): def open(self, container): "Get opening line." - if not self.checktag(): + if not self.checktag(container): return '' open = '<' + self.tag + '>' if self.breaklines: @@ -1639,7 +963,7 @@ class TaggedOutput(ContentsOutput): def close(self, container): "Get closing line." - if not self.checktag(): + if not self.checktag(container): return '' close = '</' + self.tag.split()[0] + '>' if self.breaklines: @@ -1648,14 +972,14 @@ class TaggedOutput(ContentsOutput): def selfclosing(self, container): "Get self-closing line." - if not self.checktag(): + if not self.checktag(container): return '' selfclosing = '<' + self.tag + '/>' if self.breaklines: return selfclosing + '\n' return selfclosing - def checktag(self): + def checktag(self, container): "Check that the tag is valid." if not self.tag: Trace.error('No tag in ' + unicode(container)) @@ -1691,6 +1015,7 @@ class FilteredOutput(ContentsOutput): line = line.replace(original, replacement) return line + class StringOutput(ContainerOutput): "Returns a bare string as output" @@ -1699,123 +1024,6 @@ class StringOutput(ContainerOutput): return [container.string] - - - - - -import sys -import codecs - - -class LineReader(object): - "Reads a file line by line" - - def __init__(self, filename): - if isinstance(filename, file): - self.file = filename - else: - self.file = codecs.open(filename, 'rU', 'utf-8') - self.linenumber = 1 - self.lastline = None - self.current = None - self.mustread = True - self.depleted = False - try: - self.readline() - except UnicodeDecodeError: - # try compressed file - import gzip - self.file = gzip.open(filename, 'rb') - self.readline() - - def setstart(self, firstline): - "Set the first line to read." - for i in range(firstline): - self.file.readline() - self.linenumber = firstline - - def setend(self, lastline): - "Set the last line to read." - self.lastline = lastline - - def currentline(self): - "Get the current line" - if self.mustread: - self.readline() - return self.current - - def nextline(self): - "Go to next line" - if self.depleted: - Trace.fatal('Read beyond file end') - self.mustread = True - - def readline(self): - "Read a line from elyxer.file" - self.current = self.file.readline() - if not isinstance(self.file, codecs.StreamReaderWriter): - self.current = self.current.decode('utf-8') - if len(self.current) == 0: - self.depleted = True - self.current = self.current.rstrip('\n\r') - self.linenumber += 1 - self.mustread = False - Trace.prefix = 'Line ' + unicode(self.linenumber) + ': ' - if self.linenumber % 1000 == 0: - Trace.message('Parsing') - - def finished(self): - "Find out if the file is finished" - if self.lastline and self.linenumber == self.lastline: - return True - if self.mustread: - self.readline() - return self.depleted - - def close(self): - self.file.close() - -class LineWriter(object): - "Writes a file as a series of lists" - - file = False - - def __init__(self, filename): - if isinstance(filename, file): - self.file = filename - self.filename = None - else: - self.filename = filename - - def write(self, strings): - "Write a list of strings" - for string in strings: - if not isinstance(string, basestring): - Trace.error('Not a string: ' + unicode(string) + ' in ' + unicode(strings)) - return - self.writestring(string) - - def writestring(self, string): - "Write a string" - if not self.file: - self.file = codecs.open(self.filename, 'w', "utf-8") - if self.file == sys.stdout and sys.version_info < (3,0): - string = string.encode('utf-8') - self.file.write(string) - - def writeline(self, line): - "Write a line to file" - self.writestring(line + '\n') - - def close(self): - self.file.close() - - - - - - class Globable(object): """A bit of text which can be globbed (lumped together in bits). Methods current(), skipcurrent(), checkfor() and isout() have to be @@ -1997,6 +1205,10 @@ class EndingList(object): string = string[:-1] return string + ']' + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class PositionEnding(object): "An ending for a parsing position" @@ -2015,6 +1227,8 @@ class PositionEnding(object): string += ' (optional)' return string + if sys.version_info >= (3, 0): + __str__ = __unicode__ class Position(Globable): @@ -2057,11 +1271,14 @@ class Position(Globable): self.skip(current) return current - def next(self): + def __next__(self): "Advance the position and return the next character." self.skipcurrent() return self.current() + if sys.version_info < (3, 0): + next = __next__ + def checkskip(self, string): "Check for a string at the given position; if there, skip it" if not self.checkfor(string): @@ -2077,7 +1294,7 @@ class TextPosition(Position): "A parse position based on a raw text." def __init__(self, text): - "Create the position from elyxer.some text." + "Create the position from some text." Position.__init__(self) self.pos = 0 self.text = text @@ -2108,77 +1325,16 @@ class TextPosition(Position): return None return self.text[self.pos : self.pos + length] -class FilePosition(Position): - "A parse position based on an underlying file." - def __init__(self, filename): - "Create the position from a file." - Position.__init__(self) - self.reader = LineReader(filename) - self.pos = 0 - self.checkbytemark() - - def skip(self, string): - "Skip a string of characters." - length = len(string) - while self.pos + length > len(self.reader.currentline()): - length -= len(self.reader.currentline()) - self.pos + 1 - self.nextline() - self.pos += length - - def currentline(self): - "Get the current line of the underlying file." - return self.reader.currentline() - - def nextline(self): - "Go to the next line." - self.reader.nextline() - self.pos = 0 +class Container(object): + "A container for text and objects in a lyx file" - def linenumber(self): - "Return the line number of the file." - return self.reader.linenumber + 1 + partkey = None + parent = None + begin = None - def identifier(self): - "Return the current line and line number in the file." - before = self.reader.currentline()[:self.pos - 1] - after = self.reader.currentline()[self.pos:] - return 'line ' + unicode(self.getlinenumber()) + ': ' + before + '*' + after - - def isout(self): - "Find out if we are out of the text yet." - if self.pos > len(self.reader.currentline()): - if self.pos > len(self.reader.currentline()) + 1: - Trace.error('Out of the line ' + self.reader.currentline() + ': ' + unicode(self.pos)) - self.nextline() - return self.reader.finished() - - def current(self): - "Return the current character, assuming we are not out." - if self.pos == len(self.reader.currentline()): - return '\n' - if self.pos > len(self.reader.currentline()): - Trace.error('Out of the line ' + self.reader.currentline() + ': ' + unicode(self.pos)) - return '*' - return self.reader.currentline()[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.reader.currentline()): - return None - return self.reader.currentline()[self.pos : self.pos + length] - - - -class Container(object): - "A container for text and objects in a lyx file" - - partkey = None - parent = None - begin = None - - def __init__(self): - self.contents = list() + def __init__(self): + self.contents = list() def process(self): "Process contents" @@ -2190,27 +1346,12 @@ class Container(object): if isinstance(html, basestring): Trace.error('Raw string ' + html) html = [html] - return self.escapeall(html) - - def escapeall(self, lines): - "Escape all lines in an array according to the output options." - result = [] - for line in lines: - if Options.html: - line = self.escape(line, EscapeConfig.html) - if Options.iso885915: - line = self.escape(line, EscapeConfig.iso885915) - line = self.escapeentities(line) - elif not Options.unicode: - line = self.escape(line, EscapeConfig.nonunicode) - result.append(line) - return result + return html def escape(self, line, replacements = EscapeConfig.entities): - "Escape a line with replacements from elyxer.a map" - pieces = replacements.keys() + "Escape a line with replacements from a map" + pieces = sorted(replacements.keys()) # do them in order - pieces.sort() for piece in pieces: if piece in line: line = line.replace(piece, replacements[piece]) @@ -2224,7 +1365,7 @@ class Container(object): if ord(pos.current()) > 128: codepoint = hex(ord(pos.current())) if codepoint == '0xd835': - codepoint = hex(ord(pos.next()) + 0xf800) + codepoint = hex(ord(next(pos)) + 0xf800) result += '' + codepoint[1:] + ';' else: result += pos.current() @@ -2264,12 +1405,10 @@ class Container(object): process(container) def extracttext(self): - "Extract all text from elyxer.allowed containers." + "Extract all text from allowed containers." result = '' constants = ContainerExtractor(ContainerConfig.extracttext).extract(self) - for constant in constants: - result += constant.string - return result + return ''.join(constant.string for constant in constants) def group(self, index, group, isingroup): "Group some adjoining elements into a group" @@ -2324,6 +1463,10 @@ class Container(object): return self.__class__.__name__ return self.__class__.__name__ + '@' + unicode(self.begin) + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class BlackBox(Container): "A container that does not output anything" @@ -2332,17 +1475,6 @@ class BlackBox(Container): self.output = EmptyOutput() self.contents = [] -class LyXFormat(BlackBox): - "Read the lyxformat command" - - def process(self): - "Show warning if version < 276" - version = int(self.header[1]) - if version < 276: - Trace.error('Warning: unsupported old format version ' + str(version)) - if version > int(GeneralConfig.version['lyxformat']): - Trace.error('Warning: unsupported new format version ' + str(version)) - class StringContainer(Container): "A container for a single string" @@ -2354,13 +1486,13 @@ class StringContainer(Container): self.string = '' def process(self): - "Replace special chars from elyxer.the contents." + "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 elyxer.a 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: @@ -2374,15 +1506,12 @@ class StringContainer(Container): def changeline(self, line): line = self.escape(line, EscapeConfig.chars) - if not ContainerConfig.string['startcommand'] in line: - return line - line = self.escape(line, EscapeConfig.commands) return line def extracttext(self): "Return all text." return self.string - + def __unicode__(self): "Return a printable representation." result = 'StringContainer' @@ -2393,6 +1522,10 @@ class StringContainer(Container): ellipsis = '' return result + ' (' + self.string.strip()[:15] + ellipsis + ')' + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class Constant(StringContainer): "A constant string" @@ -2404,58 +1537,16 @@ class Constant(StringContainer): def __unicode__(self): return 'Constant: ' + self.string -class TaggedText(Container): - "Text inside a tag" - - output = None - - def __init__(self): - self.parser = TextParser(self) - self.output = TaggedOutput() - - def complete(self, contents, tag, breaklines=False): - "Complete the tagged text and return it" - self.contents = contents - self.output.tag = tag - self.output.breaklines = breaklines - return self - - def constant(self, text, tag, breaklines=False): - "Complete the tagged text with a constant" - constant = Constant(text) - return self.complete([constant], tag, breaklines) - - def __unicode__(self): - "Return a printable representation." - if not hasattr(self.output, 'tag'): - return 'Emtpy tagged text' - if not self.output.tag: - return 'Tagged <unknown tag>' - return 'Tagged <' + self.output.tag + '>' - - - - + if sys.version_info >= (3, 0): + __str__ = __unicode__ class DocumentParameters(object): "Global parameters for the document." - pdftitle = None - indentstandard = False - tocdepth = 10 - startinglevel = 0 - maxdepth = 10 - language = None - bibliography = None - outputchanges = False displaymode = False - - - - class FormulaParser(Parser): "Parses a formula" @@ -2482,7 +1573,7 @@ class FormulaParser(Parser): 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) @@ -2560,27 +1651,6 @@ class FormulaParser(Parser): reader.nextline() return formula -class MacroParser(FormulaParser): - "A parser for a formula macro." - - def parseheader(self, reader): - "See if the formula is inlined" - self.begin = reader.linenumber + 1 - return ['inline'] - - def parse(self, reader): - "Parse the formula until the end" - formula = self.parsemultiliner(reader, self.parent.start, self.ending) - reader.nextline() - return formula - - - - - - - - class FormulaBit(Container): "A bit of a formula" @@ -2626,6 +1696,10 @@ class FormulaBit(Container): "Get a string representation" return self.__class__.__name__ + ' read in ' + self.original + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class TaggedBit(FormulaBit): "A tagged string in a formula" @@ -2668,6 +1742,10 @@ class FormulaConstant(Constant): "Return a printable representation." return 'Formula constant: ' + self.string + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class RawText(FormulaBit): "A bit of text inside a formula" @@ -2751,6 +1829,10 @@ class WhiteSpace(FormulaBit): "Return a printable representation." return 'Whitespace: *' + self.original + '*' + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class Bracket(FormulaBit): "A {} bracket inside a formula" @@ -2799,7 +1881,7 @@ class Bracket(FormulaBit): def innertext(self, pos): "Parse some text inside the bracket, following textual rules." - specialchars = FormulaConfig.symbolfunctions.keys() + specialchars = list(FormulaConfig.symbolfunctions.keys()) specialchars.append(FormulaConfig.starts['command']) specialchars.append(FormulaConfig.starts['bracket']) specialchars.append(Comment.start) @@ -2834,7 +1916,6 @@ class SquareBracket(Bracket): return bracket - class MathsProcessor(object): "A processor for a maths construction inside the FormulaProcessor." @@ -2846,6 +1927,10 @@ class MathsProcessor(object): "Return a printable description." return 'Maths processor ' + self.__class__.__name__ + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class FormulaProcessor(object): "A processor specifically for formulas." @@ -2882,7 +1967,7 @@ class FormulaProcessor(object): if bit.type == 'alpha': self.italicize(bit, contents) elif bit.type == 'font' and last and last.type == 'number': - bit.contents.insert(0, FormulaConstant(u' ')) + bit.contents.insert(0, FormulaConstant(u'\u2009')) last = bit def traverse(self, bit): @@ -2900,8 +1985,6 @@ class FormulaProcessor(object): contents[index] = TaggedBit().complete([bit], 'i') - - class Formula(Container): "A LaTeX formula" @@ -2916,36 +1999,7 @@ class Formula(Container): else: DocumentParameters.displaymode = True self.output.settag('div class="formula"', True) - if Options.jsmath: - self.jsmath() - elif Options.mathjax: - self.mathjax() - elif Options.googlecharts: - self.googlecharts() - else: - self.classic() - - def jsmath(self): - "Make the contents for jsMath." - if self.header[0] != 'inline': - self.output = TaggedOutput().settag('div class="math"') - else: - self.output = TaggedOutput().settag('span class="math"') - self.contents = [Constant(self.parsed)] - - def mathjax(self): - "Make the contents for MathJax." - self.output.tag = 'span class="MathJax_Preview"' - tag = 'script type="math/tex' - if self.header[0] != 'inline': - tag += ';mode=display' - self.contents = [TaggedText().constant(self.parsed, tag + '"', True)] - - def googlecharts(self): - "Make the contents using Google Charts http://code.google.com/apis/chart/." - url = FormulaConfig.urls['googlecharts'] + urllib.quote_plus(self.parsed) - img = '<img class="chart" src="' + url + '" alt="' + self.parsed + '"/>' - self.contents = [Constant(img)] + self.classic() def classic(self): "Make the contents using classic output generation with XHTML and CSS." @@ -2982,1062 +2036,120 @@ class Formula(Container): 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 __unicode__(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(object): - "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 = dict() - - 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 not type 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 - - - - -import unicodedata - - - - - - - - - - - - -import gettext - - -class Translator(object): - "Reads the configuration file and tries to find a translation." - "Otherwise falls back to the messages in the config file." - - instance = None - - def translate(cls, key): - "Get the translated message for a key." - return cls.instance.getmessage(key) - - translate = classmethod(translate) - - def __init__(self): - self.translation = None - self.first = True - - def findtranslation(self): - "Find the translation for the document language." - self.langcodes = None - if not DocumentParameters.language: - Trace.error('No language in document') - return - if not DocumentParameters.language in TranslationConfig.languages: - Trace.error('Unknown language ' + DocumentParameters.language) - return - if TranslationConfig.languages[DocumentParameters.language] == 'en': - return - langcodes = [TranslationConfig.languages[DocumentParameters.language]] - try: - self.translation = gettext.translation('elyxer', None, langcodes) - except IOError: - Trace.error('No translation for ' + unicode(langcodes)) - - def getmessage(self, key): - "Get the translated message for the given key." - if self.first: - self.findtranslation() - self.first = False - message = self.getuntranslated(key) - if not self.translation: - return message - try: - message = self.translation.ugettext(message) - except IOError: - pass - return message - - def getuntranslated(self, key): - "Get the untranslated message." - if not key in TranslationConfig.constants: - Trace.error('Cannot translate ' + key) - return key - return TranslationConfig.constants[key] - -Translator.instance = Translator() - - - -class NumberCounter(object): - "A counter for numbers (by default)." - "The type can be changed to return letters, roman numbers..." - - name = None - value = None - mode = None - master = None - - letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' - symbols = NumberingConfig.sequence['symbols'] - romannumerals = [ - ('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 __init__(self, name): - "Give a name to the counter." - self.name = name - - def setmode(self, mode): - "Set the counter mode. Can be changed at runtime." - self.mode = mode - return self - - def init(self, value): - "Set an initial value." - self.value = value - - def gettext(self): - "Get the next value as a text string." - return unicode(self.value) - - def getletter(self): - "Get the next value as a letter." - return self.getsequence(self.letters) - - def getsymbol(self): - "Get the next value as a symbol." - return self.getsequence(self.symbols) - - def getsequence(self, sequence): - "Get the next value from elyxer.a sequence." - return sequence[(self.value - 1) % len(sequence)] - - def getroman(self): - "Get the next value as a roman number." - result = '' - number = self.value - for numeral, value in self.romannumerals: - if number >= value: - result += numeral * (number / value) - number = number % value - return result - - def getvalue(self): - "Get the current value as configured in the current mode." - if not self.mode or self.mode in ['text', '1']: - return self.gettext() - if self.mode == 'A': - return self.getletter() - if self.mode == 'a': - return self.getletter().lower() - if self.mode == 'I': - return self.getroman() - if self.mode == '*': - return self.getsymbol() - Trace.error('Unknown counter mode ' + self.mode) - return self.gettext() - - def getnext(self): - "Increase the current value and get the next value as configured." - if not self.value: - self.value = 0 - self.value += 1 - return self.getvalue() - - def reset(self): - "Reset the counter." - self.value = 0 - - def __unicode__(self): - "Return a printable representation." - result = 'Counter ' + self.name - if self.mode: - result += ' in mode ' + self.mode - return result - -class DependentCounter(NumberCounter): - "A counter which depends on another one (the master)." - - def setmaster(self, master): - "Set the master counter." - self.master = master - self.last = self.master.getvalue() - return self - - def getnext(self): - "Increase or, if the master counter has changed, restart." - if self.last != self.master.getvalue(): - self.reset() - value = NumberCounter.getnext(self) - self.last = self.master.getvalue() - return value - - def getvalue(self): - "Get the value of the combined counter: master.dependent." - return self.master.getvalue() + '.' + NumberCounter.getvalue(self) - -class NumberGenerator(object): - "A number generator for unique sequences and hierarchical structures. Used in:" - " * ordered part numbers: Chapter 3, Section 5.3." - " * unique part numbers: Footnote 15, Bibliography cite [15]." - " * chaptered part numbers: Figure 3.15, Equation (8.3)." - " * unique roman part numbers: Part I, Book IV." - - chaptered = None - generator = None - - romanlayouts = [x.lower() for x in NumberingConfig.layouts['roman']] - orderedlayouts = [x.lower() for x in NumberingConfig.layouts['ordered']] - - counters = dict() - appendix = None - - def deasterisk(self, type): - "Remove the possible asterisk in a layout type." - return type.replace('*', '') - - def isunique(self, type): - "Find out if the layout type corresponds to a unique part." - return self.isroman(type) - - def isroman(self, type): - "Find out if the layout type should have roman numeration." - return self.deasterisk(type).lower() in self.romanlayouts - - def isinordered(self, type): - "Find out if the layout type corresponds to an (un)ordered part." - return self.deasterisk(type).lower() in self.orderedlayouts - - def isnumbered(self, type): - "Find out if the type for a layout corresponds to a numbered layout." - if '*' in type: - return False - if self.isroman(type): - return True - if not self.isinordered(type): - return False - if self.getlevel(type) > DocumentParameters.maxdepth: - return False - return True - - def isunordered(self, type): - "Find out if the type contains an asterisk, basically." - return '*' in type - - def getlevel(self, type): - "Get the level that corresponds to a layout type." - if self.isunique(type): - return 0 - if not self.isinordered(type): - Trace.error('Unknown layout type ' + type) - return 0 - type = self.deasterisk(type).lower() - level = self.orderedlayouts.index(type) + 1 - return level - DocumentParameters.startinglevel - - def getparttype(self, type): - "Obtain the type for the part: without the asterisk, " - "and switched to Appendix if necessary." - if NumberGenerator.appendix and self.getlevel(type) == 1: - return 'Appendix' - return self.deasterisk(type) - - def generate(self, type): - "Generate a number for a layout type." - "Unique part types such as Part or Book generate roman numbers: Part I." - "Ordered part types return dot-separated tuples: Chapter 5, Subsection 2.3.5." - "Everything else generates unique numbers: Bibliography [1]." - "Each invocation results in a new number." - return self.getcounter(type).getnext() - - def getcounter(self, type): - "Get the counter for the given type." - type = type.lower() - if not type in self.counters: - self.counters[type] = self.create(type) - return self.counters[type] - - def create(self, type): - "Create a counter for the given type." - if self.isnumbered(type) and self.getlevel(type) > 1: - index = self.orderedlayouts.index(type) - above = self.orderedlayouts[index - 1] - master = self.getcounter(above) - return self.createdependent(type, master) - counter = NumberCounter(type) - if self.isroman(type): - counter.setmode('I') - return counter - - def getdependentcounter(self, type, master): - "Get (or create) a counter of the given type that depends on another." - if not type in self.counters or not self.counters[type].master: - self.counters[type] = self.createdependent(type, master) - return self.counters[type] - - def createdependent(self, type, master): - "Create a dependent counter given the master." - return DependentCounter(type).setmaster(master) - - def startappendix(self): - "Start appendices here." - firsttype = self.orderedlayouts[DocumentParameters.startinglevel] - counter = self.getcounter(firsttype) - counter.setmode('A').reset() - NumberGenerator.appendix = True - -class ChapteredGenerator(NumberGenerator): - "Generate chaptered numbers, as in Chapter.Number." - "Used in equations, figures: Equation (5.3), figure 8.15." - - def generate(self, type): - "Generate a number which goes with first-level numbers (chapters). " - "For the article classes a unique number is generated." - if DocumentParameters.startinglevel > 0: - return NumberGenerator.generator.generate(type) - chapter = self.getcounter('Chapter') - return self.getdependentcounter(type, chapter).getnext() - - -NumberGenerator.chaptered = ChapteredGenerator() -NumberGenerator.generator = NumberGenerator() - - - - - - -class ContainerSize(object): - "The size of a container." - - width = None - height = None - maxwidth = None - maxheight = None - scale = None - - def set(self, width = None, height = None): - "Set the proper size with width and height." - self.setvalue('width', width) - self.setvalue('height', height) - return self - - def setmax(self, maxwidth = None, maxheight = None): - "Set max width and/or height." - self.setvalue('maxwidth', maxwidth) - self.setvalue('maxheight', maxheight) - return self - - def readparameters(self, container): - "Read some size parameters off a container." - self.setparameter(container, 'width') - self.setparameter(container, 'height') - self.setparameter(container, 'scale') - self.checkvalidheight(container) - return self - - def setparameter(self, container, name): - "Read a size parameter off a container, and set it if present." - value = container.getparameter(name) - self.setvalue(name, value) - - def setvalue(self, name, value): - "Set the value of a parameter name, only if it's valid." - value = self.processparameter(value) - if value: - setattr(self, name, value) - - def checkvalidheight(self, container): - "Check if the height parameter is valid; otherwise erase it." - heightspecial = container.getparameter('height_special') - if self.height and self.extractnumber(self.height) == '1' and heightspecial == 'totalheight': - self.height = None - - def processparameter(self, value): - "Do the full processing on a parameter." - if not value: - return None - if self.extractnumber(value) == '0': - return None - for ignored in StyleConfig.size['ignoredtexts']: - if ignored in value: - value = value.replace(ignored, '') - return value - - def extractnumber(self, text): - "Extract the first number in the given text." - result = '' - decimal = False - for char in text: - if char.isdigit(): - result += char - elif char == '.' and not decimal: - result += char - decimal = True - else: - return result - return result - - def checkimage(self, width, height): - "Check image dimensions, set them if possible." - if width: - self.maxwidth = unicode(width) + 'px' - if self.scale and not self.width: - self.width = self.scalevalue(width) - if height: - self.maxheight = unicode(height) + 'px' - if self.scale and not self.height: - self.height = self.scalevalue(height) - if self.width and not self.height: - self.height = 'auto' - if self.height and not self.width: - self.width = 'auto' - - def scalevalue(self, value): - "Scale the value according to the image scale and return it as unicode." - scaled = value * int(self.scale) / 100 - return unicode(int(scaled)) + 'px' - - def removepercentwidth(self): - "Remove percent width if present, to set it at the figure level." - if not self.width: - return None - if not '%' in self.width: - return None - width = self.width - self.width = None - if self.height == 'auto': - self.height = None - return width - - def addstyle(self, container): - "Add the proper style attribute to the output tag." - if not isinstance(container.output, TaggedOutput): - Trace.error('No tag to add style, in ' + unicode(container)) - if not self.width and not self.height and not self.maxwidth and not self.maxheight: - # nothing to see here; move along - return - tag = ' style="' - tag += self.styleparameter('width') - tag += self.styleparameter('maxwidth') - tag += self.styleparameter('height') - tag += self.styleparameter('maxheight') - if tag[-1] == ' ': - tag = tag[:-1] - tag += '"' - container.output.tag += tag - - def styleparameter(self, name): - "Get the style for a single parameter." - value = getattr(self, name) - if value: - return name.replace('max', 'max-') + ': ' + value + '; ' - return '' - - - -class QuoteContainer(Container): - "A container for a pretty quote" - - def __init__(self): - self.parser = BoundedParser() - self.output = FixedOutput() - - def process(self): - "Process contents" - self.type = self.header[2] - if not self.type in StyleConfig.quotes: - Trace.error('Quote type ' + self.type + ' not found') - self.html = ['"'] - return - self.html = [StyleConfig.quotes[self.type]] - -class LyXLine(Container): - "A Lyx line" - - def __init__(self): - self.parser = LoneCommand() - self.output = FixedOutput() - - def process(self): - self.html = ['<hr class="line" />'] - -class EmphaticText(TaggedText): - "Text with emphatic mode" - - def process(self): - self.output.tag = 'i' - -class ShapedText(TaggedText): - "Text shaped (italic, slanted)" - - def process(self): - self.type = self.header[1] - if not self.type in TagConfig.shaped: - Trace.error('Unrecognized shape ' + self.header[1]) - self.output.tag = 'span' - return - self.output.tag = TagConfig.shaped[self.type] - -class VersalitasText(TaggedText): - "Text in versalitas" - - def process(self): - self.output.tag = 'span class="versalitas"' - -class ColorText(TaggedText): - "Colored text" - - def process(self): - self.color = self.header[1] - self.output.tag = 'span class="' + self.color + '"' - -class SizeText(TaggedText): - "Sized text" - - def process(self): - self.size = self.header[1] - self.output.tag = 'span class="' + self.size + '"' - -class BoldText(TaggedText): - "Bold text" - - def process(self): - self.output.tag = 'b' - -class TextFamily(TaggedText): - "A bit of text from elyxer.a different family" - - def process(self): - "Parse the type of family" - self.type = self.header[1] - if not self.type in TagConfig.family: - Trace.error('Unrecognized family ' + type) - self.output.tag = 'span' - return - self.output.tag = TagConfig.family[self.type] - -class Hfill(TaggedText): - "Horizontall fill" - - def process(self): - self.output.tag = 'span class="hfill"' - -class BarredText(TaggedText): - "Text with a bar somewhere" - - def process(self): - "Parse the type of bar" - self.type = self.header[1] - if not self.type in TagConfig.barred: - Trace.error('Unknown bar type ' + self.type) - self.output.tag = 'span' - return - self.output.tag = TagConfig.barred[self.type] - -class LangLine(TaggedText): - "A line with language information" - - def process(self): - "Only generate a span with lang info when the language is recognized." - lang = self.header[1] - if not lang in TranslationConfig.languages: - self.output = ContentsOutput() - return - isolang = TranslationConfig.languages[lang] - self.output = TaggedOutput().settag('span lang="' + isolang + '"', False) - -class InsetLength(BlackBox): - "A length measure inside an inset." - - def process(self): - self.length = self.header[1] - -class Space(Container): - "A space of several types" - - def __init__(self): - self.parser = InsetParser() - self.output = FixedOutput() - - def process(self): - self.type = self.header[2] - if self.type not in StyleConfig.hspaces: - Trace.error('Unknown space type ' + self.type) - self.html = [' '] - return - self.html = [StyleConfig.hspaces[self.type]] - length = self.getlength() - if not length: - return - self.output = TaggedOutput().settag('span class="hspace"', False) - ContainerSize().set(length).addstyle(self) - - def getlength(self): - "Get the space length from elyxer.the contents or parameters." - if len(self.contents) == 0 or not isinstance(self.contents[0], InsetLength): - return None - return self.contents[0].length - -class VerticalSpace(Container): - "An inset that contains a vertical space." - - def __init__(self): - self.parser = InsetParser() - self.output = FixedOutput() - - def process(self): - "Set the correct tag" - self.type = self.header[2] - if self.type not in StyleConfig.vspaces: - self.output = TaggedOutput().settag('div class="vspace" style="height: ' + self.type + ';"', True) - return - self.html = [StyleConfig.vspaces[self.type]] - -class Align(Container): - "Bit of aligned text" - - def __init__(self): - self.parser = ExcludingParser() - self.output = TaggedOutput().setbreaklines(True) - - def process(self): - self.output.tag = 'div class="' + self.header[1] + '"' - -class Newline(Container): - "A newline" - - def __init__(self): - self.parser = LoneCommand() - self.output = FixedOutput() - - def process(self): - "Process contents" - self.html = ['<br/>\n'] - -class NewPage(Newline): - "A new page" - - def process(self): - "Process contents" - self.html = ['<p><br/>\n</p>\n'] - -class Separator(Container): - "A separator string which is not extracted by extracttext()." - - def __init__(self, constant): - self.output = FixedOutput() - self.contents = [] - self.html = [constant] - -class StrikeOut(TaggedText): - "Striken out text." - - def process(self): - "Set the output tag to strike." - self.output.tag = 'strike' - -class StartAppendix(BlackBox): - "Mark to start an appendix here." - "From this point on, all chapters become appendices." - - def process(self): - "Activate the special numbering scheme for appendices, using letters." - NumberGenerator.generator.startappendix() - - - - - - -class Link(Container): - "A link to another part of the document" - - anchor = None - url = None - type = None - page = None - target = None - destination = None - title = None - - def __init__(self): - "Initialize the link, add target if configured." - self.contents = [] - self.parser = InsetParser() - self.output = LinkOutput() - if Options.target: - self.target = Options.target - - def complete(self, text, anchor = None, url = None, type = None, title = None): - "Complete the link." - self.contents = [Constant(text)] - if anchor: - self.anchor = anchor - if url: - self.url = url - if type: - self.type = type - if title: - self.title = title - return self - - def computedestination(self): - "Use the destination link to fill in the destination URL." - if not self.destination: - return - self.url = '' - if self.destination.anchor: - self.url = '#' + self.destination.anchor - if self.destination.page: - self.url = self.destination.page + self.url - - def setmutualdestination(self, destination): - "Set another link as destination, and set its destination to this one." - self.destination = destination - destination.destination = self - - def __unicode__(self): - "Return a printable representation." - result = 'Link' - if self.anchor: - result += ' #' + self.anchor - if self.url: - result += ' to ' + self.url - return result - -class URL(Link): - "A clickable URL" - - def process(self): - "Read URL from elyxer.parameters" - target = self.escape(self.getparameter('target')) - self.url = target - type = self.getparameter('type') - if type: - self.url = self.escape(type) + target - name = self.getparameter('name') - if not name: - name = target - self.contents = [Constant(name)] - -class FlexURL(URL): - "A flexible URL" - - def process(self): - "Read URL from elyxer.contents" - self.url = self.extracttext() - -class LinkOutput(ContainerOutput): - "A link pointing to some destination" - "Or an anchor (destination)" - - def gethtml(self, link): - "Get the HTML code for the link" - type = link.__class__.__name__ - if link.type: - type = link.type - tag = 'a class="' + type + '"' - if link.anchor: - tag += ' name="' + link.anchor + '"' - if link.destination: - link.computedestination() - if link.url: - tag += ' href="' + link.url + '"' - if link.target: - tag += ' target="' + link.target + '"' - if link.title: - tag += ' title="' + link.title + '"' - return TaggedOutput().settag(tag).gethtml(link) - + "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) -class Postprocessor(object): - "Postprocess a container keeping some context" + def __unicode__(self): + "Return a printable representation." + if self.partkey and self.partkey.number: + return 'Formula (' + self.partkey.number + ')' + return 'Unnumbered formula' - stages = [] + if sys.version_info >= (3, 0): + __str__ = __unicode__ - def __init__(self): - self.stages = StageDict(Postprocessor.stages, self) - self.current = None - self.last = None - - def postprocess(self, next): - "Postprocess a container and its contents." - self.postrecursive(self.current) - result = self.postcurrent(next) - self.last = self.current - self.current = next - return result - def postrecursive(self, container): - "Postprocess the container contents recursively" - if not hasattr(container, 'contents'): - return - if len(container.contents) == 0: - return - if hasattr(container, 'postprocess'): - if not container.postprocess: - return - postprocessor = Postprocessor() - contents = [] - for element in container.contents: - post = postprocessor.postprocess(element) - if post: - contents.append(post) - # two rounds to empty the pipeline - for i in range(2): - post = postprocessor.postprocess(None) - if post: - contents.append(post) - container.contents = contents - - def postcurrent(self, next): - "Postprocess the current element taking into account next and last." - stage = self.stages.getstage(self.current) - if not stage: - return self.current - return stage.postprocess(self.last, self.current, next) - -class StageDict(object): - "A dictionary of stages corresponding to classes" - - def __init__(self, classes, postprocessor): - "Instantiate an element from elyxer.each class and store as a dictionary" - instances = self.instantiate(classes, postprocessor) - self.stagedict = dict([(x.processedclass, x) for x in instances]) - - def instantiate(self, classes, postprocessor): - "Instantiate an element from elyxer.each class" - stages = [x.__new__(x) for x in classes] - for element in stages: - element.__init__() - element.postprocessor = postprocessor - return stages - - def getstage(self, element): - "Get the stage for a given element, if the type is in the dict" - if not element.__class__ in self.stagedict: - return None - return self.stagedict[element.__class__] +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 Label(Link): - "A label to be referenced" +class FormulaFactory(object): + "Construct bits of formula" - names = dict() - lastlayout = None + # bit types will be appended later + types = [FormulaSymbol, RawText, FormulaNumber, Bracket, Comment, WhiteSpace] + skippedtypes = [Comment, WhiteSpace] + defining = False def __init__(self): - Link.__init__(self) - self.lastnumbered = None - - def process(self): - "Process a label container." - key = self.getparameter('name') - self.create(' ', key) - self.lastnumbered = Label.lastlayout - - def create(self, text, key, type = 'Label'): - "Create the label for a given key." - self.key = key - self.complete(text, anchor = key, type = type) - Label.names[key] = self - if key in Reference.references: - for reference in Reference.references[key]: - reference.destination = self - return self + "Initialize the map of instances." + self.instances = dict() - def findpartkey(self): - "Get the part key for the latest numbered container seen." - numbered = self.numbered(self) - if numbered and numbered.partkey: - return numbered.partkey - return '' + def detecttype(self, type, pos): + "Detect a bit of a given type." + if pos.finished(): + return False + return self.instance(type).detect(pos) - def numbered(self, container): - "Get the numbered container for the label." - if container.partkey: - return container - if not container.parent: - if self.lastnumbered: - return self.lastnumbered - return None - return self.numbered(container.parent) + def instance(self, type): + "Get an instance of the given type." + if not type in self.instances or not self.instances[type]: + self.instances[type] = self.create(type) + return self.instances[type] - def __unicode__(self): - "Return a printable representation." - if not hasattr(self, 'key'): - return 'Unnamed label' - return 'Label ' + self.key + def create(self, type): + "Create a new formula bit of the given type." + return Cloner.create(type).setfactory(self) -class Reference(Link): - "A reference to a label." + def clearskipped(self, pos): + "Clear any skipped types." + while not pos.finished(): + if not self.skipany(pos): + return + return - references = dict() - key = 'none' + 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 process(self): - "Read the reference and set the arrow." - self.key = self.getparameter('reference') - if self.key in Label.names: - self.direction = u'↑' - label = Label.names[self.key] - else: - self.direction = u'↓' - label = Label().complete(' ', self.key, 'preref') - self.destination = label - self.formatcontents() - if not self.key in Reference.references: - Reference.references[self.key] = [] - Reference.references[self.key].append(self) - - def formatcontents(self): - "Format the reference contents." - formatkey = self.getparameter('LatexCommand') - if not formatkey: - formatkey = 'ref' - self.formatted = u'↕' - if formatkey in StyleConfig.referenceformats: - self.formatted = StyleConfig.referenceformats[formatkey] - else: - Trace.error('Unknown reference format ' + formatkey) - self.replace(u'↕', self.direction) - self.replace('#', '1') - self.replace('on-page', Translator.translate('on-page')) - partkey = self.destination.findpartkey() - # only if partkey and partkey.number are not null, send partkey.number - self.replace('@', partkey and partkey.number) - self.replace(u'¶', partkey and partkey.tocentry) - if not '$' in self.formatted or not partkey or not partkey.titlecontents: - # there is a $ left, but it should go away on preprocessing - self.contents = [Constant(self.formatted)] - return - pieces = self.formatted.split('$') - self.contents = [Constant(pieces[0])] - for piece in pieces[1:]: - self.contents += partkey.titlecontents - self.contents.append(Constant(piece)) - - def replace(self, key, value): - "Replace a key in the format template with a value." - if not key in self.formatted: - return - if not value: - value = '' - self.formatted = self.formatted.replace(key, value) + 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 __unicode__(self): - "Return a printable representation." - return 'Reference ' + self.key + 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): @@ -4084,7 +2196,7 @@ class FormulaCommand(FormulaBit): return bit def extractcommand(self, pos): - "Extract the command from elyxer.the current position." + "Extract the command from the current position." if not pos.checkskip(FormulaCommand.start): pos.error('Missing command start ' + FormulaCommand.start) return @@ -4134,7 +2246,7 @@ class CommandBit(FormulaCommand): 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) @@ -4194,23 +2306,32 @@ class EmptyCommand(CommandBit): self.contents = [FormulaConstant(self.translated)] class SpacedCommand(CommandBit): - "An empty command which should have math spacing in formulas." + """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." - self.contents = [FormulaConstant(u' ' + self.translated + u' ')] + # pad with MEDIUM MATHEMATICAL SPACE (4/18 em): too wide in STIX fonts :( + # self.contents = [FormulaConstant(u'\u205f' + self.translated + u'\u205f')] + # pad with THIN SPACE (1/5 em) + self.contents = [FormulaConstant(u'\u2009' + self.translated + u'\u2009')] class AlphaCommand(EmptyCommand): - "A command without paramters whose result is alphabetical" + """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) - self.type = 'alpha' + 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" @@ -4279,7 +2400,8 @@ class LabelFunction(CommandBit): Label.names[self.key] = self.label class FontFunction(OneParamFunction): - "A function of one parameter that changes the font" + """A function of one parameter that changes the font.""" + # TODO: keep letters italic with \boldsymbol. commandmap = FormulaConfig.fontfunctions @@ -4290,47 +2412,11 @@ class FontFunction(OneParamFunction): FormulaFactory.types += [FormulaCommand, SymbolFunction] FormulaCommand.types = [ - AlphaCommand, EmptyCommand, OneParamFunction, FontFunction, LabelFunction, - TextFunction, SpacedCommand, - ] - - - - - - - - - - - - -class BigSymbol(object): - "A big symbol generator." + AlphaCommand, EmptyCommand, OneParamFunction, FontFunction, + LabelFunction, TextFunction, SpacedCommand] - symbols = FormulaConfig.bigsymbols - def __init__(self, symbol): - "Create the big symbol." - self.symbol = symbol - - def getpieces(self): - "Get an array with all pieces." - if not self.symbol in self.symbols: - return [self.symbol] - if self.smalllimit(): - return [self.symbol] - return self.symbols[self.symbol] - - def smalllimit(self): - "Decide if the limit should be a small, one-line symbol." - if not DocumentParameters.displaymode: - return True - if len(self.symbols[self.symbol]) == 1: - return True - return Options.simplemath - -class BigBracket(BigSymbol): +class BigBracket(object): "A big bracket generator." def __init__(self, size, bracket, alignment='l'): @@ -4389,11 +2475,7 @@ class BigBracket(BigSymbol): "Return the bracket as a single sign." if self.original == '.': return [TaggedBit().constant('', 'span class="emptydot"')] - return [TaggedBit().constant(self.original, 'span class="symbol"')] - - - - + return [TaggedBit().constant(self.original, 'span class="stretchy"')] class FormulaEquation(CommandBit): @@ -4550,12 +2632,17 @@ class EquationEnvironment(MultiRowFormula): def parsebit(self, pos): "Parse the whole environment." - self.output = TaggedOutput().settag('span class="environment"', False) 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) @@ -4589,51 +2676,71 @@ class BeginCommand(CommandBit): FormulaCommand.types += [BeginCommand] -import datetime - - class CombiningFunction(OneParamFunction): commandmap = FormulaConfig.combiningfunctions def parsebit(self, pos): "Parse a combining function." - self.type = 'alpha' combining = self.translated parameter = self.parsesingleparameter(pos) if not parameter: - Trace.error('Empty parameter for combining function ' + self.command) - elif len(parameter.extracttext()) != 1: - Trace.error('Applying combining function ' + self.command + ' to invalid string "' + parameter.extracttext() + '"') - self.contents.append(Constant(combining)) + 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(u'\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(): - Trace.error('Error while parsing single parameter at ' + pos.identifier()) return None - if self.factory.detecttype(Bracket, pos) \ - or self.factory.detecttype(FormulaCommand, pos): - return self.parseparameter(pos) - letter = FormulaConstant(pos.skipcurrent()) - self.add(letter) - return letter + return self.parseparameter(pos) -class DecoratingFunction(OneParamFunction): - "A function that decorates some bit of text" +class OversetFunction(OneParamFunction): + "A function that decorates some bit of text with an overset." - commandmap = FormulaConfig.decoratingfunctions + commandmap = FormulaConfig.oversetfunctions def parsebit(self, pos): - "Parse a decorating function" - self.type = 'alpha' + "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, 'span class="symbolover"') + self.symbol = TaggedBit().constant(symbol, 'sub') self.parameter = self.parseparameter(pos) - self.output = TaggedOutput().settag('span class="withsymbol"') + self.output = TaggedOutput().settag('span class="embellished"') self.contents.insert(0, self.symbol) - self.parameter.output = TaggedOutput().settag('span class="undersymbol"') + self.parameter.output = TaggedOutput().settag('span class="base"') self.simplifyifpossible() class LimitCommand(EmptyCommand): @@ -4643,10 +2750,9 @@ class LimitCommand(EmptyCommand): def parsebit(self, pos): "Parse a limit command." - pieces = BigSymbol(self.translated).getpieces() self.output = TaggedOutput().settag('span class="limits"') - for piece in pieces: - self.contents.append(TaggedBit().constant(piece, 'span class="limit"')) + symbol = self.translated + self.contents.append(TaggedBit().constant(symbol, 'span class="limit"')) class LimitPreviousCommand(LimitCommand): "A command to limit the previous command." @@ -4662,6 +2768,10 @@ class LimitPreviousCommand(LimitCommand): "Return a printable representation." return 'Limit previous command' + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class LimitsProcessor(MathsProcessor): "A processor for limits inside an element." @@ -4676,6 +2786,7 @@ class LimitsProcessor(MathsProcessor): 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): @@ -4694,11 +2805,14 @@ class LimitsProcessor(MathsProcessor): "Modify a limits commands so that the limits appear above and below." limited = contents[index] subscript = self.getlimit(contents, index + 1) - limited.contents.append(subscript) if self.checkscript(contents, index + 1): superscript = self.getlimit(contents, index + 1) else: - superscript = TaggedBit().constant(u' ', 'sup class="limit"') + superscript = TaggedBit().constant(u'\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): @@ -4712,6 +2826,9 @@ class LimitsProcessor(MathsProcessor): 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) @@ -4770,7 +2887,7 @@ class BracketProcessor(MathsProcessor): 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') @@ -4807,20 +2924,9 @@ class BracketProcessor(MathsProcessor): command.output = ContentsOutput() command.contents = bracket.getcontents() -class TodayCommand(EmptyCommand): - "Shows today's date." - - commandmap = None - - def parsebit(self, pos): - "Parse a command without parameters" - self.output = FixedOutput() - self.html = [datetime.date.today().strftime('%b %d, %Y')] - -FormulaCommand.types += [ - DecoratingFunction, CombiningFunction, LimitCommand, BracketCommand, - ] +FormulaCommand.types += [OversetFunction, UndersetFunction, + CombiningFunction, LimitCommand, BracketCommand] FormulaProcessor.processors += [ LimitsProcessor(), BracketProcessor(), @@ -4886,6 +2992,10 @@ class ParameterDefinition(object): result += ' (empty)' return result + if sys.version_info >= (3, 0): + __str__ = __unicode__ + + class ParameterFunction(CommandBit): "A function with a variable number of parameters defined in a template." "The parameters are defined as a parameter definition." @@ -5033,7 +3143,7 @@ class HybridFunction(ParameterFunction): 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 not self.command in HybridSize.configsizes: @@ -5044,6 +3154,7 @@ class HybridFunction(ParameterFunction): for element in self.contents: element.size = self.size + class HybridSize(object): "The size associated with a hybrid function." @@ -5066,286 +3177,6 @@ FormulaCommand.types += [HybridFunction] - - - - - - -class HeaderParser(Parser): - "Parses the LyX header" - - def parse(self, reader): - "Parse header parameters into a dictionary, return the preamble." - contents = [] - self.parseending(reader, lambda: self.parseline(reader, contents)) - # skip last line - reader.nextline() - return contents - - def parseline(self, reader, contents): - "Parse a single line as a parameter or as a start" - line = reader.currentline() - if line.startswith(HeaderConfig.parameters['branch']): - self.parsebranch(reader) - return - elif line.startswith(HeaderConfig.parameters['lstset']): - LstParser().parselstset(reader) - return - elif line.startswith(HeaderConfig.parameters['beginpreamble']): - contents.append(self.factory.createcontainer(reader)) - return - # no match - self.parseparameter(reader) - - def parsebranch(self, reader): - "Parse all branch definitions." - branch = reader.currentline().split()[1] - reader.nextline() - subparser = HeaderParser().complete(HeaderConfig.parameters['endbranch']) - subparser.parse(reader) - options = BranchOptions(branch) - for key in subparser.parameters: - options.set(key, subparser.parameters[key]) - Options.branches[branch] = options - - def complete(self, ending): - "Complete the parser with the given ending." - self.ending = ending - return self - -class PreambleParser(Parser): - "A parser for the LyX preamble." - - preamble = [] - - def parse(self, reader): - "Parse the full preamble with all statements." - self.ending = HeaderConfig.parameters['endpreamble'] - self.parseending(reader, lambda: self.parsepreambleline(reader)) - return [] - - def parsepreambleline(self, reader): - "Parse a single preamble line." - PreambleParser.preamble.append(reader.currentline()) - reader.nextline() - -class LstParser(object): - "Parse global and local lstparams." - - globalparams = dict() - - def parselstset(self, reader): - "Parse a declaration of lstparams in lstset." - paramtext = self.extractlstset(reader) - if not '{' in paramtext: - Trace.error('Missing opening bracket in lstset: ' + paramtext) - return - lefttext = paramtext.split('{')[1] - croppedtext = lefttext[:-1] - LstParser.globalparams = self.parselstparams(croppedtext) - - def extractlstset(self, reader): - "Extract the global lstset parameters." - paramtext = '' - while not reader.finished(): - paramtext += reader.currentline() - reader.nextline() - if paramtext.endswith('}'): - return paramtext - Trace.error('Could not find end of \\lstset settings; aborting') - - def parsecontainer(self, container): - "Parse some lstparams from elyxer.a container." - container.lstparams = LstParser.globalparams.copy() - paramlist = container.getparameterlist('lstparams') - container.lstparams.update(self.parselstparams(paramlist)) - - def parselstparams(self, paramlist): - "Process a number of lstparams from elyxer.a list." - paramdict = dict() - for param in paramlist: - if not '=' in param: - if len(param.strip()) > 0: - Trace.error('Invalid listing parameter ' + param) - else: - key, value = param.split('=', 1) - paramdict[key] = value - return paramdict - - - - -class MacroDefinition(CommandBit): - "A function that defines a new command (a macro)." - - macros = dict() - - def parsebit(self, pos): - "Parse the function that defines the macro." - self.output = EmptyOutput() - self.parameternumber = 0 - self.defaults = [] - self.factory.defining = True - self.parseparameters(pos) - self.factory.defining = False - Trace.debug('New command ' + self.newcommand + ' (' + \ - unicode(self.parameternumber) + ' parameters)') - self.macros[self.newcommand] = self - - def parseparameters(self, pos): - "Parse all optional parameters (number of parameters, default values)" - "and the mandatory definition." - self.newcommand = self.parsenewcommand(pos) - # parse number of parameters - literal = self.parsesquareliteral(pos) - if literal: - self.parameternumber = int(literal) - # parse all default values - bracket = self.parsesquare(pos) - while bracket: - self.defaults.append(bracket) - bracket = self.parsesquare(pos) - # parse mandatory definition - self.definition = self.parseparameter(pos) - - def parsenewcommand(self, pos): - "Parse the name of the new command." - self.factory.clearskipped(pos) - if self.factory.detecttype(Bracket, pos): - return self.parseliteral(pos) - if self.factory.detecttype(FormulaCommand, pos): - return self.factory.create(FormulaCommand).extractcommand(pos) - Trace.error('Unknown formula bit in defining function at ' + pos.identifier()) - return 'unknown' - - def instantiate(self): - "Return an instance of the macro." - return self.definition.clone() - -class MacroParameter(FormulaBit): - "A parameter from elyxer.a macro." - - def detect(self, pos): - "Find a macro parameter: #n." - return pos.checkfor('#') - - def parsebit(self, pos): - "Parse the parameter: #n." - if not pos.checkskip('#'): - Trace.error('Missing parameter start #.') - return - self.number = int(pos.skipcurrent()) - self.original = '#' + unicode(self.number) - self.contents = [TaggedBit().constant('#' + unicode(self.number), 'span class="unknown"')] - -class MacroFunction(CommandBit): - "A function that was defined using a macro." - - commandmap = MacroDefinition.macros - - def parsebit(self, pos): - "Parse a number of input parameters." - self.output = FilteredOutput() - self.values = [] - macro = self.translated - self.parseparameters(pos, macro) - self.completemacro(macro) - - def parseparameters(self, pos, macro): - "Parse as many parameters as are needed." - self.parseoptional(pos, list(macro.defaults)) - self.parsemandatory(pos, macro.parameternumber - len(macro.defaults)) - if len(self.values) < macro.parameternumber: - Trace.error('Missing parameters in macro ' + unicode(self)) - - def parseoptional(self, pos, defaults): - "Parse optional parameters." - optional = [] - while self.factory.detecttype(SquareBracket, pos): - optional.append(self.parsesquare(pos)) - if len(optional) > len(defaults): - break - for value in optional: - default = defaults.pop() - if len(value.contents) > 0: - self.values.append(value) - else: - self.values.append(default) - self.values += defaults - - def parsemandatory(self, pos, number): - "Parse a number of mandatory parameters." - for index in range(number): - parameter = self.parsemacroparameter(pos, number - index) - if not parameter: - return - self.values.append(parameter) - - def parsemacroparameter(self, pos, remaining): - "Parse a macro parameter. Could be a bracket or a single letter." - "If there are just two values remaining and there is a running number," - "parse as two separater numbers." - self.factory.clearskipped(pos) - if pos.finished(): - return None - if self.factory.detecttype(FormulaNumber, pos): - return self.parsenumbers(pos, remaining) - return self.parseparameter(pos) - - def parsenumbers(self, pos, remaining): - "Parse the remaining parameters as a running number." - "For example, 12 would be {1}{2}." - number = self.factory.parsetype(FormulaNumber, pos) - if not len(number.original) == remaining: - return number - for digit in number.original: - value = self.factory.create(FormulaNumber) - value.add(FormulaConstant(digit)) - value.type = number - self.values.append(value) - return None - - def completemacro(self, macro): - "Complete the macro with the parameters read." - self.contents = [macro.instantiate()] - replaced = [False] * len(self.values) - for parameter in self.searchall(MacroParameter): - index = parameter.number - 1 - if index >= len(self.values): - Trace.error('Macro parameter index out of bounds: ' + unicode(index)) - return - replaced[index] = True - parameter.contents = [self.values[index].clone()] - for index in range(len(self.values)): - if not replaced[index]: - self.addfilter(index, self.values[index]) - - def addfilter(self, index, value): - "Add a filter for the given parameter number and parameter value." - original = '#' + unicode(index + 1) - value = ''.join(self.values[0].gethtml()) - self.output.addfilter(original, value) - -class FormulaMacro(Formula): - "A math macro defined in an inset." - - def __init__(self): - self.parser = MacroParser() - self.output = EmptyOutput() - - def __unicode__(self): - "Return a printable representation." - return 'Math macro' - -FormulaFactory.types += [ MacroParameter ] - -FormulaCommand.types += [ - MacroFunction, - ] - - - def math2html(formula): "Convert some TeX math to HTML." factory = FormulaFactory() @@ -5355,7 +3186,7 @@ def math2html(formula): return ''.join(whole.gethtml()) def main(): - "Main function, called if invoked from elyxer.the command line" + "Main function, called if invoked from the command line" args = sys.argv Options().parseoptions(args) if len(args) != 1: @@ -5366,4 +3197,3 @@ def main(): if __name__ == '__main__': main() - diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py b/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py index ccc5593..dd7389d 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# :Id: $Id: tex2mathml_extern.py 7861 2015-04-10 23:48:51Z milde $ +# :Id: $Id: tex2mathml_extern.py 8883 2021-11-09 23:54:54Z milde $ # :Copyright: © 2015 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -10,11 +10,15 @@ # 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 +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause -# Wrappers for TeX->MathML conversion by external tools -# ===================================================== +"""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. +""" + +from __future__ import print_function import subprocess document_template = r"""\documentclass{article} @@ -26,7 +30,7 @@ document_template = r"""\documentclass{article} def latexml(math_code, reporter=None): """Convert LaTeX math code to MathML with LaTeXML_ - + .. _LaTeXML: http://dlmf.nist.gov/LaTeXML/ """ p = subprocess.Popen(['latexml', @@ -42,7 +46,7 @@ def latexml(math_code, reporter=None): p.stdin.close() latexml_code = p.stdout.read() latexml_err = p.stderr.read().decode('utf8') - if reporter and latexml_err.find('Error') >= 0 or not latexml_code: + if reporter and (latexml_err.find('Error') >= 0 or not latexml_code): reporter.error(latexml_err) post_p = subprocess.Popen(['latexmlpost', @@ -60,11 +64,11 @@ def latexml(math_code, reporter=None): post_p.stdin.close() result = post_p.stdout.read().decode('utf8') post_p_err = post_p.stderr.read().decode('utf8') - if reporter and post_p_err.find('Error') >= 0 or not result: + if reporter and (post_p_err.find('Error') >= 0 or not result): reporter.error(post_p_err) - + # extract MathML code: - start,end = result.find('<math'), result.find('</math>')+7 + start, end = result.find('<math'), result.find('</math>')+7 result = result[start:end] if 'class="ltx_ERROR' in result: raise SyntaxError(result) @@ -72,7 +76,7 @@ def latexml(math_code, reporter=None): def ttm(math_code, reporter=None): """Convert LaTeX math code to MathML with TtM_ - + .. _TtM: http://hutchinson.belmont.ma.us/tth/mml/ """ p = subprocess.Popen(['ttm', @@ -94,13 +98,13 @@ def ttm(math_code, reporter=None): raise SyntaxError('\nMessage from external converter TtM:\n'+ msg) if reporter and err.find('**** Error') >= 0 or not result: reporter.error(err) - start,end = result.find('<math'), result.find('</math>')+7 + start, end = result.find('<math'), result.find('</math>')+7 result = result[start:end] return result def blahtexml(math_code, inline=True, reporter=None): """Convert LaTeX math code to MathML with blahtexml_ - + .. _blahtexml: http://gva.noekeon.org/blahtexml/ """ options = ['--mathml', @@ -116,7 +120,7 @@ def blahtexml(math_code, inline=True, reporter=None): else: mathmode_arg = 'mode="display"' options.append('--displaymath') - + p = subprocess.Popen(['blahtexml']+options, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -126,14 +130,13 @@ def blahtexml(math_code, inline=True, reporter=None): p.stdin.close() result = p.stdout.read().decode('utf8') err = p.stderr.read().decode('utf8') - - print err + if result.find('<error>') >= 0: raise SyntaxError('\nMessage from external converter blahtexml:\n' +result[result.find('<message>')+9:result.find('</message>')]) if reporter and (err.find('**** Error') >= 0 or not result): reporter.error(err) - start,end = result.find('<markup>')+9, result.find('</markup>') + start, end = result.find('<markup>')+9, result.find('</markup>') result = ('<math xmlns="http://www.w3.org/1998/Math/MathML"%s>\n' '%s</math>\n') % (mathmode_arg, result[start:end]) return result @@ -141,7 +144,8 @@ def blahtexml(math_code, inline=True, reporter=None): # self-test if __name__ == "__main__": - example = ur'\frac{\partial \sin^2(\alpha)}{\partial \vec r} \varpi \, \text{Grüße}' - # print latexml(example).encode('utf8') - # print ttm(example)#.encode('utf8') - print blahtexml(example).encode('utf8') + example = (u'\\frac{\\partial \\sin^2(\\alpha)}{\\partial \\vec r}' + u'\\varpi \\, \\text{Grüße}') + # print(latexml(example).encode('utf8')) + # print(ttm(example)) + print(blahtexml(example).encode('utf8')) diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/tex2unichar.py b/docutils/src/main/resources/docutils/docutils/utils/math/tex2unichar.py index 434c0e8..dfcc1c9 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/tex2unichar.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/tex2unichar.py @@ -1,36 +1,32 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf8 -*- # 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: wasysym, stmaryrd, mathdots, mathabx, esint, bbold, amsxtra, amsmath, amssymb, standard LaTeX +# Includes commands from: wasysym, stmaryrd, txfonts, mathdots, mathabx, esint, bbold, amsxtra, amsmath, amssymb, standard LaTeX mathaccent = { - 'acute': u'\u0301', # x́ COMBINING ACUTE ACCENT - 'bar': u'\u0304', # x̄ COMBINING MACRON - 'breve': u'\u0306', # x̆ COMBINING BREVE - 'check': u'\u030c', # x̌ COMBINING CARON - 'ddddot': u'\u20dc', # x⃜ COMBINING FOUR DOTS ABOVE - 'dddot': u'\u20db', # x⃛ COMBINING THREE DOTS ABOVE - 'ddot': u'\u0308', # ẍ COMBINING DIAERESIS - 'dot': u'\u0307', # ẋ COMBINING DOT ABOVE - 'grave': u'\u0300', # x̀ COMBINING GRAVE ACCENT - 'hat': u'\u0302', # x̂ COMBINING CIRCUMFLEX ACCENT - 'mathring': u'\u030a', # x̊ COMBINING RING ABOVE - 'not': u'\u0338', # x̸ COMBINING LONG SOLIDUS OVERLAY - 'overleftarrow': u'\u20d6', # x⃖ COMBINING LEFT ARROW ABOVE - 'overleftrightarrow': u'\u20e1', # x⃡ COMBINING LEFT RIGHT ARROW ABOVE - 'overline': u'\u0305', # x̅ COMBINING OVERLINE - 'overrightarrow': u'\u20d7', # x⃗ COMBINING RIGHT ARROW ABOVE - 'tilde': u'\u0303', # x̃ COMBINING TILDE - 'underbar': u'\u0331', # x̱ COMBINING MACRON BELOW - 'underleftarrow': u'\u20ee', # x⃮ COMBINING LEFT ARROW BELOW - 'underline': u'\u0332', # x̲ COMBINING LOW LINE - 'underrightarrow': u'\u20ef', # x⃯ COMBINING RIGHT ARROW BELOW - 'vec': u'\u20d7', # x⃗ COMBINING RIGHT ARROW ABOVE - 'widehat': u'\u0302', # x̂ COMBINING CIRCUMFLEX ACCENT - 'widetilde': u'\u0303', # x̃ COMBINING TILDE + 'acute': u'\u0301', # ́ COMBINING ACUTE ACCENT + 'bar': u'\u0304', # ̄ COMBINING MACRON + 'breve': u'\u0306', # ̆ COMBINING BREVE + 'check': u'\u030c', # ̌ COMBINING CARON + 'ddddot': u'\u20dc', # ⃜ COMBINING FOUR DOTS ABOVE + 'dddot': u'\u20db', # ⃛ COMBINING THREE DOTS ABOVE + 'ddot': u'\u0308', # ̈ COMBINING DIAERESIS + 'dot': u'\u0307', # ̇ COMBINING DOT ABOVE + 'grave': u'\u0300', # ̀ COMBINING GRAVE ACCENT + 'hat': u'\u0302', # ̂ COMBINING CIRCUMFLEX ACCENT + 'mathring': u'\u030a', # ̊ COMBINING RING ABOVE + 'not': u'\u0338', # ̸ COMBINING LONG SOLIDUS OVERLAY + 'overleftrightarrow': u'\u20e1', # ⃡ COMBINING LEFT RIGHT ARROW ABOVE + 'overline': u'\u0305', # ̅ COMBINING OVERLINE + 'tilde': u'\u0303', # ̃ COMBINING TILDE + 'underbar': u'\u0331', # ̱ COMBINING MACRON BELOW + 'underleftarrow': u'\u20ee', # ⃮ COMBINING LEFT ARROW BELOW + 'underline': u'\u0332', # ̲ COMBINING LOW LINE + 'underrightarrow': u'\u20ef', # ⃯ COMBINING RIGHT ARROW BELOW + 'vec': u'\u20d7', # ⃗ COMBINING RIGHT ARROW ABOVE } mathalpha = { 'Bbbk': u'\U0001d55c', # 𝕜 MATHEMATICAL DOUBLE-STRUCK SMALL K @@ -54,14 +50,13 @@ mathalpha = { 'chi': u'\u03c7', # χ GREEK SMALL LETTER CHI 'daleth': u'\u2138', # ℸ DALET SYMBOL 'delta': u'\u03b4', # δ GREEK SMALL LETTER DELTA - 'digamma': u'\u03dc', # Ϝ GREEK LETTER DIGAMMA + 'digamma': u'\u03dd', # ϝ GREEK SMALL LETTER DIGAMMA 'ell': u'\u2113', # ℓ SCRIPT SMALL L 'epsilon': u'\u03f5', # ϵ GREEK LUNATE EPSILON SYMBOL 'eta': u'\u03b7', # η GREEK SMALL LETTER ETA 'eth': u'\xf0', # ð LATIN SMALL LETTER ETH 'gamma': u'\u03b3', # γ GREEK SMALL LETTER GAMMA 'gimel': u'\u2137', # ℷ GIMEL SYMBOL - 'hbar': u'\u210f', # ℏ PLANCK CONSTANT OVER TWO PI 'hslash': u'\u210f', # ℏ PLANCK CONSTANT OVER TWO PI 'imath': u'\u0131', # ı LATIN SMALL LETTER DOTLESS I 'iota': u'\u03b9', # ι GREEK SMALL LETTER IOTA @@ -91,7 +86,7 @@ mathalpha = { 'varUpsilon': u'\U0001d6f6', # 𝛶 MATHEMATICAL ITALIC CAPITAL UPSILON 'varXi': u'\U0001d6ef', # 𝛯 MATHEMATICAL ITALIC CAPITAL XI 'varepsilon': u'\u03b5', # ε GREEK SMALL LETTER EPSILON - 'varkappa': u'\U0001d718', # 𝜘 MATHEMATICAL ITALIC KAPPA SYMBOL + 'varkappa': u'\u03f0', # ϰ GREEK KAPPA SYMBOL 'varphi': u'\u03c6', # φ GREEK SMALL LETTER PHI 'varpi': u'\u03d6', # ϖ GREEK PI SYMBOL 'varrho': u'\u03f1', # ϱ GREEK RHO SYMBOL @@ -110,14 +105,11 @@ mathbin = { 'amalg': u'\u2a3f', # ⨿ AMALGAMATION OR COPRODUCT 'ast': u'\u2217', # ∗ ASTERISK OPERATOR 'barwedge': u'\u22bc', # ⊼ NAND + 'bigcirc': u'\u25ef', # ◯ LARGE CIRCLE 'bigtriangledown': u'\u25bd', # ▽ WHITE DOWN-POINTING TRIANGLE 'bigtriangleup': u'\u25b3', # △ WHITE UP-POINTING TRIANGLE 'bindnasrepma': u'\u214b', # ⅋ TURNED AMPERSAND 'blacklozenge': u'\u29eb', # ⧫ BLACK LOZENGE - 'blacktriangledown': u'\u25be', # ▾ BLACK DOWN-POINTING SMALL TRIANGLE - 'blacktriangleleft': u'\u25c2', # ◂ BLACK LEFT-POINTING SMALL TRIANGLE - 'blacktriangleright': u'\u25b8', # ▸ BLACK RIGHT-POINTING SMALL TRIANGLE - 'blacktriangleup': u'\u25b4', # ▴ BLACK UP-POINTING SMALL TRIANGLE 'boxast': u'\u29c6', # ⧆ SQUARED ASTERISK 'boxbar': u'\u25eb', # ◫ WHITE SQUARE WITH VERTICAL BISECTING LINE 'boxbox': u'\u29c8', # ⧈ SQUARED SQUARE @@ -128,13 +120,16 @@ mathbin = { 'boxplus': u'\u229e', # ⊞ SQUARED PLUS 'boxslash': u'\u29c4', # ⧄ SQUARED RISING DIAGONAL SLASH 'boxtimes': u'\u22a0', # ⊠ SQUARED TIMES - 'bullet': u'\u2219', # ∙ BULLET OPERATOR + 'bullet': u'\u2022', # • BULLET 'cap': u'\u2229', # ∩ INTERSECTION 'cdot': u'\u22c5', # ⋅ DOT OPERATOR 'circ': u'\u2218', # ∘ RING OPERATOR 'circledast': u'\u229b', # ⊛ CIRCLED ASTERISK OPERATOR + 'circledbslash': u'\u29b8', # ⦸ CIRCLED REVERSE SOLIDUS 'circledcirc': u'\u229a', # ⊚ CIRCLED RING OPERATOR 'circleddash': u'\u229d', # ⊝ CIRCLED DASH + 'circledgtr': u'\u29c1', # ⧁ CIRCLED GREATER-THAN + 'circledless': u'\u29c0', # ⧀ CIRCLED LESS-THAN 'cup': u'\u222a', # ∪ UNION 'curlyvee': u'\u22ce', # ⋎ CURLY LOGICAL OR 'curlywedge': u'\u22cf', # ⋏ CURLY LOGICAL AND @@ -145,11 +140,13 @@ mathbin = { 'divideontimes': u'\u22c7', # ⋇ DIVISION TIMES 'dotplus': u'\u2214', # ∔ DOT PLUS 'doublebarwedge': u'\u2a5e', # ⩞ LOGICAL AND WITH DOUBLE OVERBAR + 'gtrdot': u'\u22d7', # ⋗ GREATER-THAN WITH DOT 'intercal': u'\u22ba', # ⊺ INTERCALATE 'interleave': u'\u2af4', # ⫴ TRIPLE VERTICAL BAR BINARY RELATION + 'invamp': u'\u214b', # ⅋ TURNED AMPERSAND 'land': u'\u2227', # ∧ LOGICAL AND 'leftthreetimes': u'\u22cb', # ⋋ LEFT SEMIDIRECT PRODUCT - 'lhd': u'\u25c1', # ◁ WHITE LEFT-POINTING TRIANGLE + 'lessdot': u'\u22d6', # ⋖ LESS-THAN WITH DOT 'lor': u'\u2228', # ∨ LOGICAL OR 'ltimes': u'\u22c9', # ⋉ LEFT NORMAL FACTOR SEMIDIRECT PRODUCT 'mp': u'\u2213', # ∓ MINUS-OR-PLUS SIGN @@ -159,7 +156,6 @@ mathbin = { 'oslash': u'\u2298', # ⊘ CIRCLED DIVISION SLASH 'otimes': u'\u2297', # ⊗ CIRCLED TIMES 'pm': u'\xb1', # ± PLUS-MINUS SIGN - 'rhd': u'\u25b7', # ▷ WHITE RIGHT-POINTING TRIANGLE 'rightthreetimes': u'\u22cc', # ⋌ RIGHT SEMIDIRECT PRODUCT 'rtimes': u'\u22ca', # ⋊ RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT 'setminus': u'\u29f5', # ⧵ REVERSE SOLIDUS OPERATOR @@ -168,19 +164,15 @@ mathbin = { 'smalltriangledown': u'\u25bf', # ▿ WHITE DOWN-POINTING SMALL TRIANGLE 'smalltriangleleft': u'\u25c3', # ◃ WHITE LEFT-POINTING SMALL TRIANGLE 'smalltriangleright': u'\u25b9', # ▹ WHITE RIGHT-POINTING SMALL TRIANGLE - 'smalltriangleup': u'\u25b5', # ▵ WHITE UP-POINTING SMALL TRIANGLE 'sqcap': u'\u2293', # ⊓ SQUARE CAP 'sqcup': u'\u2294', # ⊔ SQUARE CUP 'sslash': u'\u2afd', # ⫽ DOUBLE SOLIDUS OPERATOR 'star': u'\u22c6', # ⋆ STAR OPERATOR 'talloblong': u'\u2afe', # ⫾ WHITE VERTICAL BAR 'times': u'\xd7', # × MULTIPLICATION SIGN - 'triangle': u'\u25b3', # △ WHITE UP-POINTING TRIANGLE - 'triangledown': u'\u25bf', # ▿ WHITE DOWN-POINTING SMALL TRIANGLE 'triangleleft': u'\u25c3', # ◃ WHITE LEFT-POINTING SMALL TRIANGLE 'triangleright': u'\u25b9', # ▹ WHITE RIGHT-POINTING SMALL TRIANGLE 'uplus': u'\u228e', # ⊎ MULTISET UNION - 'vartriangle': u'\u25b3', # △ WHITE UP-POINTING TRIANGLE 'vee': u'\u2228', # ∨ LOGICAL OR 'veebar': u'\u22bb', # ⊻ XOR 'wedge': u'\u2227', # ∧ LOGICAL AND @@ -207,13 +199,13 @@ mathfence = { '|': u'\u2016', # ‖ DOUBLE VERTICAL LINE } mathop = { - 'Join': u'\u2a1d', # ⨝ JOIN 'bigcap': u'\u22c2', # ⋂ N-ARY INTERSECTION 'bigcup': u'\u22c3', # ⋃ N-ARY UNION 'biginterleave': u'\u2afc', # ⫼ LARGE TRIPLE VERTICAL BAR OPERATOR 'bigodot': u'\u2a00', # ⨀ N-ARY CIRCLED DOT OPERATOR 'bigoplus': u'\u2a01', # ⨁ N-ARY CIRCLED PLUS OPERATOR 'bigotimes': u'\u2a02', # ⨂ N-ARY CIRCLED TIMES OPERATOR + 'bigsqcap': u'\u2a05', # ⨅ N-ARY SQUARE INTERSECTION OPERATOR 'bigsqcup': u'\u2a06', # ⨆ N-ARY SQUARE UNION OPERATOR 'biguplus': u'\u2a04', # ⨄ N-ARY UNION OPERATOR WITH PLUS 'bigvee': u'\u22c1', # ⋁ N-ARY LOGICAL OR @@ -225,13 +217,17 @@ mathop = { 'iiint': u'\u222d', # ∭ TRIPLE INTEGRAL 'iint': u'\u222c', # ∬ DOUBLE INTEGRAL 'int': u'\u222b', # ∫ INTEGRAL + 'intop': u'\u222b', # ∫ INTEGRAL + 'oiiint': u'\u2230', # ∰ VOLUME INTEGRAL 'oiint': u'\u222f', # ∯ SURFACE INTEGRAL 'oint': u'\u222e', # ∮ CONTOUR INTEGRAL 'ointctrclockwise': u'\u2233', # ∳ ANTICLOCKWISE CONTOUR INTEGRAL + 'ointop': u'\u222e', # ∮ CONTOUR INTEGRAL 'prod': u'\u220f', # ∏ N-ARY PRODUCT 'sqint': u'\u2a16', # ⨖ QUATERNION INTEGRAL OPERATOR 'sum': u'\u2211', # ∑ N-ARY SUMMATION 'varointclockwise': u'\u2232', # ∲ CLOCKWISE CONTOUR INTEGRAL + 'varprod': u'\u2a09', # ⨉ N-ARY TIMES OPERATOR } mathopen = { 'Lbag': u'\u27c5', # ⟅ LEFT S-SHAPED BAG DELIMITER @@ -263,9 +259,12 @@ mathord = { 'APLrightarrowbox': u'\u2348', # ⍈ APL FUNCTIONAL SYMBOL QUAD RIGHTWARDS ARROW 'APLuparrowbox': u'\u2350', # ⍐ APL FUNCTIONAL SYMBOL QUAD UPWARDS ARROW 'Aries': u'\u2648', # ♈ ARIES + 'Box': u'\u2b1c', # ⬜ WHITE LARGE SQUARE 'CIRCLE': u'\u25cf', # ● BLACK CIRCLE 'CheckedBox': u'\u2611', # ☑ BALLOT BOX WITH CHECK 'Diamond': u'\u25c7', # ◇ WHITE DIAMOND + 'Diamondblack': u'\u25c6', # ◆ BLACK DIAMOND + 'Diamonddot': u'\u27d0', # ⟐ WHITE DIAMOND WITH CENTRED DOT 'Finv': u'\u2132', # Ⅎ TURNED CAPITAL F 'Game': u'\u2141', # ⅁ TURNED SANS-SERIF CAPITAL G 'Gemini': u'\u264a', # ♊ GEMINI @@ -277,9 +276,11 @@ mathord = { 'Mars': u'\u2642', # ♂ MALE SIGN 'Mercury': u'\u263f', # ☿ MERCURY 'Neptune': u'\u2646', # ♆ NEPTUNE + 'P': u'\xb6', # ¶ PILCROW SIGN 'Pluto': u'\u2647', # ♇ PLUTO 'RIGHTCIRCLE': u'\u25d7', # ◗ RIGHT HALF BLACK CIRCLE 'RIGHTcircle': u'\u25d1', # ◑ CIRCLE WITH RIGHT HALF BLACK + 'S': u'\xa7', # § SECTION SIGN 'Saturn': u'\u2644', # ♄ SATURN 'Scorpio': u'\u264f', # ♏ SCORPIUS 'Square': u'\u2610', # ☐ BALLOT BOX @@ -293,36 +294,33 @@ mathord = { 'angle': u'\u2220', # ∠ ANGLE 'aquarius': u'\u2652', # ♒ AQUARIUS 'aries': u'\u2648', # ♈ ARIES - 'ast': u'*', # * ASTERISK - 'backepsilon': u'\u03f6', # ϶ GREEK REVERSED LUNATE EPSILON SYMBOL + 'arrowvert': u'\u23d0', # ⏐ VERTICAL LINE EXTENSION 'backprime': u'\u2035', # ‵ REVERSED PRIME 'backslash': u'\\', # \ REVERSE SOLIDUS - 'because': u'\u2235', # ∵ BECAUSE 'bigstar': u'\u2605', # ★ BLACK STAR - 'binampersand': u'&', # & AMPERSAND - 'blacklozenge': u'\u2b27', # ⬧ BLACK MEDIUM LOZENGE 'blacksmiley': u'\u263b', # ☻ BLACK SMILING FACE 'blacksquare': u'\u25fc', # ◼ BLACK MEDIUM SQUARE + 'blacktriangle': u'\u25b4', # ▴ BLACK UP-POINTING SMALL TRIANGLE + 'blacktriangledown': u'\u25be', # ▾ BLACK DOWN-POINTING SMALL TRIANGLE + 'blacktriangleup': u'\u25b4', # ▴ BLACK UP-POINTING SMALL TRIANGLE 'bot': u'\u22a5', # ⊥ UP TACK 'boy': u'\u2642', # ♂ MALE SIGN + 'bracevert': u'\u23aa', # ⎪ CURLY BRACKET EXTENSION 'cancer': u'\u264b', # ♋ CANCER 'capricornus': u'\u2651', # ♑ CAPRICORN 'cdots': u'\u22ef', # ⋯ MIDLINE HORIZONTAL ELLIPSIS 'cent': u'\xa2', # ¢ CENT SIGN - 'centerdot': u'\u2b1d', # ⬝ BLACK VERY SMALL SQUARE 'checkmark': u'\u2713', # ✓ CHECK MARK - 'circlearrowleft': u'\u21ba', # ↺ ANTICLOCKWISE OPEN CIRCLE ARROW - 'circlearrowright': u'\u21bb', # ↻ CLOCKWISE OPEN CIRCLE ARROW - 'circledR': u'\xae', # ® REGISTERED SIGN - 'circledcirc': u'\u25ce', # ◎ BULLSEYE + 'circledR': u'\u24c7', # Ⓡ CIRCLED LATIN CAPITAL LETTER R + 'circledS': u'\u24c8', # Ⓢ CIRCLED LATIN CAPITAL LETTER S 'clubsuit': u'\u2663', # ♣ BLACK CLUB SUIT 'complement': u'\u2201', # ∁ COMPLEMENT - 'dasharrow': u'\u21e2', # ⇢ RIGHTWARDS DASHED ARROW - 'dashleftarrow': u'\u21e0', # ⇠ LEFTWARDS DASHED ARROW - 'dashrightarrow': u'\u21e2', # ⇢ RIGHTWARDS DASHED ARROW + 'diagdown': u'\u27cd', # ⟍ MATHEMATICAL FALLING DIAGONAL + 'diagup': u'\u27cb', # ⟋ MATHEMATICAL RISING DIAGONAL 'diameter': u'\u2300', # ⌀ DIAMETER SIGN 'diamondsuit': u'\u2662', # ♢ WHITE DIAMOND SUIT 'earth': u'\u2641', # ♁ EARTH + 'emptyset': u'\u2205', # ∅ EMPTY SET 'exists': u'\u2203', # ∃ THERE EXISTS 'female': u'\u2640', # ♀ FEMALE SIGN 'flat': u'\u266d', # ♭ MUSIC FLAT SIGN @@ -333,19 +331,24 @@ mathord = { 'girl': u'\u2640', # ♀ FEMALE SIGN 'heartsuit': u'\u2661', # ♡ WHITE HEART SUIT 'infty': u'\u221e', # ∞ INFINITY + 'invdiameter': u'\u2349', # ⍉ APL FUNCTIONAL SYMBOL CIRCLE BACKSLASH 'invneg': u'\u2310', # ⌐ REVERSED NOT SIGN 'jupiter': u'\u2643', # ♃ JUPITER 'ldots': u'\u2026', # … HORIZONTAL ELLIPSIS 'leftmoon': u'\u263e', # ☾ LAST QUARTER MOON - 'leftturn': u'\u21ba', # ↺ ANTICLOCKWISE OPEN CIRCLE ARROW 'leo': u'\u264c', # ♌ LEO 'libra': u'\u264e', # ♎ LIBRA + 'lmoustache': u'\u23b0', # ⎰ UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION 'lnot': u'\xac', # ¬ NOT SIGN 'lozenge': u'\u25ca', # ◊ LOZENGE 'male': u'\u2642', # ♂ MALE SIGN 'maltese': u'\u2720', # ✠ MALTESE CROSS + 'mathcent': u'\xa2', # ¢ CENT SIGN 'mathdollar': u'$', # $ DOLLAR SIGN + 'mathsterling': u'\xa3', # £ POUND SIGN 'measuredangle': u'\u2221', # ∡ MEASURED ANGLE + 'medbullet': u'\u26ab', # ⚫ MEDIUM BLACK CIRCLE + 'medcirc': u'\u26aa', # ⚪ MEDIUM WHITE CIRCLE 'mercury': u'\u263f', # ☿ MERCURY 'mho': u'\u2127', # ℧ INVERTED OHM SIGN 'nabla': u'\u2207', # ∇ NABLA @@ -361,14 +364,12 @@ mathord = { 'prime': u'\u2032', # ′ PRIME 'quarternote': u'\u2669', # ♩ QUARTER NOTE 'rightmoon': u'\u263d', # ☽ FIRST QUARTER MOON - 'rightturn': u'\u21bb', # ↻ CLOCKWISE OPEN CIRCLE ARROW + 'rmoustache': u'\u23b1', # ⎱ UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION 'sagittarius': u'\u2650', # ♐ SAGITTARIUS 'saturn': u'\u2644', # ♄ SATURN 'scorpio': u'\u264f', # ♏ SCORPIUS 'second': u'\u2033', # ″ DOUBLE PRIME 'sharp': u'\u266f', # ♯ MUSIC SHARP SIGN - 'sim': u'~', # ~ TILDE - 'slash': u'/', # / SOLIDUS 'smiley': u'\u263a', # ☺ WHITE SMILING FACE 'spadesuit': u'\u2660', # ♠ BLACK SPADE SUIT 'spddot': u'\xa8', # ¨ DIAERESIS @@ -377,32 +378,37 @@ mathord = { 'sptilde': u'~', # ~ TILDE 'square': u'\u25fb', # ◻ WHITE MEDIUM SQUARE 'sun': u'\u263c', # ☼ WHITE SUN WITH RAYS + 'surd': u'\u221a', # √ SQUARE ROOT 'taurus': u'\u2649', # ♉ TAURUS - 'therefore': u'\u2234', # ∴ THEREFORE 'third': u'\u2034', # ‴ TRIPLE PRIME 'top': u'\u22a4', # ⊤ DOWN TACK - 'triangleleft': u'\u25c5', # ◅ WHITE LEFT-POINTING POINTER - 'triangleright': u'\u25bb', # ▻ WHITE RIGHT-POINTING POINTER 'twonotes': u'\u266b', # ♫ BEAMED EIGHTH NOTES 'uranus': u'\u2645', # ♅ URANUS 'varEarth': u'\u2641', # ♁ EARTH - 'varnothing': u'\u2205', # ∅ EMPTY SET + 'varclubsuit': u'\u2667', # ♧ WHITE CLUB SUIT + 'vardiamondsuit': u'\u2666', # ♦ BLACK DIAMOND SUIT + 'varheartsuit': u'\u2665', # ♥ BLACK HEART SUIT + 'varspadesuit': u'\u2664', # ♤ WHITE SPADE SUIT 'virgo': u'\u264d', # ♍ VIRGO 'wasylozenge': u'\u2311', # ⌑ SQUARE LOZENGE - 'wasytherefore': u'\u2234', # ∴ THEREFORE 'yen': u'\xa5', # ¥ YEN SIGN } mathover = { 'overbrace': u'\u23de', # ⏞ TOP CURLY BRACKET 'wideparen': u'\u23dc', # ⏜ TOP PARENTHESIS } +mathpunct = { + 'ddots': u'\u22f1', # ⋱ DOWN RIGHT DIAGONAL ELLIPSIS + 'vdots': u'\u22ee', # ⋮ VERTICAL ELLIPSIS + } mathradical = { - 'sqrt': u'\u221a', # √ SQUARE ROOT 'sqrt[3]': u'\u221b', # ∛ CUBE ROOT 'sqrt[4]': u'\u221c', # ∜ FOURTH ROOT } mathrel = { + 'Bot': u'\u2aeb', # ⫫ DOUBLE UP TACK 'Bumpeq': u'\u224e', # ≎ GEOMETRICALLY EQUIVALENT TO + 'Coloneqq': u'\u2a74', # ⩴ DOUBLE COLON EQUAL 'Doteq': u'\u2251', # ≑ GEOMETRICALLY EQUAL TO 'Downarrow': u'\u21d3', # ⇓ DOWNWARDS DOUBLE ARROW 'Leftarrow': u'\u21d0', # ⇐ LEFTWARDS DOUBLE ARROW @@ -416,11 +422,17 @@ mathrel = { 'Lsh': u'\u21b0', # ↰ UPWARDS ARROW WITH TIP LEFTWARDS 'Mapsfrom': u'\u2906', # ⤆ LEFTWARDS DOUBLE ARROW FROM BAR 'Mapsto': u'\u2907', # ⤇ RIGHTWARDS DOUBLE ARROW FROM BAR + 'Nearrow': u'\u21d7', # ⇗ NORTH EAST DOUBLE ARROW + 'Nwarrow': u'\u21d6', # ⇖ NORTH WEST DOUBLE ARROW + 'Perp': u'\u2aeb', # ⫫ DOUBLE UP TACK 'Rightarrow': u'\u21d2', # ⇒ RIGHTWARDS DOUBLE ARROW 'Rrightarrow': u'\u21db', # ⇛ RIGHTWARDS TRIPLE ARROW 'Rsh': u'\u21b1', # ↱ UPWARDS ARROW WITH TIP RIGHTWARDS + 'Searrow': u'\u21d8', # ⇘ SOUTH EAST DOUBLE ARROW 'Subset': u'\u22d0', # ⋐ DOUBLE SUBSET 'Supset': u'\u22d1', # ⋑ DOUBLE SUPERSET + 'Swarrow': u'\u21d9', # ⇙ SOUTH WEST DOUBLE ARROW + 'Top': u'\u2aea', # ⫪ DOUBLE DOWN TACK 'Uparrow': u'\u21d1', # ⇑ UPWARDS DOUBLE ARROW 'Updownarrow': u'\u21d5', # ⇕ UP DOWN DOUBLE ARROW 'VDash': u'\u22ab', # ⊫ DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE @@ -431,24 +443,33 @@ mathrel = { 'approx': u'\u2248', # ≈ ALMOST EQUAL TO 'approxeq': u'\u224a', # ≊ ALMOST EQUAL OR EQUAL TO 'asymp': u'\u224d', # ≍ EQUIVALENT TO + 'backepsilon': u'\u220d', # ∍ SMALL CONTAINS AS MEMBER 'backsim': u'\u223d', # ∽ REVERSED TILDE 'backsimeq': u'\u22cd', # ⋍ REVERSED TILDE EQUALS 'barin': u'\u22f6', # ⋶ ELEMENT OF WITH OVERBAR 'barleftharpoon': u'\u296b', # ⥫ LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH 'barrightharpoon': u'\u296d', # ⥭ RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH + 'because': u'\u2235', # ∵ BECAUSE 'between': u'\u226c', # ≬ BETWEEN + 'blacktriangleleft': u'\u25c2', # ◂ BLACK LEFT-POINTING SMALL TRIANGLE + 'blacktriangleright': u'\u25b8', # ▸ BLACK RIGHT-POINTING SMALL TRIANGLE 'bowtie': u'\u22c8', # ⋈ BOWTIE 'bumpeq': u'\u224f', # ≏ DIFFERENCE BETWEEN 'circeq': u'\u2257', # ≗ RING EQUAL TO + 'circlearrowleft': u'\u21ba', # ↺ ANTICLOCKWISE OPEN CIRCLE ARROW + 'circlearrowright': u'\u21bb', # ↻ CLOCKWISE OPEN CIRCLE ARROW 'coloneq': u'\u2254', # ≔ COLON EQUALS + 'coloneqq': u'\u2254', # ≔ COLON EQUALS 'cong': u'\u2245', # ≅ APPROXIMATELY EQUAL TO 'corresponds': u'\u2259', # ≙ ESTIMATES 'curlyeqprec': u'\u22de', # ⋞ EQUAL TO OR PRECEDES 'curlyeqsucc': u'\u22df', # ⋟ EQUAL TO OR SUCCEEDS 'curvearrowleft': u'\u21b6', # ↶ ANTICLOCKWISE TOP SEMICIRCLE ARROW 'curvearrowright': u'\u21b7', # ↷ CLOCKWISE TOP SEMICIRCLE ARROW + 'dasharrow': u'\u21e2', # ⇢ RIGHTWARDS DASHED ARROW + 'dashleftarrow': u'\u21e0', # ⇠ LEFTWARDS DASHED ARROW + 'dashrightarrow': u'\u21e2', # ⇢ RIGHTWARDS DASHED ARROW 'dashv': u'\u22a3', # ⊣ LEFT TACK - 'ddots': u'\u22f1', # ⋱ DOWN RIGHT DIAGONAL ELLIPSIS 'dlsh': u'\u21b2', # ↲ DOWNWARDS ARROW WITH TIP LEFTWARDS 'doteq': u'\u2250', # ≐ APPROACHES THE LIMIT 'doteqdot': u'\u2251', # ≑ GEOMETRICALLY EQUAL TO @@ -462,6 +483,7 @@ mathrel = { 'drsh': u'\u21b3', # ↳ DOWNWARDS ARROW WITH TIP RIGHTWARDS 'eqcirc': u'\u2256', # ≖ RING IN EQUAL TO 'eqcolon': u'\u2255', # ≕ EQUALS COLON + 'eqqcolon': u'\u2255', # ≕ EQUALS COLON 'eqsim': u'\u2242', # ≂ MINUS TILDE 'eqslantgtr': u'\u2a96', # ⪖ SLANTED EQUAL TO OR GREATER-THAN 'eqslantless': u'\u2a95', # ⪕ SLANTED EQUAL TO OR LESS-THAN @@ -476,12 +498,12 @@ mathrel = { 'gg': u'\u226b', # ≫ MUCH GREATER-THAN 'ggcurly': u'\u2abc', # ⪼ DOUBLE SUCCEEDS 'ggg': u'\u22d9', # ⋙ VERY MUCH GREATER-THAN + 'gggtr': u'\u22d9', # ⋙ VERY MUCH GREATER-THAN 'gnapprox': u'\u2a8a', # ⪊ GREATER-THAN AND NOT APPROXIMATE 'gneq': u'\u2a88', # ⪈ GREATER-THAN AND SINGLE-LINE NOT EQUAL TO 'gneqq': u'\u2269', # ≩ GREATER-THAN BUT NOT EQUAL TO 'gnsim': u'\u22e7', # ⋧ GREATER-THAN BUT NOT EQUIVALENT TO 'gtrapprox': u'\u2a86', # ⪆ GREATER-THAN OR APPROXIMATE - 'gtrdot': u'\u22d7', # ⋗ GREATER-THAN WITH DOT 'gtreqless': u'\u22db', # ⋛ GREATER-THAN EQUAL TO OR LESS-THAN 'gtreqqless': u'\u2a8c', # ⪌ GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN 'gtrless': u'\u2277', # ≷ GREATER-THAN OR LESS-THAN @@ -494,6 +516,7 @@ mathrel = { 'implies': u'\u27f9', # ⟹ LONG RIGHTWARDS DOUBLE ARROW 'in': u'\u2208', # ∈ ELEMENT OF 'le': u'\u2264', # ≤ LESS-THAN OR EQUAL TO + 'leadsto': u'\u2933', # ⤳ WAVE ARROW POINTING DIRECTLY RIGHT 'leftarrow': u'\u2190', # ← LEFTWARDS ARROW 'leftarrowtail': u'\u21a2', # ↢ LEFTWARDS ARROW WITH TAIL 'leftarrowtriangle': u'\u21fd', # ⇽ LEFTWARDS OPEN-HEADED ARROW @@ -510,19 +533,21 @@ mathrel = { 'leftrightsquigarrow': u'\u21ad', # ↭ LEFT RIGHT WAVE ARROW 'leftslice': u'\u2aa6', # ⪦ LESS-THAN CLOSED BY CURVE 'leftsquigarrow': u'\u21dc', # ⇜ LEFTWARDS SQUIGGLE ARROW + 'leftturn': u'\u21ba', # ↺ ANTICLOCKWISE OPEN CIRCLE ARROW 'leq': u'\u2264', # ≤ LESS-THAN OR EQUAL TO 'leqq': u'\u2266', # ≦ LESS-THAN OVER EQUAL TO 'leqslant': u'\u2a7d', # ⩽ LESS-THAN OR SLANTED EQUAL TO 'lessapprox': u'\u2a85', # ⪅ LESS-THAN OR APPROXIMATE - 'lessdot': u'\u22d6', # ⋖ LESS-THAN WITH DOT 'lesseqgtr': u'\u22da', # ⋚ LESS-THAN EQUAL TO OR GREATER-THAN 'lesseqqgtr': u'\u2a8b', # ⪋ LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN 'lessgtr': u'\u2276', # ≶ LESS-THAN OR GREATER-THAN 'lesssim': u'\u2272', # ≲ LESS-THAN OR EQUIVALENT TO + 'lhd': u'\u22b2', # ⊲ NORMAL SUBGROUP OF 'lightning': u'\u21af', # ↯ DOWNWARDS ZIGZAG ARROW 'll': u'\u226a', # ≪ MUCH LESS-THAN 'llcurly': u'\u2abb', # ⪻ DOUBLE PRECEDES 'lll': u'\u22d8', # ⋘ VERY MUCH LESS-THAN + 'llless': u'\u22d8', # ⋘ VERY MUCH LESS-THAN 'lnapprox': u'\u2a89', # ⪉ LESS-THAN AND NOT APPROXIMATE 'lneq': u'\u2a87', # ⪇ LESS-THAN AND SINGLE-LINE NOT EQUAL TO 'lneqq': u'\u2268', # ≨ LESS-THAN BUT NOT EQUAL TO @@ -534,11 +559,16 @@ mathrel = { 'longrightarrow': u'\u27f6', # ⟶ LONG RIGHTWARDS ARROW 'looparrowleft': u'\u21ab', # ↫ LEFTWARDS ARROW WITH LOOP 'looparrowright': u'\u21ac', # ↬ RIGHTWARDS ARROW WITH LOOP + 'lrtimes': u'\u22c8', # ⋈ BOWTIE 'mapsfrom': u'\u21a4', # ↤ LEFTWARDS ARROW FROM BAR 'mapsto': u'\u21a6', # ↦ RIGHTWARDS ARROW FROM BAR 'mid': u'\u2223', # ∣ DIVIDES 'models': u'\u22a7', # ⊧ MODELS 'multimap': u'\u22b8', # ⊸ MULTIMAP + 'multimapboth': u'\u29df', # ⧟ DOUBLE-ENDED MULTIMAP + 'multimapdotbothA': u'\u22b6', # ⊶ ORIGINAL OF + 'multimapdotbothB': u'\u22b7', # ⊷ IMAGE OF + 'multimapinv': u'\u27dc', # ⟜ LEFT MULTIMAP 'nLeftarrow': u'\u21cd', # ⇍ LEFTWARDS DOUBLE ARROW WITH STROKE 'nLeftrightarrow': u'\u21ce', # ⇎ LEFT RIGHT DOUBLE ARROW WITH STROKE 'nRightarrow': u'\u21cf', # ⇏ RIGHTWARDS DOUBLE ARROW WITH STROKE @@ -550,14 +580,17 @@ mathrel = { 'neq': u'\u2260', # ≠ NOT EQUAL TO 'ngeq': u'\u2271', # ≱ NEITHER GREATER-THAN NOR EQUAL TO 'ngtr': u'\u226f', # ≯ NOT GREATER-THAN + 'ngtrless': u'\u2279', # ≹ NEITHER GREATER-THAN NOR LESS-THAN 'ni': u'\u220b', # ∋ CONTAINS AS MEMBER 'nleftarrow': u'\u219a', # ↚ LEFTWARDS ARROW WITH STROKE 'nleftrightarrow': u'\u21ae', # ↮ LEFT RIGHT ARROW WITH STROKE 'nleq': u'\u2270', # ≰ NEITHER LESS-THAN NOR EQUAL TO 'nless': u'\u226e', # ≮ NOT LESS-THAN + 'nlessgtr': u'\u2278', # ≸ NEITHER LESS-THAN NOR GREATER-THAN 'nmid': u'\u2224', # ∤ DOES NOT DIVIDE 'notasymp': u'\u226d', # ≭ NOT EQUIVALENT TO 'notin': u'\u2209', # ∉ NOT AN ELEMENT OF + 'notni': u'\u220c', # ∌ DOES NOT CONTAIN AS MEMBER 'notowner': u'\u220c', # ∌ DOES NOT CONTAIN AS MEMBER 'notslash': u'\u233f', # ⌿ APL FUNCTIONAL SYMBOL SLASH BAR 'nparallel': u'\u2226', # ∦ NOT PARALLEL TO @@ -565,6 +598,7 @@ mathrel = { 'npreceq': u'\u22e0', # ⋠ DOES NOT PRECEDE OR EQUAL 'nrightarrow': u'\u219b', # ↛ RIGHTWARDS ARROW WITH STROKE 'nsim': u'\u2241', # ≁ NOT TILDE + 'nsimeq': u'\u2244', # ≄ NOT ASYMPTOTICALLY EQUAL TO 'nsubseteq': u'\u2288', # ⊈ NEITHER A SUBSET OF NOR EQUAL TO 'nsucc': u'\u2281', # ⊁ DOES NOT SUCCEED 'nsucceq': u'\u22e1', # ⋡ DOES NOT SUCCEED OR EQUAL @@ -584,11 +618,14 @@ mathrel = { 'precapprox': u'\u2ab7', # ⪷ PRECEDES ABOVE ALMOST EQUAL TO 'preccurlyeq': u'\u227c', # ≼ PRECEDES OR EQUAL TO 'preceq': u'\u2aaf', # ⪯ PRECEDES ABOVE SINGLE-LINE EQUALS SIGN + 'preceqq': u'\u2ab3', # ⪳ PRECEDES ABOVE EQUALS SIGN 'precnapprox': u'\u2ab9', # ⪹ PRECEDES ABOVE NOT ALMOST EQUAL TO + 'precneqq': u'\u2ab5', # ⪵ PRECEDES ABOVE NOT EQUAL TO 'precnsim': u'\u22e8', # ⋨ PRECEDES BUT NOT EQUIVALENT TO 'precsim': u'\u227e', # ≾ PRECEDES OR EQUIVALENT TO 'propto': u'\u221d', # ∝ PROPORTIONAL TO 'restriction': u'\u21be', # ↾ UPWARDS HARPOON WITH BARB RIGHTWARDS + 'rhd': u'\u22b3', # ⊳ CONTAINS AS NORMAL SUBGROUP 'rightarrow': u'\u2192', # → RIGHTWARDS ARROW 'rightarrowtail': u'\u21a3', # ↣ RIGHTWARDS ARROW WITH TAIL 'rightarrowtriangle': u'\u21fe', # ⇾ RIGHTWARDS OPEN-HEADED ARROW @@ -602,17 +639,18 @@ mathrel = { 'rightrightharpoons': u'\u2964', # ⥤ RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN 'rightslice': u'\u2aa7', # ⪧ GREATER-THAN CLOSED BY CURVE 'rightsquigarrow': u'\u21dd', # ⇝ RIGHTWARDS SQUIGGLE ARROW + 'rightturn': u'\u21bb', # ↻ CLOCKWISE OPEN CIRCLE ARROW 'risingdotseq': u'\u2253', # ≓ IMAGE OF OR APPROXIMATELY EQUAL TO 'searrow': u'\u2198', # ↘ SOUTH EAST ARROW 'sim': u'\u223c', # ∼ TILDE OPERATOR 'simeq': u'\u2243', # ≃ ASYMPTOTICALLY EQUAL TO - 'smallfrown': u'\u2322', # ⌢ FROWN - 'smallsmile': u'\u2323', # ⌣ SMILE 'smile': u'\u2323', # ⌣ SMILE 'sqsubset': u'\u228f', # ⊏ SQUARE IMAGE OF 'sqsubseteq': u'\u2291', # ⊑ SQUARE IMAGE OF OR EQUAL TO 'sqsupset': u'\u2290', # ⊐ SQUARE ORIGINAL OF 'sqsupseteq': u'\u2292', # ⊒ SQUARE ORIGINAL OF OR EQUAL TO + 'strictfi': u'\u297c', # ⥼ LEFT FISH TAIL + 'strictif': u'\u297d', # ⥽ RIGHT FISH TAIL 'subset': u'\u2282', # ⊂ SUBSET OF 'subseteq': u'\u2286', # ⊆ SUBSET OF OR EQUAL TO 'subseteqq': u'\u2ac5', # ⫅ SUBSET OF ABOVE EQUALS SIGN @@ -622,7 +660,9 @@ mathrel = { 'succapprox': u'\u2ab8', # ⪸ SUCCEEDS ABOVE ALMOST EQUAL TO 'succcurlyeq': u'\u227d', # ≽ SUCCEEDS OR EQUAL TO 'succeq': u'\u2ab0', # ⪰ SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN + 'succeqq': u'\u2ab4', # ⪴ SUCCEEDS ABOVE EQUALS SIGN 'succnapprox': u'\u2aba', # ⪺ SUCCEEDS ABOVE NOT ALMOST EQUAL TO + 'succneqq': u'\u2ab6', # ⪶ SUCCEEDS ABOVE NOT EQUAL TO 'succnsim': u'\u22e9', # ⋩ SUCCEEDS BUT NOT EQUIVALENT TO 'succsim': u'\u227f', # ≿ SUCCEEDS OR EQUIVALENT TO 'supset': u'\u2283', # ⊃ SUPERSET OF @@ -631,6 +671,7 @@ mathrel = { 'supsetneq': u'\u228b', # ⊋ SUPERSET OF WITH NOT EQUAL TO 'supsetneqq': u'\u2acc', # ⫌ SUPERSET OF ABOVE NOT EQUAL TO 'swarrow': u'\u2199', # ↙ SOUTH WEST ARROW + 'therefore': u'\u2234', # ∴ THEREFORE 'to': u'\u2192', # → RIGHTWARDS ARROW 'trianglelefteq': u'\u22b4', # ⊴ NORMAL SUBGROUP OF OR EQUAL TO 'triangleq': u'\u225c', # ≜ DELTA EQUAL TO @@ -646,17 +687,20 @@ mathrel = { 'upuparrows': u'\u21c8', # ⇈ UPWARDS PAIRED ARROWS 'upupharpoons': u'\u2963', # ⥣ UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT 'vDash': u'\u22a8', # ⊨ TRUE - 'varpropto': u'\u221d', # ∝ PROPORTIONAL TO + 'vartriangle': u'\u25b5', # ▵ WHITE UP-POINTING SMALL TRIANGLE 'vartriangleleft': u'\u22b2', # ⊲ NORMAL SUBGROUP OF 'vartriangleright': u'\u22b3', # ⊳ CONTAINS AS NORMAL SUBGROUP 'vdash': u'\u22a2', # ⊢ RIGHT TACK - 'vdots': u'\u22ee', # ⋮ VERTICAL ELLIPSIS + 'wasytherefore': u'\u2234', # ∴ THEREFORE } mathunder = { 'underbrace': u'\u23df', # ⏟ BOTTOM CURLY BRACKET } space = { + ' ': u' ', # SPACE + ',': u'\u2006', # SIX-PER-EM SPACE ':': u'\u205f', # MEDIUM MATHEMATICAL SPACE 'medspace': u'\u205f', # MEDIUM MATHEMATICAL SPACE 'quad': u'\u2001', # EM QUAD + 'thinspace': u'\u2006', # SIX-PER-EM SPACE } diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/unichar2tex.py b/docutils/src/main/resources/docutils/docutils/utils/math/unichar2tex.py index 0e0b808..cf077f6 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/unichar2tex.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/unichar2tex.py @@ -6,783 +6,803 @@ # Includes commands from: standard LaTeX, amssymb, amsmath uni2tex_table = { -160: u'~', -163: u'\\pounds ', -165: u'\\yen ', -172: u'\\neg ', -174: u'\\circledR ', -177: u'\\pm ', -215: u'\\times ', -240: u'\\eth ', -247: u'\\div ', -305: u'\\imath ', -567: u'\\jmath ', -915: u'\\Gamma ', -916: u'\\Delta ', -920: u'\\Theta ', -923: u'\\Lambda ', -926: u'\\Xi ', -928: u'\\Pi ', -931: u'\\Sigma ', -933: u'\\Upsilon ', -934: u'\\Phi ', -936: u'\\Psi ', -937: u'\\Omega ', -945: u'\\alpha ', -946: u'\\beta ', -947: u'\\gamma ', -948: u'\\delta ', -949: u'\\varepsilon ', -950: u'\\zeta ', -951: u'\\eta ', -952: u'\\theta ', -953: u'\\iota ', -954: u'\\kappa ', -955: u'\\lambda ', -956: u'\\mu ', -957: u'\\nu ', -958: u'\\xi ', -960: u'\\pi ', -961: u'\\rho ', -962: u'\\varsigma ', -963: u'\\sigma ', -964: u'\\tau ', -965: u'\\upsilon ', -966: u'\\varphi ', -967: u'\\chi ', -968: u'\\psi ', -969: u'\\omega ', -977: u'\\vartheta ', -981: u'\\phi ', -982: u'\\varpi ', -989: u'\\digamma ', -1014: u'\\backepsilon ', -8193: u'\\quad ', -8214: u'\\| ', -8224: u'\\dagger ', -8225: u'\\ddagger ', -8230: u'\\ldots ', -8242: u'\\prime ', -8245: u'\\backprime ', -8287: u'\\: ', -8450: u'\\mathbb{C}', -8459: u'\\mathcal{H}', -8460: u'\\mathfrak{H}', -8461: u'\\mathbb{H}', -8463: u'\\hslash ', -8464: u'\\mathcal{I}', -8465: u'\\Im ', -8466: u'\\mathcal{L}', -8467: u'\\ell ', -8469: u'\\mathbb{N}', -8472: u'\\wp ', -8473: u'\\mathbb{P}', -8474: u'\\mathbb{Q}', -8475: u'\\mathcal{R}', -8476: u'\\Re ', -8477: u'\\mathbb{R}', -8484: u'\\mathbb{Z}', -8487: u'\\mho ', -8488: u'\\mathfrak{Z}', -8492: u'\\mathcal{B}', -8493: u'\\mathfrak{C}', -8496: u'\\mathcal{E}', -8497: u'\\mathcal{F}', -8498: u'\\Finv ', -8499: u'\\mathcal{M}', -8501: u'\\aleph ', -8502: u'\\beth ', -8503: u'\\gimel ', -8504: u'\\daleth ', -8592: u'\\leftarrow ', -8593: u'\\uparrow ', -8594: u'\\rightarrow ', -8595: u'\\downarrow ', -8596: u'\\leftrightarrow ', -8597: u'\\updownarrow ', -8598: u'\\nwarrow ', -8599: u'\\nearrow ', -8600: u'\\searrow ', -8601: u'\\swarrow ', -8602: u'\\nleftarrow ', -8603: u'\\nrightarrow ', -8606: u'\\twoheadleftarrow ', -8608: u'\\twoheadrightarrow ', -8610: u'\\leftarrowtail ', -8611: u'\\rightarrowtail ', -8614: u'\\mapsto ', -8617: u'\\hookleftarrow ', -8618: u'\\hookrightarrow ', -8619: u'\\looparrowleft ', -8620: u'\\looparrowright ', -8621: u'\\leftrightsquigarrow ', -8622: u'\\nleftrightarrow ', -8624: u'\\Lsh ', -8625: u'\\Rsh ', -8630: u'\\curvearrowleft ', -8631: u'\\curvearrowright ', -8634: u'\\circlearrowleft ', -8635: u'\\circlearrowright ', -8636: u'\\leftharpoonup ', -8637: u'\\leftharpoondown ', -8638: u'\\upharpoonright ', -8639: u'\\upharpoonleft ', -8640: u'\\rightharpoonup ', -8641: u'\\rightharpoondown ', -8642: u'\\downharpoonright ', -8643: u'\\downharpoonleft ', -8644: u'\\rightleftarrows ', -8646: u'\\leftrightarrows ', -8647: u'\\leftleftarrows ', -8648: u'\\upuparrows ', -8649: u'\\rightrightarrows ', -8650: u'\\downdownarrows ', -8651: u'\\leftrightharpoons ', -8652: u'\\rightleftharpoons ', -8653: u'\\nLeftarrow ', -8654: u'\\nLeftrightarrow ', -8655: u'\\nRightarrow ', -8656: u'\\Leftarrow ', -8657: u'\\Uparrow ', -8658: u'\\Rightarrow ', -8659: u'\\Downarrow ', -8660: u'\\Leftrightarrow ', -8661: u'\\Updownarrow ', -8666: u'\\Lleftarrow ', -8667: u'\\Rrightarrow ', -8669: u'\\rightsquigarrow ', -8672: u'\\dashleftarrow ', -8674: u'\\dashrightarrow ', -8704: u'\\forall ', -8705: u'\\complement ', -8706: u'\\partial ', -8707: u'\\exists ', -8708: u'\\nexists ', -8709: u'\\varnothing ', -8711: u'\\nabla ', -8712: u'\\in ', -8713: u'\\notin ', -8715: u'\\ni ', -8719: u'\\prod ', -8720: u'\\coprod ', -8721: u'\\sum ', -8722: u'-', -8723: u'\\mp ', -8724: u'\\dotplus ', -8725: u'\\slash ', -8726: u'\\smallsetminus ', -8727: u'\\ast ', -8728: u'\\circ ', -8729: u'\\bullet ', -8730: u'\\sqrt ', -8731: u'\\sqrt[3] ', -8732: u'\\sqrt[4] ', -8733: u'\\propto ', -8734: u'\\infty ', -8736: u'\\angle ', -8737: u'\\measuredangle ', -8738: u'\\sphericalangle ', -8739: u'\\mid ', -8740: u'\\nmid ', -8741: u'\\parallel ', -8742: u'\\nparallel ', -8743: u'\\wedge ', -8744: u'\\vee ', -8745: u'\\cap ', -8746: u'\\cup ', -8747: u'\\int ', -8748: u'\\iint ', -8749: u'\\iiint ', -8750: u'\\oint ', -8756: u'\\therefore ', -8757: u'\\because ', -8758: u':', -8764: u'\\sim ', -8765: u'\\backsim ', -8768: u'\\wr ', -8769: u'\\nsim ', -8770: u'\\eqsim ', -8771: u'\\simeq ', -8773: u'\\cong ', -8775: u'\\ncong ', -8776: u'\\approx ', -8778: u'\\approxeq ', -8781: u'\\asymp ', -8782: u'\\Bumpeq ', -8783: u'\\bumpeq ', -8784: u'\\doteq ', -8785: u'\\Doteq ', -8786: u'\\fallingdotseq ', -8787: u'\\risingdotseq ', -8790: u'\\eqcirc ', -8791: u'\\circeq ', -8796: u'\\triangleq ', -8800: u'\\neq ', -8801: u'\\equiv ', -8804: u'\\leq ', -8805: u'\\geq ', -8806: u'\\leqq ', -8807: u'\\geqq ', -8808: u'\\lneqq ', -8809: u'\\gneqq ', -8810: u'\\ll ', -8811: u'\\gg ', -8812: u'\\between ', -8814: u'\\nless ', -8815: u'\\ngtr ', -8816: u'\\nleq ', -8817: u'\\ngeq ', -8818: u'\\lesssim ', -8819: u'\\gtrsim ', -8822: u'\\lessgtr ', -8823: u'\\gtrless ', -8826: u'\\prec ', -8827: u'\\succ ', -8828: u'\\preccurlyeq ', -8829: u'\\succcurlyeq ', -8830: u'\\precsim ', -8831: u'\\succsim ', -8832: u'\\nprec ', -8833: u'\\nsucc ', -8834: u'\\subset ', -8835: u'\\supset ', -8838: u'\\subseteq ', -8839: u'\\supseteq ', -8840: u'\\nsubseteq ', -8841: u'\\nsupseteq ', -8842: u'\\subsetneq ', -8843: u'\\supsetneq ', -8846: u'\\uplus ', -8847: u'\\sqsubset ', -8848: u'\\sqsupset ', -8849: u'\\sqsubseteq ', -8850: u'\\sqsupseteq ', -8851: u'\\sqcap ', -8852: u'\\sqcup ', -8853: u'\\oplus ', -8854: u'\\ominus ', -8855: u'\\otimes ', -8856: u'\\oslash ', -8857: u'\\odot ', -8858: u'\\circledcirc ', -8859: u'\\circledast ', -8861: u'\\circleddash ', -8862: u'\\boxplus ', -8863: u'\\boxminus ', -8864: u'\\boxtimes ', -8865: u'\\boxdot ', -8866: u'\\vdash ', -8867: u'\\dashv ', -8868: u'\\top ', -8869: u'\\bot ', -8871: u'\\models ', -8872: u'\\vDash ', -8873: u'\\Vdash ', -8874: u'\\Vvdash ', -8876: u'\\nvdash ', -8877: u'\\nvDash ', -8878: u'\\nVdash ', -8879: u'\\nVDash ', -8882: u'\\vartriangleleft ', -8883: u'\\vartriangleright ', -8884: u'\\trianglelefteq ', -8885: u'\\trianglerighteq ', -8888: u'\\multimap ', -8890: u'\\intercal ', -8891: u'\\veebar ', -8892: u'\\barwedge ', -8896: u'\\bigwedge ', -8897: u'\\bigvee ', -8898: u'\\bigcap ', -8899: u'\\bigcup ', -8900: u'\\diamond ', -8901: u'\\cdot ', -8902: u'\\star ', -8903: u'\\divideontimes ', -8904: u'\\bowtie ', -8905: u'\\ltimes ', -8906: u'\\rtimes ', -8907: u'\\leftthreetimes ', -8908: u'\\rightthreetimes ', -8909: u'\\backsimeq ', -8910: u'\\curlyvee ', -8911: u'\\curlywedge ', -8912: u'\\Subset ', -8913: u'\\Supset ', -8914: u'\\Cap ', -8915: u'\\Cup ', -8916: u'\\pitchfork ', -8918: u'\\lessdot ', -8919: u'\\gtrdot ', -8920: u'\\lll ', -8921: u'\\ggg ', -8922: u'\\lesseqgtr ', -8923: u'\\gtreqless ', -8926: u'\\curlyeqprec ', -8927: u'\\curlyeqsucc ', -8928: u'\\npreceq ', -8929: u'\\nsucceq ', -8934: u'\\lnsim ', -8935: u'\\gnsim ', -8936: u'\\precnsim ', -8937: u'\\succnsim ', -8938: u'\\ntriangleleft ', -8939: u'\\ntriangleright ', -8940: u'\\ntrianglelefteq ', -8941: u'\\ntrianglerighteq ', -8942: u'\\vdots ', -8943: u'\\cdots ', -8945: u'\\ddots ', -8968: u'\\lceil ', -8969: u'\\rceil ', -8970: u'\\lfloor ', -8971: u'\\rfloor ', -8988: u'\\ulcorner ', -8989: u'\\urcorner ', -8990: u'\\llcorner ', -8991: u'\\lrcorner ', -8994: u'\\frown ', -8995: u'\\smile ', -9182: u'\\overbrace ', -9183: u'\\underbrace ', -9651: u'\\bigtriangleup ', -9655: u'\\rhd ', -9661: u'\\bigtriangledown ', -9665: u'\\lhd ', -9671: u'\\Diamond ', -9674: u'\\lozenge ', -9723: u'\\square ', -9724: u'\\blacksquare ', -9733: u'\\bigstar ', -9824: u'\\spadesuit ', -9825: u'\\heartsuit ', -9826: u'\\diamondsuit ', -9827: u'\\clubsuit ', -9837: u'\\flat ', -9838: u'\\natural ', -9839: u'\\sharp ', -10003: u'\\checkmark ', -10016: u'\\maltese ', -10178: u'\\perp ', -10216: u'\\langle ', -10217: u'\\rangle ', -10222: u'\\lgroup ', -10223: u'\\rgroup ', -10229: u'\\longleftarrow ', -10230: u'\\longrightarrow ', -10231: u'\\longleftrightarrow ', -10232: u'\\Longleftarrow ', -10233: u'\\Longrightarrow ', -10234: u'\\Longleftrightarrow ', -10236: u'\\longmapsto ', -10731: u'\\blacklozenge ', -10741: u'\\setminus ', -10752: u'\\bigodot ', -10753: u'\\bigoplus ', -10754: u'\\bigotimes ', -10756: u'\\biguplus ', -10758: u'\\bigsqcup ', -10764: u'\\iiiint ', -10781: u'\\Join ', -10815: u'\\amalg ', -10846: u'\\doublebarwedge ', -10877: u'\\leqslant ', -10878: u'\\geqslant ', -10885: u'\\lessapprox ', -10886: u'\\gtrapprox ', -10887: u'\\lneq ', -10888: u'\\gneq ', -10889: u'\\lnapprox ', -10890: u'\\gnapprox ', -10891: u'\\lesseqqgtr ', -10892: u'\\gtreqqless ', -10901: u'\\eqslantless ', -10902: u'\\eqslantgtr ', -10927: u'\\preceq ', -10928: u'\\succeq ', -10935: u'\\precapprox ', -10936: u'\\succapprox ', -10937: u'\\precnapprox ', -10938: u'\\succnapprox ', -10949: u'\\subseteqq ', -10950: u'\\supseteqq ', -10955: u'\\subsetneqq ', -10956: u'\\supsetneqq ', -119808: u'\\mathbf{A}', -119809: u'\\mathbf{B}', -119810: u'\\mathbf{C}', -119811: u'\\mathbf{D}', -119812: u'\\mathbf{E}', -119813: u'\\mathbf{F}', -119814: u'\\mathbf{G}', -119815: u'\\mathbf{H}', -119816: u'\\mathbf{I}', -119817: u'\\mathbf{J}', -119818: u'\\mathbf{K}', -119819: u'\\mathbf{L}', -119820: u'\\mathbf{M}', -119821: u'\\mathbf{N}', -119822: u'\\mathbf{O}', -119823: u'\\mathbf{P}', -119824: u'\\mathbf{Q}', -119825: u'\\mathbf{R}', -119826: u'\\mathbf{S}', -119827: u'\\mathbf{T}', -119828: u'\\mathbf{U}', -119829: u'\\mathbf{V}', -119830: u'\\mathbf{W}', -119831: u'\\mathbf{X}', -119832: u'\\mathbf{Y}', -119833: u'\\mathbf{Z}', -119834: u'\\mathbf{a}', -119835: u'\\mathbf{b}', -119836: u'\\mathbf{c}', -119837: u'\\mathbf{d}', -119838: u'\\mathbf{e}', -119839: u'\\mathbf{f}', -119840: u'\\mathbf{g}', -119841: u'\\mathbf{h}', -119842: u'\\mathbf{i}', -119843: u'\\mathbf{j}', -119844: u'\\mathbf{k}', -119845: u'\\mathbf{l}', -119846: u'\\mathbf{m}', -119847: u'\\mathbf{n}', -119848: u'\\mathbf{o}', -119849: u'\\mathbf{p}', -119850: u'\\mathbf{q}', -119851: u'\\mathbf{r}', -119852: u'\\mathbf{s}', -119853: u'\\mathbf{t}', -119854: u'\\mathbf{u}', -119855: u'\\mathbf{v}', -119856: u'\\mathbf{w}', -119857: u'\\mathbf{x}', -119858: u'\\mathbf{y}', -119859: u'\\mathbf{z}', -119860: u'A', -119861: u'B', -119862: u'C', -119863: u'D', -119864: u'E', -119865: u'F', -119866: u'G', -119867: u'H', -119868: u'I', -119869: u'J', -119870: u'K', -119871: u'L', -119872: u'M', -119873: u'N', -119874: u'O', -119875: u'P', -119876: u'Q', -119877: u'R', -119878: u'S', -119879: u'T', -119880: u'U', -119881: u'V', -119882: u'W', -119883: u'X', -119884: u'Y', -119885: u'Z', -119886: u'a', -119887: u'b', -119888: u'c', -119889: u'd', -119890: u'e', -119891: u'f', -119892: u'g', -119894: u'i', -119895: u'j', -119896: u'k', -119897: u'l', -119898: u'm', -119899: u'n', -119900: u'o', -119901: u'p', -119902: u'q', -119903: u'r', -119904: u's', -119905: u't', -119906: u'u', -119907: u'v', -119908: u'w', -119909: u'x', -119910: u'y', -119911: u'z', -119964: u'\\mathcal{A}', -119966: u'\\mathcal{C}', -119967: u'\\mathcal{D}', -119970: u'\\mathcal{G}', -119973: u'\\mathcal{J}', -119974: u'\\mathcal{K}', -119977: u'\\mathcal{N}', -119978: u'\\mathcal{O}', -119979: u'\\mathcal{P}', -119980: u'\\mathcal{Q}', -119982: u'\\mathcal{S}', -119983: u'\\mathcal{T}', -119984: u'\\mathcal{U}', -119985: u'\\mathcal{V}', -119986: u'\\mathcal{W}', -119987: u'\\mathcal{X}', -119988: u'\\mathcal{Y}', -119989: u'\\mathcal{Z}', -120068: u'\\mathfrak{A}', -120069: u'\\mathfrak{B}', -120071: u'\\mathfrak{D}', -120072: u'\\mathfrak{E}', -120073: u'\\mathfrak{F}', -120074: u'\\mathfrak{G}', -120077: u'\\mathfrak{J}', -120078: u'\\mathfrak{K}', -120079: u'\\mathfrak{L}', -120080: u'\\mathfrak{M}', -120081: u'\\mathfrak{N}', -120082: u'\\mathfrak{O}', -120083: u'\\mathfrak{P}', -120084: u'\\mathfrak{Q}', -120086: u'\\mathfrak{S}', -120087: u'\\mathfrak{T}', -120088: u'\\mathfrak{U}', -120089: u'\\mathfrak{V}', -120090: u'\\mathfrak{W}', -120091: u'\\mathfrak{X}', -120092: u'\\mathfrak{Y}', -120094: u'\\mathfrak{a}', -120095: u'\\mathfrak{b}', -120096: u'\\mathfrak{c}', -120097: u'\\mathfrak{d}', -120098: u'\\mathfrak{e}', -120099: u'\\mathfrak{f}', -120100: u'\\mathfrak{g}', -120101: u'\\mathfrak{h}', -120102: u'\\mathfrak{i}', -120103: u'\\mathfrak{j}', -120104: u'\\mathfrak{k}', -120105: u'\\mathfrak{l}', -120106: u'\\mathfrak{m}', -120107: u'\\mathfrak{n}', -120108: u'\\mathfrak{o}', -120109: u'\\mathfrak{p}', -120110: u'\\mathfrak{q}', -120111: u'\\mathfrak{r}', -120112: u'\\mathfrak{s}', -120113: u'\\mathfrak{t}', -120114: u'\\mathfrak{u}', -120115: u'\\mathfrak{v}', -120116: u'\\mathfrak{w}', -120117: u'\\mathfrak{x}', -120118: u'\\mathfrak{y}', -120119: u'\\mathfrak{z}', -120120: u'\\mathbb{A}', -120121: u'\\mathbb{B}', -120123: u'\\mathbb{D}', -120124: u'\\mathbb{E}', -120125: u'\\mathbb{F}', -120126: u'\\mathbb{G}', -120128: u'\\mathbb{I}', -120129: u'\\mathbb{J}', -120130: u'\\mathbb{K}', -120131: u'\\mathbb{L}', -120132: u'\\mathbb{M}', -120134: u'\\mathbb{O}', -120138: u'\\mathbb{S}', -120139: u'\\mathbb{T}', -120140: u'\\mathbb{U}', -120141: u'\\mathbb{V}', -120142: u'\\mathbb{W}', -120143: u'\\mathbb{X}', -120144: u'\\mathbb{Y}', -120156: u'\\Bbbk ', -120224: u'\\mathsf{A}', -120225: u'\\mathsf{B}', -120226: u'\\mathsf{C}', -120227: u'\\mathsf{D}', -120228: u'\\mathsf{E}', -120229: u'\\mathsf{F}', -120230: u'\\mathsf{G}', -120231: u'\\mathsf{H}', -120232: u'\\mathsf{I}', -120233: u'\\mathsf{J}', -120234: u'\\mathsf{K}', -120235: u'\\mathsf{L}', -120236: u'\\mathsf{M}', -120237: u'\\mathsf{N}', -120238: u'\\mathsf{O}', -120239: u'\\mathsf{P}', -120240: u'\\mathsf{Q}', -120241: u'\\mathsf{R}', -120242: u'\\mathsf{S}', -120243: u'\\mathsf{T}', -120244: u'\\mathsf{U}', -120245: u'\\mathsf{V}', -120246: u'\\mathsf{W}', -120247: u'\\mathsf{X}', -120248: u'\\mathsf{Y}', -120249: u'\\mathsf{Z}', -120250: u'\\mathsf{a}', -120251: u'\\mathsf{b}', -120252: u'\\mathsf{c}', -120253: u'\\mathsf{d}', -120254: u'\\mathsf{e}', -120255: u'\\mathsf{f}', -120256: u'\\mathsf{g}', -120257: u'\\mathsf{h}', -120258: u'\\mathsf{i}', -120259: u'\\mathsf{j}', -120260: u'\\mathsf{k}', -120261: u'\\mathsf{l}', -120262: u'\\mathsf{m}', -120263: u'\\mathsf{n}', -120264: u'\\mathsf{o}', -120265: u'\\mathsf{p}', -120266: u'\\mathsf{q}', -120267: u'\\mathsf{r}', -120268: u'\\mathsf{s}', -120269: u'\\mathsf{t}', -120270: u'\\mathsf{u}', -120271: u'\\mathsf{v}', -120272: u'\\mathsf{w}', -120273: u'\\mathsf{x}', -120274: u'\\mathsf{y}', -120275: u'\\mathsf{z}', -120432: u'\\mathtt{A}', -120433: u'\\mathtt{B}', -120434: u'\\mathtt{C}', -120435: u'\\mathtt{D}', -120436: u'\\mathtt{E}', -120437: u'\\mathtt{F}', -120438: u'\\mathtt{G}', -120439: u'\\mathtt{H}', -120440: u'\\mathtt{I}', -120441: u'\\mathtt{J}', -120442: u'\\mathtt{K}', -120443: u'\\mathtt{L}', -120444: u'\\mathtt{M}', -120445: u'\\mathtt{N}', -120446: u'\\mathtt{O}', -120447: u'\\mathtt{P}', -120448: u'\\mathtt{Q}', -120449: u'\\mathtt{R}', -120450: u'\\mathtt{S}', -120451: u'\\mathtt{T}', -120452: u'\\mathtt{U}', -120453: u'\\mathtt{V}', -120454: u'\\mathtt{W}', -120455: u'\\mathtt{X}', -120456: u'\\mathtt{Y}', -120457: u'\\mathtt{Z}', -120458: u'\\mathtt{a}', -120459: u'\\mathtt{b}', -120460: u'\\mathtt{c}', -120461: u'\\mathtt{d}', -120462: u'\\mathtt{e}', -120463: u'\\mathtt{f}', -120464: u'\\mathtt{g}', -120465: u'\\mathtt{h}', -120466: u'\\mathtt{i}', -120467: u'\\mathtt{j}', -120468: u'\\mathtt{k}', -120469: u'\\mathtt{l}', -120470: u'\\mathtt{m}', -120471: u'\\mathtt{n}', -120472: u'\\mathtt{o}', -120473: u'\\mathtt{p}', -120474: u'\\mathtt{q}', -120475: u'\\mathtt{r}', -120476: u'\\mathtt{s}', -120477: u'\\mathtt{t}', -120478: u'\\mathtt{u}', -120479: u'\\mathtt{v}', -120480: u'\\mathtt{w}', -120481: u'\\mathtt{x}', -120482: u'\\mathtt{y}', -120483: u'\\mathtt{z}', -120484: u'\\imath ', -120485: u'\\jmath ', -120490: u'\\mathbf{\\Gamma}', -120491: u'\\mathbf{\\Delta}', -120495: u'\\mathbf{\\Theta}', -120498: u'\\mathbf{\\Lambda}', -120501: u'\\mathbf{\\Xi}', -120503: u'\\mathbf{\\Pi}', -120506: u'\\mathbf{\\Sigma}', -120508: u'\\mathbf{\\Upsilon}', -120509: u'\\mathbf{\\Phi}', -120511: u'\\mathbf{\\Psi}', -120512: u'\\mathbf{\\Omega}', -120548: u'\\mathit{\\Gamma}', -120549: u'\\mathit{\\Delta}', -120553: u'\\mathit{\\Theta}', -120556: u'\\mathit{\\Lambda}', -120559: u'\\mathit{\\Xi}', -120561: u'\\mathit{\\Pi}', -120564: u'\\mathit{\\Sigma}', -120566: u'\\mathit{\\Upsilon}', -120567: u'\\mathit{\\Phi}', -120569: u'\\mathit{\\Psi}', -120570: u'\\mathit{\\Omega}', -120572: u'\\alpha ', -120573: u'\\beta ', -120574: u'\\gamma ', -120575: u'\\delta ', -120576: u'\\varepsilon ', -120577: u'\\zeta ', -120578: u'\\eta ', -120579: u'\\theta ', -120580: u'\\iota ', -120581: u'\\kappa ', -120582: u'\\lambda ', -120583: u'\\mu ', -120584: u'\\nu ', -120585: u'\\xi ', -120587: u'\\pi ', -120588: u'\\rho ', -120589: u'\\varsigma ', -120590: u'\\sigma ', -120591: u'\\tau ', -120592: u'\\upsilon ', -120593: u'\\varphi ', -120594: u'\\chi ', -120595: u'\\psi ', -120596: u'\\omega ', -120597: u'\\partial ', -120598: u'\\epsilon ', -120599: u'\\vartheta ', -120600: u'\\varkappa ', -120601: u'\\phi ', -120602: u'\\varrho ', -120603: u'\\varpi ', -120782: u'\\mathbf{0}', -120783: u'\\mathbf{1}', -120784: u'\\mathbf{2}', -120785: u'\\mathbf{3}', -120786: u'\\mathbf{4}', -120787: u'\\mathbf{5}', -120788: u'\\mathbf{6}', -120789: u'\\mathbf{7}', -120790: u'\\mathbf{8}', -120791: u'\\mathbf{9}', -120802: u'\\mathsf{0}', -120803: u'\\mathsf{1}', -120804: u'\\mathsf{2}', -120805: u'\\mathsf{3}', -120806: u'\\mathsf{4}', -120807: u'\\mathsf{5}', -120808: u'\\mathsf{6}', -120809: u'\\mathsf{7}', -120810: u'\\mathsf{8}', -120811: u'\\mathsf{9}', -120822: u'\\mathtt{0}', -120823: u'\\mathtt{1}', -120824: u'\\mathtt{2}', -120825: u'\\mathtt{3}', -120826: u'\\mathtt{4}', -120827: u'\\mathtt{5}', -120828: u'\\mathtt{6}', -120829: u'\\mathtt{7}', -120830: u'\\mathtt{8}', -120831: u'\\mathtt{9}', +0xa0: u'~', +0xa3: u'\\pounds ', +0xa5: u'\\yen ', +0xa7: u'\\S ', +0xac: u'\\neg ', +0xb1: u'\\pm ', +0xb6: u'\\P ', +0xd7: u'\\times ', +0xf0: u'\\eth ', +0xf7: u'\\div ', +0x131: u'\\imath ', +0x237: u'\\jmath ', +0x393: u'\\Gamma ', +0x394: u'\\Delta ', +0x398: u'\\Theta ', +0x39b: u'\\Lambda ', +0x39e: u'\\Xi ', +0x3a0: u'\\Pi ', +0x3a3: u'\\Sigma ', +0x3a5: u'\\Upsilon ', +0x3a6: u'\\Phi ', +0x3a8: u'\\Psi ', +0x3a9: u'\\Omega ', +0x3b1: u'\\alpha ', +0x3b2: u'\\beta ', +0x3b3: u'\\gamma ', +0x3b4: u'\\delta ', +0x3b5: u'\\varepsilon ', +0x3b6: u'\\zeta ', +0x3b7: u'\\eta ', +0x3b8: u'\\theta ', +0x3b9: u'\\iota ', +0x3ba: u'\\kappa ', +0x3bb: u'\\lambda ', +0x3bc: u'\\mu ', +0x3bd: u'\\nu ', +0x3be: u'\\xi ', +0x3c0: u'\\pi ', +0x3c1: u'\\rho ', +0x3c2: u'\\varsigma ', +0x3c3: u'\\sigma ', +0x3c4: u'\\tau ', +0x3c5: u'\\upsilon ', +0x3c6: u'\\varphi ', +0x3c7: u'\\chi ', +0x3c8: u'\\psi ', +0x3c9: u'\\omega ', +0x3d1: u'\\vartheta ', +0x3d5: u'\\phi ', +0x3d6: u'\\varpi ', +0x3dd: u'\\digamma ', +0x3f0: u'\\varkappa ', +0x3f1: u'\\varrho ', +0x3f5: u'\\epsilon ', +0x3f6: u'\\backepsilon ', +0x2001: u'\\quad ', +0x2003: u'\\quad ', +0x2006: u'\\, ', +0x2016: u'\\| ', +0x2020: u'\\dagger ', +0x2021: u'\\ddagger ', +0x2022: u'\\bullet ', +0x2026: u'\\ldots ', +0x2032: u'\\prime ', +0x2035: u'\\backprime ', +0x205f: u'\\: ', +0x2102: u'\\mathbb{C}', +0x210b: u'\\mathcal{H}', +0x210c: u'\\mathfrak{H}', +0x210d: u'\\mathbb{H}', +0x210f: u'\\hslash ', +0x2110: u'\\mathcal{I}', +0x2111: u'\\Im ', +0x2112: u'\\mathcal{L}', +0x2113: u'\\ell ', +0x2115: u'\\mathbb{N}', +0x2118: u'\\wp ', +0x2119: u'\\mathbb{P}', +0x211a: u'\\mathbb{Q}', +0x211b: u'\\mathcal{R}', +0x211c: u'\\Re ', +0x211d: u'\\mathbb{R}', +0x2124: u'\\mathbb{Z}', +0x2127: u'\\mho ', +0x2128: u'\\mathfrak{Z}', +0x212c: u'\\mathcal{B}', +0x212d: u'\\mathfrak{C}', +0x2130: u'\\mathcal{E}', +0x2131: u'\\mathcal{F}', +0x2132: u'\\Finv ', +0x2133: u'\\mathcal{M}', +0x2135: u'\\aleph ', +0x2136: u'\\beth ', +0x2137: u'\\gimel ', +0x2138: u'\\daleth ', +0x2190: u'\\leftarrow ', +0x2191: u'\\uparrow ', +0x2192: u'\\rightarrow ', +0x2193: u'\\downarrow ', +0x2194: u'\\leftrightarrow ', +0x2195: u'\\updownarrow ', +0x2196: u'\\nwarrow ', +0x2197: u'\\nearrow ', +0x2198: u'\\searrow ', +0x2199: u'\\swarrow ', +0x219a: u'\\nleftarrow ', +0x219b: u'\\nrightarrow ', +0x219e: u'\\twoheadleftarrow ', +0x21a0: u'\\twoheadrightarrow ', +0x21a2: u'\\leftarrowtail ', +0x21a3: u'\\rightarrowtail ', +0x21a6: u'\\mapsto ', +0x21a9: u'\\hookleftarrow ', +0x21aa: u'\\hookrightarrow ', +0x21ab: u'\\looparrowleft ', +0x21ac: u'\\looparrowright ', +0x21ad: u'\\leftrightsquigarrow ', +0x21ae: u'\\nleftrightarrow ', +0x21b0: u'\\Lsh ', +0x21b1: u'\\Rsh ', +0x21b6: u'\\curvearrowleft ', +0x21b7: u'\\curvearrowright ', +0x21ba: u'\\circlearrowleft ', +0x21bb: u'\\circlearrowright ', +0x21bc: u'\\leftharpoonup ', +0x21bd: u'\\leftharpoondown ', +0x21be: u'\\upharpoonright ', +0x21bf: u'\\upharpoonleft ', +0x21c0: u'\\rightharpoonup ', +0x21c1: u'\\rightharpoondown ', +0x21c2: u'\\downharpoonright ', +0x21c3: u'\\downharpoonleft ', +0x21c4: u'\\rightleftarrows ', +0x21c6: u'\\leftrightarrows ', +0x21c7: u'\\leftleftarrows ', +0x21c8: u'\\upuparrows ', +0x21c9: u'\\rightrightarrows ', +0x21ca: u'\\downdownarrows ', +0x21cb: u'\\leftrightharpoons ', +0x21cc: u'\\rightleftharpoons ', +0x21cd: u'\\nLeftarrow ', +0x21ce: u'\\nLeftrightarrow ', +0x21cf: u'\\nRightarrow ', +0x21d0: u'\\Leftarrow ', +0x21d1: u'\\Uparrow ', +0x21d2: u'\\Rightarrow ', +0x21d3: u'\\Downarrow ', +0x21d4: u'\\Leftrightarrow ', +0x21d5: u'\\Updownarrow ', +0x21da: u'\\Lleftarrow ', +0x21db: u'\\Rrightarrow ', +0x21dd: u'\\rightsquigarrow ', +0x21e0: u'\\dashleftarrow ', +0x21e2: u'\\dashrightarrow ', +0x2200: u'\\forall ', +0x2201: u'\\complement ', +0x2202: u'\\partial ', +0x2203: u'\\exists ', +0x2204: u'\\nexists ', +0x2205: u'\\emptyset ', +0x2207: u'\\nabla ', +0x2208: u'\\in ', +0x2209: u'\\notin ', +0x220b: u'\\ni ', +0x220f: u'\\prod ', +0x2210: u'\\coprod ', +0x2211: u'\\sum ', +0x2212: u'-', +0x2213: u'\\mp ', +0x2214: u'\\dotplus ', +0x2215: u'\\slash ', +0x2216: u'\\smallsetminus ', +0x2217: u'\\ast ', +0x2218: u'\\circ ', +0x2219: u'\\bullet ', +0x221a: u'\\surd ', +0x221b: u'\\sqrt[3] ', +0x221c: u'\\sqrt[4] ', +0x221d: u'\\propto ', +0x221e: u'\\infty ', +0x2220: u'\\angle ', +0x2221: u'\\measuredangle ', +0x2222: u'\\sphericalangle ', +0x2223: u'\\mid ', +0x2224: u'\\nmid ', +0x2225: u'\\parallel ', +0x2226: u'\\nparallel ', +0x2227: u'\\wedge ', +0x2228: u'\\vee ', +0x2229: u'\\cap ', +0x222a: u'\\cup ', +0x222b: u'\\int ', +0x222c: u'\\iint ', +0x222d: u'\\iiint ', +0x222e: u'\\oint ', +0x2234: u'\\therefore ', +0x2235: u'\\because ', +0x2236: u':', +0x223c: u'\\sim ', +0x223d: u'\\backsim ', +0x2240: u'\\wr ', +0x2241: u'\\nsim ', +0x2242: u'\\eqsim ', +0x2243: u'\\simeq ', +0x2245: u'\\cong ', +0x2247: u'\\ncong ', +0x2248: u'\\approx ', +0x224a: u'\\approxeq ', +0x224d: u'\\asymp ', +0x224e: u'\\Bumpeq ', +0x224f: u'\\bumpeq ', +0x2250: u'\\doteq ', +0x2251: u'\\Doteq ', +0x2252: u'\\fallingdotseq ', +0x2253: u'\\risingdotseq ', +0x2256: u'\\eqcirc ', +0x2257: u'\\circeq ', +0x225c: u'\\triangleq ', +0x2260: u'\\neq ', +0x2261: u'\\equiv ', +0x2264: u'\\leq ', +0x2265: u'\\geq ', +0x2266: u'\\leqq ', +0x2267: u'\\geqq ', +0x2268: u'\\lneqq ', +0x2269: u'\\gneqq ', +0x226a: u'\\ll ', +0x226b: u'\\gg ', +0x226c: u'\\between ', +0x226e: u'\\nless ', +0x226f: u'\\ngtr ', +0x2270: u'\\nleq ', +0x2271: u'\\ngeq ', +0x2272: u'\\lesssim ', +0x2273: u'\\gtrsim ', +0x2276: u'\\lessgtr ', +0x2277: u'\\gtrless ', +0x227a: u'\\prec ', +0x227b: u'\\succ ', +0x227c: u'\\preccurlyeq ', +0x227d: u'\\succcurlyeq ', +0x227e: u'\\precsim ', +0x227f: u'\\succsim ', +0x2280: u'\\nprec ', +0x2281: u'\\nsucc ', +0x2282: u'\\subset ', +0x2283: u'\\supset ', +0x2286: u'\\subseteq ', +0x2287: u'\\supseteq ', +0x2288: u'\\nsubseteq ', +0x2289: u'\\nsupseteq ', +0x228a: u'\\subsetneq ', +0x228b: u'\\supsetneq ', +0x228e: u'\\uplus ', +0x228f: u'\\sqsubset ', +0x2290: u'\\sqsupset ', +0x2291: u'\\sqsubseteq ', +0x2292: u'\\sqsupseteq ', +0x2293: u'\\sqcap ', +0x2294: u'\\sqcup ', +0x2295: u'\\oplus ', +0x2296: u'\\ominus ', +0x2297: u'\\otimes ', +0x2298: u'\\oslash ', +0x2299: u'\\odot ', +0x229a: u'\\circledcirc ', +0x229b: u'\\circledast ', +0x229d: u'\\circleddash ', +0x229e: u'\\boxplus ', +0x229f: u'\\boxminus ', +0x22a0: u'\\boxtimes ', +0x22a1: u'\\boxdot ', +0x22a2: u'\\vdash ', +0x22a3: u'\\dashv ', +0x22a4: u'\\top ', +0x22a5: u'\\bot ', +0x22a7: u'\\models ', +0x22a8: u'\\vDash ', +0x22a9: u'\\Vdash ', +0x22aa: u'\\Vvdash ', +0x22ac: u'\\nvdash ', +0x22ad: u'\\nvDash ', +0x22ae: u'\\nVdash ', +0x22af: u'\\nVDash ', +0x22b2: u'\\vartriangleleft ', +0x22b3: u'\\vartriangleright ', +0x22b4: u'\\trianglelefteq ', +0x22b5: u'\\trianglerighteq ', +0x22b8: u'\\multimap ', +0x22ba: u'\\intercal ', +0x22bb: u'\\veebar ', +0x22bc: u'\\barwedge ', +0x22c0: u'\\bigwedge ', +0x22c1: u'\\bigvee ', +0x22c2: u'\\bigcap ', +0x22c3: u'\\bigcup ', +0x22c4: u'\\diamond ', +0x22c5: u'\\cdot ', +0x22c6: u'\\star ', +0x22c7: u'\\divideontimes ', +0x22c8: u'\\bowtie ', +0x22c9: u'\\ltimes ', +0x22ca: u'\\rtimes ', +0x22cb: u'\\leftthreetimes ', +0x22cc: u'\\rightthreetimes ', +0x22cd: u'\\backsimeq ', +0x22ce: u'\\curlyvee ', +0x22cf: u'\\curlywedge ', +0x22d0: u'\\Subset ', +0x22d1: u'\\Supset ', +0x22d2: u'\\Cap ', +0x22d3: u'\\Cup ', +0x22d4: u'\\pitchfork ', +0x22d6: u'\\lessdot ', +0x22d7: u'\\gtrdot ', +0x22d8: u'\\lll ', +0x22d9: u'\\ggg ', +0x22da: u'\\lesseqgtr ', +0x22db: u'\\gtreqless ', +0x22de: u'\\curlyeqprec ', +0x22df: u'\\curlyeqsucc ', +0x22e0: u'\\npreceq ', +0x22e1: u'\\nsucceq ', +0x22e6: u'\\lnsim ', +0x22e7: u'\\gnsim ', +0x22e8: u'\\precnsim ', +0x22e9: u'\\succnsim ', +0x22ea: u'\\ntriangleleft ', +0x22eb: u'\\ntriangleright ', +0x22ec: u'\\ntrianglelefteq ', +0x22ed: u'\\ntrianglerighteq ', +0x22ee: u'\\vdots ', +0x22ef: u'\\cdots ', +0x22f1: u'\\ddots ', +0x2308: u'\\lceil ', +0x2309: u'\\rceil ', +0x230a: u'\\lfloor ', +0x230b: u'\\rfloor ', +0x231c: u'\\ulcorner ', +0x231d: u'\\urcorner ', +0x231e: u'\\llcorner ', +0x231f: u'\\lrcorner ', +0x2322: u'\\frown ', +0x2323: u'\\smile ', +0x23aa: u'\\bracevert ', +0x23b0: u'\\lmoustache ', +0x23b1: u'\\rmoustache ', +0x23d0: u'\\arrowvert ', +0x23de: u'\\overbrace ', +0x23df: u'\\underbrace ', +0x24c7: u'\\circledR ', +0x24c8: u'\\circledS ', +0x25b2: u'\\blacktriangle ', +0x25b3: u'\\bigtriangleup ', +0x25b7: u'\\triangleright ', +0x25bc: u'\\blacktriangledown ', +0x25bd: u'\\bigtriangledown ', +0x25c1: u'\\triangleleft ', +0x25c7: u'\\Diamond ', +0x25ca: u'\\lozenge ', +0x25ef: u'\\bigcirc ', +0x25fb: u'\\square ', +0x25fc: u'\\blacksquare ', +0x2605: u'\\bigstar ', +0x2660: u'\\spadesuit ', +0x2661: u'\\heartsuit ', +0x2662: u'\\diamondsuit ', +0x2663: u'\\clubsuit ', +0x266d: u'\\flat ', +0x266e: u'\\natural ', +0x266f: u'\\sharp ', +0x2713: u'\\checkmark ', +0x2720: u'\\maltese ', +0x27c2: u'\\perp ', +0x27cb: u'\\diagup ', +0x27cd: u'\\diagdown ', +0x27e8: u'\\langle ', +0x27e9: u'\\rangle ', +0x27ee: u'\\lgroup ', +0x27ef: u'\\rgroup ', +0x27f5: u'\\longleftarrow ', +0x27f6: u'\\longrightarrow ', +0x27f7: u'\\longleftrightarrow ', +0x27f8: u'\\Longleftarrow ', +0x27f9: u'\\Longrightarrow ', +0x27fa: u'\\Longleftrightarrow ', +0x27fc: u'\\longmapsto ', +0x29eb: u'\\blacklozenge ', +0x29f5: u'\\setminus ', +0x2a00: u'\\bigodot ', +0x2a01: u'\\bigoplus ', +0x2a02: u'\\bigotimes ', +0x2a04: u'\\biguplus ', +0x2a06: u'\\bigsqcup ', +0x2a0c: u'\\iiiint ', +0x2a3f: u'\\amalg ', +0x2a5e: u'\\doublebarwedge ', +0x2a7d: u'\\leqslant ', +0x2a7e: u'\\geqslant ', +0x2a85: u'\\lessapprox ', +0x2a86: u'\\gtrapprox ', +0x2a87: u'\\lneq ', +0x2a88: u'\\gneq ', +0x2a89: u'\\lnapprox ', +0x2a8a: u'\\gnapprox ', +0x2a8b: u'\\lesseqqgtr ', +0x2a8c: u'\\gtreqqless ', +0x2a95: u'\\eqslantless ', +0x2a96: u'\\eqslantgtr ', +0x2aaf: u'\\preceq ', +0x2ab0: u'\\succeq ', +0x2ab5: u'\\precneqq ', +0x2ab6: u'\\succneqq ', +0x2ab7: u'\\precapprox ', +0x2ab8: u'\\succapprox ', +0x2ab9: u'\\precnapprox ', +0x2aba: u'\\succnapprox ', +0x2ac5: u'\\subseteqq ', +0x2ac6: u'\\supseteqq ', +0x2acb: u'\\subsetneqq ', +0x2acc: u'\\supsetneqq ', +0x2b1c: u'\\Box ', +0x1d400: u'\\mathbf{A}', +0x1d401: u'\\mathbf{B}', +0x1d402: u'\\mathbf{C}', +0x1d403: u'\\mathbf{D}', +0x1d404: u'\\mathbf{E}', +0x1d405: u'\\mathbf{F}', +0x1d406: u'\\mathbf{G}', +0x1d407: u'\\mathbf{H}', +0x1d408: u'\\mathbf{I}', +0x1d409: u'\\mathbf{J}', +0x1d40a: u'\\mathbf{K}', +0x1d40b: u'\\mathbf{L}', +0x1d40c: u'\\mathbf{M}', +0x1d40d: u'\\mathbf{N}', +0x1d40e: u'\\mathbf{O}', +0x1d40f: u'\\mathbf{P}', +0x1d410: u'\\mathbf{Q}', +0x1d411: u'\\mathbf{R}', +0x1d412: u'\\mathbf{S}', +0x1d413: u'\\mathbf{T}', +0x1d414: u'\\mathbf{U}', +0x1d415: u'\\mathbf{V}', +0x1d416: u'\\mathbf{W}', +0x1d417: u'\\mathbf{X}', +0x1d418: u'\\mathbf{Y}', +0x1d419: u'\\mathbf{Z}', +0x1d41a: u'\\mathbf{a}', +0x1d41b: u'\\mathbf{b}', +0x1d41c: u'\\mathbf{c}', +0x1d41d: u'\\mathbf{d}', +0x1d41e: u'\\mathbf{e}', +0x1d41f: u'\\mathbf{f}', +0x1d420: u'\\mathbf{g}', +0x1d421: u'\\mathbf{h}', +0x1d422: u'\\mathbf{i}', +0x1d423: u'\\mathbf{j}', +0x1d424: u'\\mathbf{k}', +0x1d425: u'\\mathbf{l}', +0x1d426: u'\\mathbf{m}', +0x1d427: u'\\mathbf{n}', +0x1d428: u'\\mathbf{o}', +0x1d429: u'\\mathbf{p}', +0x1d42a: u'\\mathbf{q}', +0x1d42b: u'\\mathbf{r}', +0x1d42c: u'\\mathbf{s}', +0x1d42d: u'\\mathbf{t}', +0x1d42e: u'\\mathbf{u}', +0x1d42f: u'\\mathbf{v}', +0x1d430: u'\\mathbf{w}', +0x1d431: u'\\mathbf{x}', +0x1d432: u'\\mathbf{y}', +0x1d433: u'\\mathbf{z}', +0x1d434: u'A', +0x1d435: u'B', +0x1d436: u'C', +0x1d437: u'D', +0x1d438: u'E', +0x1d439: u'F', +0x1d43a: u'G', +0x1d43b: u'H', +0x1d43c: u'I', +0x1d43d: u'J', +0x1d43e: u'K', +0x1d43f: u'L', +0x1d440: u'M', +0x1d441: u'N', +0x1d442: u'O', +0x1d443: u'P', +0x1d444: u'Q', +0x1d445: u'R', +0x1d446: u'S', +0x1d447: u'T', +0x1d448: u'U', +0x1d449: u'V', +0x1d44a: u'W', +0x1d44b: u'X', +0x1d44c: u'Y', +0x1d44d: u'Z', +0x1d44e: u'a', +0x1d44f: u'b', +0x1d450: u'c', +0x1d451: u'd', +0x1d452: u'e', +0x1d453: u'f', +0x1d454: u'g', +0x1d456: u'i', +0x1d457: u'j', +0x1d458: u'k', +0x1d459: u'l', +0x1d45a: u'm', +0x1d45b: u'n', +0x1d45c: u'o', +0x1d45d: u'p', +0x1d45e: u'q', +0x1d45f: u'r', +0x1d460: u's', +0x1d461: u't', +0x1d462: u'u', +0x1d463: u'v', +0x1d464: u'w', +0x1d465: u'x', +0x1d466: u'y', +0x1d467: u'z', +0x1d49c: u'\\mathcal{A}', +0x1d49e: u'\\mathcal{C}', +0x1d49f: u'\\mathcal{D}', +0x1d4a2: u'\\mathcal{G}', +0x1d4a5: u'\\mathcal{J}', +0x1d4a6: u'\\mathcal{K}', +0x1d4a9: u'\\mathcal{N}', +0x1d4aa: u'\\mathcal{O}', +0x1d4ab: u'\\mathcal{P}', +0x1d4ac: u'\\mathcal{Q}', +0x1d4ae: u'\\mathcal{S}', +0x1d4af: u'\\mathcal{T}', +0x1d4b0: u'\\mathcal{U}', +0x1d4b1: u'\\mathcal{V}', +0x1d4b2: u'\\mathcal{W}', +0x1d4b3: u'\\mathcal{X}', +0x1d4b4: u'\\mathcal{Y}', +0x1d4b5: u'\\mathcal{Z}', +0x1d504: u'\\mathfrak{A}', +0x1d505: u'\\mathfrak{B}', +0x1d507: u'\\mathfrak{D}', +0x1d508: u'\\mathfrak{E}', +0x1d509: u'\\mathfrak{F}', +0x1d50a: u'\\mathfrak{G}', +0x1d50d: u'\\mathfrak{J}', +0x1d50e: u'\\mathfrak{K}', +0x1d50f: u'\\mathfrak{L}', +0x1d510: u'\\mathfrak{M}', +0x1d511: u'\\mathfrak{N}', +0x1d512: u'\\mathfrak{O}', +0x1d513: u'\\mathfrak{P}', +0x1d514: u'\\mathfrak{Q}', +0x1d516: u'\\mathfrak{S}', +0x1d517: u'\\mathfrak{T}', +0x1d518: u'\\mathfrak{U}', +0x1d519: u'\\mathfrak{V}', +0x1d51a: u'\\mathfrak{W}', +0x1d51b: u'\\mathfrak{X}', +0x1d51c: u'\\mathfrak{Y}', +0x1d51e: u'\\mathfrak{a}', +0x1d51f: u'\\mathfrak{b}', +0x1d520: u'\\mathfrak{c}', +0x1d521: u'\\mathfrak{d}', +0x1d522: u'\\mathfrak{e}', +0x1d523: u'\\mathfrak{f}', +0x1d524: u'\\mathfrak{g}', +0x1d525: u'\\mathfrak{h}', +0x1d526: u'\\mathfrak{i}', +0x1d527: u'\\mathfrak{j}', +0x1d528: u'\\mathfrak{k}', +0x1d529: u'\\mathfrak{l}', +0x1d52a: u'\\mathfrak{m}', +0x1d52b: u'\\mathfrak{n}', +0x1d52c: u'\\mathfrak{o}', +0x1d52d: u'\\mathfrak{p}', +0x1d52e: u'\\mathfrak{q}', +0x1d52f: u'\\mathfrak{r}', +0x1d530: u'\\mathfrak{s}', +0x1d531: u'\\mathfrak{t}', +0x1d532: u'\\mathfrak{u}', +0x1d533: u'\\mathfrak{v}', +0x1d534: u'\\mathfrak{w}', +0x1d535: u'\\mathfrak{x}', +0x1d536: u'\\mathfrak{y}', +0x1d537: u'\\mathfrak{z}', +0x1d538: u'\\mathbb{A}', +0x1d539: u'\\mathbb{B}', +0x1d53b: u'\\mathbb{D}', +0x1d53c: u'\\mathbb{E}', +0x1d53d: u'\\mathbb{F}', +0x1d53e: u'\\mathbb{G}', +0x1d540: u'\\mathbb{I}', +0x1d541: u'\\mathbb{J}', +0x1d542: u'\\mathbb{K}', +0x1d543: u'\\mathbb{L}', +0x1d544: u'\\mathbb{M}', +0x1d546: u'\\mathbb{O}', +0x1d54a: u'\\mathbb{S}', +0x1d54b: u'\\mathbb{T}', +0x1d54c: u'\\mathbb{U}', +0x1d54d: u'\\mathbb{V}', +0x1d54e: u'\\mathbb{W}', +0x1d54f: u'\\mathbb{X}', +0x1d550: u'\\mathbb{Y}', +0x1d55c: u'\\Bbbk ', +0x1d5a0: u'\\mathsf{A}', +0x1d5a1: u'\\mathsf{B}', +0x1d5a2: u'\\mathsf{C}', +0x1d5a3: u'\\mathsf{D}', +0x1d5a4: u'\\mathsf{E}', +0x1d5a5: u'\\mathsf{F}', +0x1d5a6: u'\\mathsf{G}', +0x1d5a7: u'\\mathsf{H}', +0x1d5a8: u'\\mathsf{I}', +0x1d5a9: u'\\mathsf{J}', +0x1d5aa: u'\\mathsf{K}', +0x1d5ab: u'\\mathsf{L}', +0x1d5ac: u'\\mathsf{M}', +0x1d5ad: u'\\mathsf{N}', +0x1d5ae: u'\\mathsf{O}', +0x1d5af: u'\\mathsf{P}', +0x1d5b0: u'\\mathsf{Q}', +0x1d5b1: u'\\mathsf{R}', +0x1d5b2: u'\\mathsf{S}', +0x1d5b3: u'\\mathsf{T}', +0x1d5b4: u'\\mathsf{U}', +0x1d5b5: u'\\mathsf{V}', +0x1d5b6: u'\\mathsf{W}', +0x1d5b7: u'\\mathsf{X}', +0x1d5b8: u'\\mathsf{Y}', +0x1d5b9: u'\\mathsf{Z}', +0x1d5ba: u'\\mathsf{a}', +0x1d5bb: u'\\mathsf{b}', +0x1d5bc: u'\\mathsf{c}', +0x1d5bd: u'\\mathsf{d}', +0x1d5be: u'\\mathsf{e}', +0x1d5bf: u'\\mathsf{f}', +0x1d5c0: u'\\mathsf{g}', +0x1d5c1: u'\\mathsf{h}', +0x1d5c2: u'\\mathsf{i}', +0x1d5c3: u'\\mathsf{j}', +0x1d5c4: u'\\mathsf{k}', +0x1d5c5: u'\\mathsf{l}', +0x1d5c6: u'\\mathsf{m}', +0x1d5c7: u'\\mathsf{n}', +0x1d5c8: u'\\mathsf{o}', +0x1d5c9: u'\\mathsf{p}', +0x1d5ca: u'\\mathsf{q}', +0x1d5cb: u'\\mathsf{r}', +0x1d5cc: u'\\mathsf{s}', +0x1d5cd: u'\\mathsf{t}', +0x1d5ce: u'\\mathsf{u}', +0x1d5cf: u'\\mathsf{v}', +0x1d5d0: u'\\mathsf{w}', +0x1d5d1: u'\\mathsf{x}', +0x1d5d2: u'\\mathsf{y}', +0x1d5d3: u'\\mathsf{z}', +0x1d670: u'\\mathtt{A}', +0x1d671: u'\\mathtt{B}', +0x1d672: u'\\mathtt{C}', +0x1d673: u'\\mathtt{D}', +0x1d674: u'\\mathtt{E}', +0x1d675: u'\\mathtt{F}', +0x1d676: u'\\mathtt{G}', +0x1d677: u'\\mathtt{H}', +0x1d678: u'\\mathtt{I}', +0x1d679: u'\\mathtt{J}', +0x1d67a: u'\\mathtt{K}', +0x1d67b: u'\\mathtt{L}', +0x1d67c: u'\\mathtt{M}', +0x1d67d: u'\\mathtt{N}', +0x1d67e: u'\\mathtt{O}', +0x1d67f: u'\\mathtt{P}', +0x1d680: u'\\mathtt{Q}', +0x1d681: u'\\mathtt{R}', +0x1d682: u'\\mathtt{S}', +0x1d683: u'\\mathtt{T}', +0x1d684: u'\\mathtt{U}', +0x1d685: u'\\mathtt{V}', +0x1d686: u'\\mathtt{W}', +0x1d687: u'\\mathtt{X}', +0x1d688: u'\\mathtt{Y}', +0x1d689: u'\\mathtt{Z}', +0x1d68a: u'\\mathtt{a}', +0x1d68b: u'\\mathtt{b}', +0x1d68c: u'\\mathtt{c}', +0x1d68d: u'\\mathtt{d}', +0x1d68e: u'\\mathtt{e}', +0x1d68f: u'\\mathtt{f}', +0x1d690: u'\\mathtt{g}', +0x1d691: u'\\mathtt{h}', +0x1d692: u'\\mathtt{i}', +0x1d693: u'\\mathtt{j}', +0x1d694: u'\\mathtt{k}', +0x1d695: u'\\mathtt{l}', +0x1d696: u'\\mathtt{m}', +0x1d697: u'\\mathtt{n}', +0x1d698: u'\\mathtt{o}', +0x1d699: u'\\mathtt{p}', +0x1d69a: u'\\mathtt{q}', +0x1d69b: u'\\mathtt{r}', +0x1d69c: u'\\mathtt{s}', +0x1d69d: u'\\mathtt{t}', +0x1d69e: u'\\mathtt{u}', +0x1d69f: u'\\mathtt{v}', +0x1d6a0: u'\\mathtt{w}', +0x1d6a1: u'\\mathtt{x}', +0x1d6a2: u'\\mathtt{y}', +0x1d6a3: u'\\mathtt{z}', +0x1d6a4: u'\\imath ', +0x1d6a5: u'\\jmath ', +0x1d6aa: u'\\mathbf{\\Gamma}', +0x1d6ab: u'\\mathbf{\\Delta}', +0x1d6af: u'\\mathbf{\\Theta}', +0x1d6b2: u'\\mathbf{\\Lambda}', +0x1d6b5: u'\\mathbf{\\Xi}', +0x1d6b7: u'\\mathbf{\\Pi}', +0x1d6ba: u'\\mathbf{\\Sigma}', +0x1d6bc: u'\\mathbf{\\Upsilon}', +0x1d6bd: u'\\mathbf{\\Phi}', +0x1d6bf: u'\\mathbf{\\Psi}', +0x1d6c0: u'\\mathbf{\\Omega}', +0x1d6e4: u'\\mathit{\\Gamma}', +0x1d6e5: u'\\mathit{\\Delta}', +0x1d6e9: u'\\mathit{\\Theta}', +0x1d6ec: u'\\mathit{\\Lambda}', +0x1d6ef: u'\\mathit{\\Xi}', +0x1d6f1: u'\\mathit{\\Pi}', +0x1d6f4: u'\\mathit{\\Sigma}', +0x1d6f6: u'\\mathit{\\Upsilon}', +0x1d6f7: u'\\mathit{\\Phi}', +0x1d6f9: u'\\mathit{\\Psi}', +0x1d6fa: u'\\mathit{\\Omega}', +0x1d6fc: u'\\alpha ', +0x1d6fd: u'\\beta ', +0x1d6fe: u'\\gamma ', +0x1d6ff: u'\\delta ', +0x1d700: u'\\varepsilon ', +0x1d701: u'\\zeta ', +0x1d702: u'\\eta ', +0x1d703: u'\\theta ', +0x1d704: u'\\iota ', +0x1d705: u'\\kappa ', +0x1d706: u'\\lambda ', +0x1d707: u'\\mu ', +0x1d708: u'\\nu ', +0x1d709: u'\\xi ', +0x1d70b: u'\\pi ', +0x1d70c: u'\\rho ', +0x1d70d: u'\\varsigma ', +0x1d70e: u'\\sigma ', +0x1d70f: u'\\tau ', +0x1d710: u'\\upsilon ', +0x1d711: u'\\varphi ', +0x1d712: u'\\chi ', +0x1d713: u'\\psi ', +0x1d714: u'\\omega ', +0x1d715: u'\\partial ', +0x1d716: u'\\epsilon ', +0x1d717: u'\\vartheta ', +0x1d718: u'\\varkappa ', +0x1d719: u'\\phi ', +0x1d71a: u'\\varrho ', +0x1d71b: u'\\varpi ', +0x1d7ce: u'\\mathbf{0}', +0x1d7cf: u'\\mathbf{1}', +0x1d7d0: u'\\mathbf{2}', +0x1d7d1: u'\\mathbf{3}', +0x1d7d2: u'\\mathbf{4}', +0x1d7d3: u'\\mathbf{5}', +0x1d7d4: u'\\mathbf{6}', +0x1d7d5: u'\\mathbf{7}', +0x1d7d6: u'\\mathbf{8}', +0x1d7d7: u'\\mathbf{9}', +0x1d7e2: u'\\mathsf{0}', +0x1d7e3: u'\\mathsf{1}', +0x1d7e4: u'\\mathsf{2}', +0x1d7e5: u'\\mathsf{3}', +0x1d7e6: u'\\mathsf{4}', +0x1d7e7: u'\\mathsf{5}', +0x1d7e8: u'\\mathsf{6}', +0x1d7e9: u'\\mathsf{7}', +0x1d7ea: u'\\mathsf{8}', +0x1d7eb: u'\\mathsf{9}', +0x1d7f6: u'\\mathtt{0}', +0x1d7f7: u'\\mathtt{1}', +0x1d7f8: u'\\mathtt{2}', +0x1d7f9: u'\\mathtt{3}', +0x1d7fa: u'\\mathtt{4}', +0x1d7fb: u'\\mathtt{5}', +0x1d7fc: u'\\mathtt{6}', +0x1d7fd: u'\\mathtt{7}', +0x1d7fe: u'\\mathtt{8}', +0x1d7ff: u'\\mathtt{9}', } diff --git a/docutils/src/main/resources/docutils/docutils/utils/punctuation_chars.py b/docutils/src/main/resources/docutils/docutils/utils/punctuation_chars.py index 041cf9c..c87bbef 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/punctuation_chars.py +++ b/docutils/src/main/resources/docutils/docutils/utils/punctuation_chars.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# :Id: $Id: punctuation_chars.py 8016 2017-01-17 15:06:17Z milde $ +# :Id: $Id: punctuation_chars.py 8554 2020-09-04 16:52:11Z milde $ # :Copyright: © 2011, 2017 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -9,7 +9,7 @@ # 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 +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause # # This file is generated by # ``docutils/tools/dev/generate_punctuation_chars.py``. diff --git a/docutils/src/main/resources/docutils/docutils/utils/roman.py b/docutils/src/main/resources/docutils/docutils/utils/roman.py index 0335f29..dce927f 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/roman.py +++ b/docutils/src/main/resources/docutils/docutils/utils/roman.py @@ -40,9 +40,9 @@ romanNumeralMap = (('M', 1000), def toRoman(n): """convert integer to Roman numeral""" if not (0 < n < 5000): - raise OutOfRangeError, "number out of range (must be 1..4999)" + raise OutOfRangeError("number out of range (must be 1..4999)") if int(n) != n: - raise NotIntegerError, "decimals can not be converted" + raise NotIntegerError("decimals can not be converted") result = "" for numeral, integer in romanNumeralMap: @@ -62,14 +62,15 @@ romanNumeralPattern = re.compile(""" (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) + """, re.VERBOSE) def fromRoman(s): """convert Roman numeral to integer""" if not s: - raise InvalidRomanNumeralError, 'Input can not be blank' + raise InvalidRomanNumeralError('Input can not be blank') + if not romanNumeralPattern.search(s): - raise InvalidRomanNumeralError, 'Invalid Roman numeral: %s' % s + raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) result = 0 index = 0 diff --git a/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py b/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py index f7425ef..32d0055 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py +++ b/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py @@ -1,7 +1,7 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# :Id: $Id: smartquotes.py 8095 2017-05-30 21:04:18Z milde $ +# :Id: $Id: smartquotes.py 8860 2021-10-22 16:39:59Z milde $ # :Copyright: © 2010 Günter Milde, # original `SmartyPants`_: © 2003 John Gruber # smartypants.py: © 2004, 2007 Chad Miller @@ -13,7 +13,7 @@ # notices 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 +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause r""" @@ -133,7 +133,7 @@ smartypants.py license (2-Clause BSD license): .. _Pyblosxom: http://pyblosxom.bluesock.org/ .. _SmartyPants: http://daringfireball.net/projects/smartypants/ .. _Movable Type: http://www.movabletype.org/ -.. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause +.. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause .. _Docutils: http://docutils.sf.net/ Description @@ -228,22 +228,22 @@ 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 -single-quote, when in fact it should be the `right single quotation mark` +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 use the proper character for closing single-quotes (’) by hand. +best to inset the `RIGHT SINGLE QUOTATION MARK` (’) by hand. -In English, the same character is used for apostrophe and closing single +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" single closing quotes differ from the curly apostrophe. +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 single quote by the algorithm. Therefore, a text like:: +from a secondary quote by the algorithm. Therefore, a text like:: .. class:: language-de-CH @@ -251,7 +251,7 @@ from a single quote by the algorithm. Therefore, a text like:: will get a single closing guillemet instead of an apostrophe. -This can be prevented by use use of the curly apostrophe character (’) in +This can be prevented by use use of the `RIGHT SINGLE QUOTATION MARK` in the source:: - "Er sagt: 'Ich fass' es nicht.'" @@ -261,6 +261,10 @@ the source:: Version History =============== +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. @@ -311,6 +315,7 @@ Version History 1.5_1.0: Tue, 09 Mar 2004 08:08:35 -0500 - Initial release """ +from __future__ import print_function options = r""" Options @@ -400,11 +405,7 @@ class smartchars(object): # [9] Typografisk håndbok. Oslo: Spartacus. 2000. s. 67. ISBN 8243001530. # [10] http://www.typografi.org/sitat/sitatart.html # - # TODO: configuration option, e.g.:: - # - # smartquote-locales: nl: „“’’, # apostrophe for ``'s Gravenhage`` - # nr: se, # alias - # fr: « : »:‹ : ›, # :-separated list with NBSPs + # See also configuration option "smartquote-locales". quotes = {'af': u'“”‘’', 'af-x-altquot': u'„”‚’', 'bg': u'„“‚‘', # Bulgarian, https://bg.wikipedia.org/wiki/Кавички @@ -440,7 +441,7 @@ class smartchars(object): 'hr': u'„”‘’', # http://hrvatska-tipografija.com/polunavodnici/ 'hr-x-altquot': u'»«›‹', 'hsb': u'„“‚‘', - 'hsb-x-altquot':u'»«›‹', + 'hsb-x-altquot': u'»«›‹', 'hu': u'„”«»', 'is': u'„“‚‘', 'it': u'«»“”', @@ -448,6 +449,7 @@ class smartchars(object): 'it-x-altquot': u'“”‘’', # 'it-x-altquot2': u'“„‘‚', # [7] in headlines 'ja': u'「」『』', + 'ko': u'“”‘’', 'lt': u'„“‚‘', 'lv': u'„“‚‘', 'mk': u'„“‚‘', # Macedonian, https://mk.wikipedia.org/wiki/Правопис_и_правоговор_на_македонскиот_јазик @@ -569,7 +571,7 @@ def educate_tokens(text_tokens, attr=default_smartypants_attr, language='en'): for (ttype, text) in text_tokens: - # skip HTML and/or XML tags as well as emtpy 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 @@ -606,9 +608,9 @@ def educate_tokens(text_tokens, attr=default_smartypants_attr, language='en'): text = educateSingleBackticks(text, language) if do_quotes: - # Replace plain quotes in context to prevent converstion to + # Replace plain quotes in context to prevent conversion to # 2-character sequence in French. - context = prev_token_last_char.replace('"',';').replace("'",';') + context = prev_token_last_char.replace('"', ';').replace("'", ';') text = educateQuotes(context+text, language)[1:] if do_stupefy: @@ -634,98 +636,83 @@ def educateQuotes(text, language='en'): """ smart = smartchars(language) - - # oldtext = text - punct_class = r"""[!"#\$\%'()*+,-.\/:;<=>?\@\[\\\]\^_`{|}~]""" + ch_classes = {'open': u'[([{]', # opening braces + 'close': r'[^\s]', # everything except whitespace + 'punct': r"""[-!"#\$\%'()*+,.\/:;<=>?\@\[\\\]\^_`{|}~]""", + 'dash': u'[-–—]' # hyphen and em/en dashes + + r'|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];', + 'sep': u'[\\s\u200B\u200C]| ', # Whitespace, ZWSP, ZWNJ + } # Special case if the very first character is a quote - # followed by punctuation at a non-word-break. - # Close the quotes by brute force: - text = re.sub(r"""^'(?=%s\\B)""" % (punct_class,), smart.csquote, text) - text = re.sub(r"""^"(?=%s\\B)""" % (punct_class,), smart.cpquote, text) + # followed by punctuation at a non-word-break. Use closing quotes. + # TODO: example (when does this match?) + text = re.sub(r"^'(?=%s\\B)" % ch_classes['punct'], smart.csquote, text) + text = re.sub(r'^"(?=%s\\B)' % ch_classes['punct'], smart.cpquote, text) - # Special case for double sets of quotes, e.g.: - # <p>He said, "'Quoted' words in a larger quote."</p> + # Special case for adjacent quotes + # like "'Quoted' words in a larger quote." text = re.sub(r""""'(?=\w)""", smart.opquote+smart.osquote, text) text = re.sub(r"""'"(?=\w)""", smart.osquote+smart.opquote, text) + # Special case: "opening character" followed by quote, + # optional punctuation and space like "[", '(', or '-'. + text = re.sub(r"(%(open)s|%(dash)s)'(?=%(punct)s? )" % ch_classes, + r'\1%s'%smart.csquote, text) + text = re.sub(r'(%(open)s|%(dash)s)"(?=%(punct)s? )' % ch_classes, + r'\1%s'%smart.cpquote, text) + # Special case for decade abbreviations (the '80s): if language.startswith('en'): # TODO similar cases in other languages? - text = re.sub(r"""'(?=\d{2}s)""", smart.apostrophe, text, re.UNICODE) - - close_class = r"""[^\ \t\r\n\[\{\(\-]""" - dec_dashes = r"""–|—""" - - # Get most opening single quotes: - opening_single_quotes_regex = re.compile(r""" - ( - \s | # a whitespace char, or - | # a non-breaking space entity, or - -- | # dashes, or - &[mn]dash; | # named dash entities - %s | # or decimal entities - &\#x201[34]; # or hex + text = re.sub(r"'(?=\d{2}s)", smart.apostrophe, text) + + # Get most opening secondary quotes: + opening_secondary_quotes_regex = re.compile(u""" + (# ?<= # 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) # followed by a word character - """ % (dec_dashes,), re.VERBOSE | re.UNICODE) - text = opening_single_quotes_regex.sub(r'\1'+smart.osquote, text) + (?=\\w|%(punct)s) # followed by a word character or punctuation + """ % ch_classes, re.VERBOSE | re.UNICODE) - # In many locales, single closing quotes are different from apostrophe: + text = opening_secondary_quotes_regex.sub(r'\1'+smart.osquote, text) + + # In many locales, secondary closing quotes are different from apostrophe: if smart.csquote != smart.apostrophe: apostrophe_regex = re.compile(r"(?<=(\w|\d))'(?=\w)", re.UNICODE) text = apostrophe_regex.sub(smart.apostrophe, text) # TODO: keep track of quoting level to recognize apostrophe in, e.g., # "Ich fass' es nicht." - closing_single_quotes_regex = re.compile(r""" - (%s) - ' - (?!\s | # whitespace - s\b | - \d # digits ('80s) - ) - """ % (close_class,), re.VERBOSE | re.UNICODE) - text = closing_single_quotes_regex.sub(r'\1'+smart.csquote, text) - - closing_single_quotes_regex = re.compile(r""" - (%s) - ' - (\s | s\b) - """ % (close_class,), re.VERBOSE | re.UNICODE) - text = closing_single_quotes_regex.sub(r'\1%s\2' % smart.csquote, text) + closing_secondary_quotes_regex = re.compile(r"(?<!\s)'", re.UNICODE) + text = closing_secondary_quotes_regex.sub(smart.csquote, text) - # Any remaining single quotes should be opening ones: + # Any remaining secondary quotes should be opening ones: text = re.sub(r"""'""", smart.osquote, text) - # Get most opening double quotes: - opening_double_quotes_regex = re.compile(r""" + # Get most opening primary quotes: + opening_primary_quotes_regex = re.compile(u""" ( - \s | # a whitespace char, or - | # a non-breaking space entity, or - -- | # dashes, or - &[mn]dash; | # named dash entities - %s | # or decimal entities - &\#x201[34]; # or hex + %(sep)s | # a whitespace char, or + %(open)s | # zero width separating char, or + %(dash)s # em/en-dash ) " # the quote - (?=\w) # followed by a word character - """ % (dec_dashes,), re.VERBOSE) - text = opening_double_quotes_regex.sub(r'\1'+smart.opquote, text) - - # Double closing quotes: - closing_double_quotes_regex = re.compile(r""" - #(%s)? # character that indicates the quote should be closing - " - (?=\s) - """ % (close_class,), re.VERBOSE) - text = closing_double_quotes_regex.sub(smart.cpquote, text) - - closing_double_quotes_regex = re.compile(r""" - (%s) # character that indicates the quote should be closing - " - """ % (close_class,), re.VERBOSE) - text = closing_double_quotes_regex.sub(r'\1'+smart.cpquote, text) + (?=\\w|%(punct)s) # followed by a word character or punctuation + """ % ch_classes, re.VERBOSE | re.UNICODE) + + text = opening_primary_quotes_regex.sub(r'\1'+smart.opquote, text) + + # primary closing quotes: + closing_primary_quotes_regex = re.compile(r""" + ( + (?<!\s)" | # no whitespace before + "(?=\s) # whitespace behind + ) + """, re.VERBOSE | re.UNICODE) + text = closing_primary_quotes_regex.sub(smart.cpquote, text) # Any remaining quotes should be opening ones. text = re.sub(r'"', smart.opquote, text) @@ -835,16 +822,16 @@ def stupefyEntities(text, language='en'): """ smart = smartchars(language) - text = re.sub(smart.endash, "-", text) # en-dash - text = re.sub(smart.emdash, "--", text) # em-dash + text = re.sub(smart.endash, "-", text) # en-dash + text = re.sub(smart.emdash, "--", text) # em-dash - text = re.sub(smart.osquote, "'", text) # open single quote - text = re.sub(smart.csquote, "'", text) # close single quote + text = re.sub(smart.osquote, "'", text) # open secondary quote + text = re.sub(smart.csquote, "'", text) # close secondary quote - text = re.sub(smart.opquote, '"', text) # open double quote - text = re.sub(smart.cpquote, '"', text) # close double quote + text = re.sub(smart.opquote, '"', text) # open primary quote + text = re.sub(smart.cpquote, '"', text) # close primary quote - text = re.sub(smart.ellipsis, '...', text)# ellipsis + text = re.sub(smart.ellipsis, '...', text) # ellipsis return text @@ -934,7 +921,7 @@ if __name__ == "__main__": defaultlanguage = 'en' # Normalize and drop unsupported subtags: - defaultlanguage = defaultlanguage.lower().replace('-','_') + 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('_')] @@ -962,7 +949,8 @@ if __name__ == "__main__": parser.add_argument("-e", "--encoding", default="utf8", help="text encoding") parser.add_argument("-l", "--language", default=defaultlanguage, - help="text language (BCP47 tag), Default: %s"%defaultlanguage) + help="text language (BCP47 tag), " + "Default: %s"% defaultlanguage) parser.add_argument("-q", "--alternative-quotes", action="store_true", help="use alternative quote style") parser.add_argument("--doc", action="store_true", @@ -976,16 +964,16 @@ if __name__ == "__main__": args = parser.parse_args() if args.doc: - print (__doc__) + print(__doc__) elif args.actionhelp: - print options + print(options) elif args.stylehelp: - print - print "Available styles (primary open/close, secondary open/close)" - print "language tag quotes" - print "============ ======" + 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]) + print("%-14s %s" % (key, smartchars.quotes[key])) elif args.test: # Unit test output goes to stderr. import unittest @@ -1018,5 +1006,5 @@ if __name__ == "__main__": else: args.language += '-x-altquot' text = sys.stdin.read().decode(args.encoding) - print smartyPants(text, attr=args.action, - language=args.language).encode(args.encoding) + print(smartyPants(text, attr=args.action, + language=args.language).encode(args.encoding)) diff --git a/docutils/src/main/resources/docutils/docutils/utils/urischemes.py b/docutils/src/main/resources/docutils/docutils/utils/urischemes.py index 253bc5f..d9309ed 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/urischemes.py +++ b/docutils/src/main/resources/docutils/docutils/utils/urischemes.py @@ -1,4 +1,4 @@ -# $Id: urischemes.py 7922 2015-09-22 15:28:09Z milde $ +# $Id: urischemes.py 8376 2019-08-27 19:49:29Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -33,7 +33,7 @@ schemes = { 'specialized to justify their own schemes'), 'fax': ('a connection to a terminal that can handle telefaxes ' '(facsimiles); RFC 2806'), - 'feed' : 'NetNewsWire feed', + 'feed': 'NetNewsWire feed', 'file': 'Host-specific file names; RFC 1738', 'finger': '', 'freenet': '', @@ -60,7 +60,7 @@ schemes = { '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.', + 'iseek': 'See www.ambrosiasw.com; a little util for OS X.', 'jar': 'Java archive', 'javascript': ('JavaScript code; evaluates the expression after the ' 'colon'), @@ -89,7 +89,7 @@ schemes = { 'pres': 'Presence; RFC 3859', 'printer': '', 'prospero': 'Prospero Directory Service; RFC 4157', - 'rdar' : ('URLs found in Darwin source ' + 'rdar': ('URLs found in Darwin source ' '(http://www.opensource.apple.com/darwinsource/).'), 'res': '', 'rtsp': 'real time streaming protocol; RFC 2326', diff --git a/docutils/src/main/resources/docutils/docutils/writers/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/__init__.py index 3208c8a..2b4d0d0 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7969 2016-08-18 21:40:00Z milde $ +# $Id: __init__.py 8673 2021-04-07 17:57:27Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -10,13 +10,11 @@ __docformat__ = 'reStructuredText' import os.path import sys +from importlib import import_module import docutils from docutils import languages, Component from docutils.transforms import universal -if sys.version_info < (2,5): - from docutils._compat import __import__ - class Writer(Component): @@ -122,15 +120,22 @@ class UnfilteredWriter(Writer): _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', - 's5': 's5_html', - 'xelatex': 'xetex', - 'xhtml': 'html5_polyglot', - 'xhtml10': 'html4css1', 'xml': 'docutils_xml'} def get_writer_class(writer_name): @@ -139,7 +144,10 @@ def get_writer_class(writer_name): if writer_name in _writer_aliases: writer_name = _writer_aliases[writer_name] try: - module = __import__(writer_name, globals(), locals(), level=1) + module = import_module('docutils.writers.'+writer_name) except ImportError: - module = __import__(writer_name, globals(), locals(), level=0) + try: + module = import_module(writer_name) + except ImportError as err: + raise ImportError('No writer named "%s".' % writer_name) return module.Writer diff --git a/docutils/src/main/resources/docutils/docutils/writers/_html_base.py b/docutils/src/main/resources/docutils/docutils/writers/_html_base.py index f92ddc1..7215e82 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/_html_base.py +++ b/docutils/src/main/resources/docutils/docutils/writers/_html_base.py @@ -3,7 +3,7 @@ # :Author: David Goodger, Günter Milde # Based on the html4css1 writer by David Goodger. # :Maintainer: docutils-develop@lists.sourceforge.net -# :Revision: $Revision: 8118 $ +# :Revision: $Revision: 8891 $ # :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: @@ -13,48 +13,134 @@ # 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 +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause """common definitions for Docutils HTML writers""" -import sys -import os.path +import base64 +import mimetypes +import os, os.path import re -import urllib - -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(object): pass # dummy wrapper - PIL.Image = Image - except ImportError: - PIL = None +import sys +import warnings import docutils -from docutils import nodes, utils, writers, languages, io +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.utils.error_reporting import SafeString from docutils.transforms import writer_aux from docutils.utils.math import (unichar2tex, pick_math_environment, math2html, latex2mathml, tex2mathml_extern) +if sys.version_info >= (3, 0): + from urllib.request import url2pathname +else: + from urllib import url2pathname + +if sys.version_info >= (3, 0): + unicode = str # noqa + class Writer(writers.Writer): supported = ('html', 'xhtml') # update in subclass """Formats this writer supports.""" - # default_stylesheets = [] # set in subclass! - # default_stylesheet_dirs = ['.'] # set in subclass! - default_template = 'template.txt' - # default_template_path = ... # set in subclass! - # settings_spec = ... # set in subclass! + 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': 1, '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'}), + ('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'} - # config_section = ... # set in subclass! - config_section_dependencies = ['writers', 'html writers'] + config_section = 'html writers' + config_section_dependencies = ('writers', ) visitor_attributes = ( 'head_prefix', 'head', 'stylesheet', 'body_prefix', @@ -159,7 +245,7 @@ class HTMLTranslator(nodes.NodeVisitor): 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') + content_type = '<meta charset="%s"/>\n' generator = ('<meta name="generator" content="Docutils %s: ' 'http://docutils.sourceforge.net/" />\n') @@ -183,7 +269,7 @@ class HTMLTranslator(nodes.NodeVisitor): 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'\S+| +|\n') + words_and_spaces = re.compile(r'[^ \n]+| +|\n') # wrap point inside word: in_word_wrap_point = re.compile(r'.+\W\W.+|[-?].+', re.U) lang_attribute = 'lang' # name changes to 'xml:lang' in XHTML 1.1 @@ -224,7 +310,21 @@ class HTMLTranslator(nodes.NodeVisitor): self.body_suffix = ['</body>\n</html>\n'] self.section_level = 0 self.initial_header_level = int(settings.initial_header_level) - + # image_loading only defined for HTML5 writer + self.image_loading = getattr(settings, 'image_loading', None) + # legacy setting embed_images: + if getattr(settings, 'embed_images', None) is True: + warnings.warn('The configuration setting "embed_images" ' + 'will be removed in Docutils 1.2. Use "image_loading: embed".', + FutureWarning, stacklevel=8) + if self.image_loading == None: + self.image_loading = 'embed' + if getattr(settings, 'embed_images', None) is False: + warnings.warn('The configuration setting "embed_images" ' + 'will be removed in Docutils 1.2. Use "image_loading: link".', + FutureWarning, stacklevel=8) + if self.image_loading == None: + self.image_loading = 'link' # default self.math_output = settings.math_output.split() self.math_output_options = self.math_output[1:] self.math_output = self.math_output[0].lower() @@ -235,14 +335,12 @@ class HTMLTranslator(nodes.NodeVisitor): Used by visit_* and depart_* functions in conjunction with the tree traversal. Make sure that the pops correspond to the pushes.""" - self.topic_classes = [] # TODO: replace with self_in_contents 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_footnote_list = False self.title = [] self.subtitle = [] self.header = [] @@ -295,22 +393,24 @@ class HTMLTranslator(nodes.NodeVisitor): encoded = encoded.replace('.', '.') return encoded - def stylesheet_call(self, path): + 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: - content = io.FileInput(source_path=path, - encoding='utf-8').read() + content = docutils.io.FileInput(source_path=path, + encoding='utf-8').read() self.settings.record_dependencies.add(path) - except IOError, err: - msg = u"Cannot embed stylesheet '%s': %s." % ( + except IOError as err: + msg = u"Cannot embed stylesheet '%r': %s." % ( path, SafeString(err.strerror)) self.document.reporter.error(msg) return '<--- %s --->\n' % msg return self.embedded_stylesheet % content # else link to style file: - if self.settings.stylesheet_path: - # adapt path relative to output (cf. config.html#stylesheet-path) + 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) @@ -322,13 +422,12 @@ class HTMLTranslator(nodes.NodeVisitor): tagname = tagname.lower() prefix = [] atts = {} - ids = [] for (name, value) in attributes.items(): atts[name.lower()] = value - classes = [] + classes = atts.pop('classes', []) languages = [] # unify class arguments and move language specification - for cls in node.get('classes', []) + atts.pop('class', '').split() : + 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: @@ -336,13 +435,15 @@ class HTMLTranslator(nodes.NodeVisitor): 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.extend(node.get('ids', [])) - if 'ids' in atts: - ids.extend(atts['ids']) - del atts['ids'] + ids = node.get('ids', []) + ids.extend(atts.pop('ids', [])) if ids: atts['id'] = ids[0] for id in ids[1:]: @@ -362,8 +463,7 @@ class HTMLTranslator(nodes.NodeVisitor): # Non-empty tag. Place the auxiliary <span> tag # *inside* the element, as the first child. suffix += '<span id="%s"></span>' % id - attlist = atts.items() - attlist.sort() + attlist = sorted(atts.items()) parts = [tagname] for name, value in attlist: # value=None was used for boolean attributes without @@ -432,11 +532,10 @@ class HTMLTranslator(nodes.NodeVisitor): self.depart_docinfo_item() def visit_admonition(self, node): - node['classes'].insert(0, 'admonition') - self.body.append(self.starttag(node, 'div')) + self.body.append(self.starttag(node, 'aside', classes=['admonition'])) def depart_admonition(self, node=None): - self.body.append('</div>\n') + self.body.append('</aside>\n') attribution_formats = {'dash': (u'\u2014', ''), 'parentheses': ('(', ')'), @@ -494,8 +593,7 @@ class HTMLTranslator(nodes.NodeVisitor): # the end of this file). def is_compactable(self, node): - # print "is_compactable %s ?" % node.__class__, - # explicite class arguments have precedence + # explicit class arguments have precedence if 'compact' in node['classes']: return True if 'open' in node['classes']: @@ -503,14 +601,12 @@ class HTMLTranslator(nodes.NodeVisitor): # check config setting: if (isinstance(node, (nodes.field_list, nodes.definition_list)) and not self.settings.compact_field_lists): - # print "`compact-field-lists` is False" return False if (isinstance(node, (nodes.enumerated_list, nodes.bullet_list)) and not self.settings.compact_lists): - # print "`compact-lists` is False" return False - # more special cases: - if (self.topic_classes == ['contents']): # TODO: self.in_contents + # Table of Contents: + if 'contents' in node.parent['classes']: return True # check the list items: return self.check_simple_list(node) @@ -535,23 +631,23 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_caption(self, node): self.body.append('</p>\n') - # citations - # --------- - # Use definition list instead of table for bibliographic references. - # Join adjacent citation entries. - + # Use semantic tag and DPub role (HTML4 uses a table) def visit_citation(self, node): - if not self.in_footnote_list: - self.body.append('<dl class="citation">\n') - self.in_footnote_list = True + # 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('</dd>\n') - if not isinstance(node.next_node(descend=False, siblings=True), - nodes.citation): - self.body.append('</dl>\n') - self.in_footnote_list = False + self.body.append('</div>\n') + next_node = node.next_node(descend=False, siblings=True) + if not isinstance(next_node, type(node)): + self.body.append('</div>\n') + # Use DPub role (overwritten in HTML4) def visit_citation_reference(self, node): href = '#' if 'refid' in node: @@ -560,8 +656,9 @@ class HTMLTranslator(nodes.NodeVisitor): 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', '[', CLASS='citation-reference', href=href)) + 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>') @@ -587,8 +684,8 @@ class HTMLTranslator(nodes.NodeVisitor): 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'])): + 'colwidths-grid' not 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')) @@ -607,11 +704,6 @@ class HTMLTranslator(nodes.NodeVisitor): 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') @@ -647,32 +739,36 @@ class HTMLTranslator(nodes.NodeVisitor): pass def visit_definition(self, node): - self.body.append('</dt>\n') - self.body.append(self.starttag(node, 'dd', '')) + if "details" in node.parent.parent['classes']: + self.body.append('</summary>\n') + else: + self.body.append('</dt>\n') + self.body.append(self.starttag(node, 'dd', '')) def depart_definition(self, node): - self.body.append('</dd>\n') + if "details" not in node.parent.parent['classes']: + self.body.append('</dd>\n') def visit_definition_list(self, node): - classes = node.setdefault('classes', []) - if self.is_compactable(node): - classes.append('simple') - self.body.append(self.starttag(node, 'dl')) + if "details" not in node['classes']: + classes = ['simple'] if self.is_compactable(node) else [] + self.body.append(self.starttag(node, 'dl', classes=classes)) def depart_definition_list(self, node): - self.body.append('</dl>\n') + if "details" not in node['classes']: + self.body.append('</dl>\n') + # Use a "details" disclosure element if parent has "class" arg "details". def visit_definition_list_item(self, node): - # pass class arguments, ids and names to definition term: - node.children[0]['classes'] = ( - node.get('classes', []) + node.children[0].get('classes', [])) - node.children[0]['ids'] = ( - node.get('ids', []) + node.children[0].get('ids', [])) - node.children[0]['names'] = ( - node.get('names', []) + node.children[0].get('names', [])) + 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): - pass + if "details" in node.parent['classes']: + self.body.append('</details>\n') def visit_description(self, node): self.body.append(self.starttag(node, 'dd', '')) @@ -681,21 +777,26 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</dd>\n') def visit_docinfo(self, node): - classes = 'docinfo' + self.context.append(len(self.body)) + classes = ['docinfo'] if (self.is_compactable(node)): - classes += ' simple' - self.body.append(self.starttag(node, 'dl', CLASS=classes)) + 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: meta_tag = '<meta name="%s" content="%s" />\n' \ % (name, self.attval(node.astext())) self.add_meta(meta_tag) - self.body.append('<dt class="%s">%s</dt>\n' - % (name, self.language.labels[name])) + self.body.append( + '<dt class="%s">%s<span class="colon">:</span></dt>\n' + % (name, self.language.labels[name])) self.body.append(self.starttag(node, 'dd', '', CLASS=name)) def depart_docinfo_item(self): @@ -703,7 +804,7 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_doctest_block(self, node): self.body.append(self.starttag(node, 'pre', suffix='', - CLASS='code python doctest')) + classes=['code', 'python', 'doctest'])) def depart_doctest_block(self, node): self.body.append('\n</pre>\n') @@ -720,6 +821,9 @@ class HTMLTranslator(nodes.NodeVisitor): self.html_prolog.append(self.doctype) self.meta.insert(0, self.content_type % self.settings.output_encoding) self.head.insert(0, self.content_type % self.settings.output_encoding) + 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) @@ -742,18 +846,16 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</em>') def visit_entry(self, node): - atts = {'class': []} + atts = {'classes': []} if isinstance(node.parent.parent, nodes.thead): - atts['class'].append('head') + atts['classes'].append('head') if node.parent.parent.parent.stubs[node.parent.column]: # "stubs" list is an attribute of the tgroup element - atts['class'].append('stub') - if atts['class']: + atts['classes'].append('stub') + if atts['classes']: tagname = 'th' - atts['class'] = ' '.join(atts['class']) else: tagname = 'td' - del atts['class'] node.parent.column += 1 if 'morerows' in node: atts['rowspan'] = node['morerows'] + 1 @@ -762,33 +864,40 @@ class HTMLTranslator(nodes.NodeVisitor): node.parent.column += node['morecols'] self.body.append(self.starttag(node, tagname, '', **atts)) self.context.append('</%s>\n' % tagname.lower()) - # TODO: why does the html4css1 writer insert an NBSP into empty cells? - # if len(node) == 0: # empty cell - # self.body.append(' ') # no-break space def depart_entry(self, node): self.body.append(self.context.pop()) def visit_enumerated_list(self, node): - atts = {} + atts = {'classes': []} if 'start' in node: atts['start'] = node['start'] if 'enumtype' in node: - atts['class'] = node['enumtype'] + atts['classes'].append(node['enumtype']) if self.is_compactable(node): - atts['class'] = (atts.get('class', '') + ' simple').strip() + 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): - # Keep simple paragraphs in the field_body to enable CSS - # rule to start body on new line if the label is too long - classes = 'field-list' + 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 += ' simple' - self.body.append(self.starttag(node, 'dl', CLASS=classes)) + classes.append('simple') + self.body.append(self.starttag(node, 'dl', **atts)) def depart_field_list(self, node): self.body.append('</dl>\n') @@ -800,17 +909,16 @@ class HTMLTranslator(nodes.NodeVisitor): 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', '', - CLASS=''.join(node.parent['classes']))) + classes=node.parent['classes'])) def depart_field_name(self, node): - self.body.append('</dt>\n') + self.body.append('<span class="colon">:</span></dt>\n') def visit_field_body(self, node): self.body.append(self.starttag(node, 'dd', '', - CLASS=''.join(node.parent['classes']))) + classes=node.parent['classes'])) # prevent misalignment of following content if the field is empty: if not node.children: self.body.append('<p></p>') @@ -829,7 +937,6 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_figure(self, node): self.body.append('</div>\n') - # use HTML 5 <footer> element? def visit_footer(self, node): self.context.append(len(self.body)) @@ -843,31 +950,25 @@ class HTMLTranslator(nodes.NodeVisitor): self.body_suffix[:0] = footer del self.body[start:] - # footnotes - # --------- - # use definition list instead of table for footnote text - - # TODO: use the new HTML5 element <aside>? (Also for footnote text) + # use HTML5 element <aside> with ARIA role "note" for footnote text + # (the html4css1 writer uses a table). def visit_footnote(self, node): - if not self.in_footnote_list: - classes = 'footnote ' + self.settings.footnote_references - self.body.append('<dl class="%s">\n'%classes) - self.in_footnote_list = True + classes = [node.tagname, self.settings.footnote_references] + self.body.append(self.starttag(node, 'aside', classes=classes, + role="note")) def depart_footnote(self, node): - self.body.append('</dd>\n') - if not isinstance(node.next_node(descend=False, siblings=True), - nodes.footnote): - self.body.append('</dl>\n') - self.in_footnote_list = False + self.body.append('</aside>\n') def visit_footnote_reference(self, node): href = '#' + node['refid'] - classes = 'footnote-reference ' + self.settings.footnote_references - self.body.append(self.starttag(node, 'a', '', #suffix, - CLASS=classes, href=href)) + classes = ['footnote-reference', 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: @@ -875,8 +976,7 @@ class HTMLTranslator(nodes.NodeVisitor): if 'sectnum' in node['classes']: # get section number (strip trailing no-break-spaces) sectnum = node.astext().rstrip(u' ') - # print sectnum.encode('utf-8') - self.body.append('<span class="sectnum">%s</span> ' + self.body.append('<span class="sectnum">%s </span>' % self.encode(sectnum)) # Content already processed: raise nodes.SkipNode @@ -896,19 +996,10 @@ class HTMLTranslator(nodes.NodeVisitor): self.header.extend(header) del self.body[start:] - # Image types to place in an <object> element - object_image_types = {'.swf': 'application/x-shockwave-flash'} - 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) + mimetype = mimetypes.guess_type(uri)[0] # image size if 'width' in node: atts['width'] = node['width'] @@ -917,7 +1008,7 @@ class HTMLTranslator(nodes.NodeVisitor): if 'scale' in node: if (PIL and not ('width' in node and 'height' in node) and self.settings.file_insertion_enabled): - imagepath = urllib.url2pathname(uri) + imagepath = url2pathname(uri) try: img = PIL.Image.open( imagepath.encode(sys.getfilesystemencoding())) @@ -957,15 +1048,43 @@ class HTMLTranslator(nodes.NodeVisitor): suffix = '\n' if 'align' in node: atts['class'] = 'align-%s' % node['align'] - if ext in self.object_image_types: + # Embed image file (embedded SVG or data URI): + if self.image_loading == 'embed': + err_msg = '' + if not mimetype: + err_msg = 'unknown MIME type' + if not self.settings.file_insertion_enabled: + err_msg = 'file insertion disabled.' + try: + with open(url2pathname(uri), 'rb') as imagefile: + imagedata = imagefile.read() + except IOError as err: + err_msg = err.strerror + if err_msg: + self.document.reporter.error('Cannot embed image %r: %s' + %(uri, err_msg)) + else: + self.settings.record_dependencies.add( + uri.replace('\\', '/')) + # TODO: insert SVG as-is? + # if mimetype == 'image/svg+xml': + # read/parse, apply arguments, + # insert as <svg ....> ... </svg> # (about 1/3 less data) + data64 = base64.b64encode(imagedata).decode() + uri = u'data:%s;base64,%s' % (mimetype, data64) + elif self.image_loading == 'lazy': + atts['loading'] = 'lazy' + if mimetype == 'application/x-shockwave-flash': + atts['type'] = mimetype # do NOT use an empty tag: incorrect rendering in browsers - self.body.append(self.starttag(node, 'object', suffix, **atts) + - node.get('alt', uri) + '</object>' + suffix) + tag = (self.starttag(node, 'object', '', data=uri, **atts) + + node.get('alt', uri) + '</object>' + suffix) else: - self.body.append(self.emptytag(node, 'img', suffix, **atts)) + atts['alt'] = node.get('alt', node['uri']) + tag = self.emptytag(node, 'img', suffix, src=uri, **atts) + self.body.append(tag) def depart_image(self, node): - # self.body.append(self.context.pop()) pass def visit_inline(self, node): @@ -976,33 +1095,27 @@ class HTMLTranslator(nodes.NodeVisitor): # footnote and citation labels: def visit_label(self, node): - if (isinstance(node.parent, nodes.footnote)): - classes = self.settings.footnote_references - else: - classes = 'brackets' - # pass parent node to get id into starttag: - self.body.append(self.starttag(node.parent, 'dt', '', CLASS='label')) - self.body.append(self.starttag(node, 'span', '', CLASS=classes)) + 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['backrefs'] + backrefs = node.parent.get('backrefs', []) if len(backrefs) == 1: - self.body.append('<a class="fn-backref" href="#%s">' - % backrefs[0]) + 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['backrefs'] - if len(backrefs) == 1: - self.body.append('</a>') - self.body.append('</span>') - if self.settings.footnote_backlinks and len(backrefs) > 1: - # Python 2.4 fails with enumerate(backrefs, 1) - backlinks = ['<a href="#%s">%s</a>' % (ref, i+1) - for (i, ref) in enumerate(backrefs)] - self.body.append('<span class="fn-backref">(%s)</span>' - % ','.join(backlinks)) - self.body.append('</dt>\n<dd>') + 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')) @@ -1033,16 +1146,15 @@ class HTMLTranslator(nodes.NodeVisitor): # inline literal def visit_literal(self, node): # special case: "code" role - classes = node.get('classes', []) + classes = node['classes'] if 'code' in classes: # filter 'code' from class arguments - node['classes'] = [cls for cls in classes if cls != 'code'] + 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() - # 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 @@ -1054,8 +1166,7 @@ class HTMLTranslator(nodes.NodeVisitor): else: self.body.append(self.encode(token)) self.body.append('</span>') - # Content already processed: - raise nodes.SkipNode + raise nodes.SkipNode # content already processed def depart_literal(self, node): # skipped unless literal element is from "code" role: @@ -1063,11 +1174,11 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_literal_block(self, node): self.body.append(self.starttag(node, 'pre', '', CLASS='literal-block')) - if 'code' in node.get('classes', []): + if 'code' in node['classes']: self.body.append('<code>') def depart_literal_block(self, node): - if 'code' in node.get('classes', []): + if 'code' in node['classes']: self.body.append('</code>') self.body.append('</pre>\n') @@ -1128,7 +1239,8 @@ class HTMLTranslator(nodes.NodeVisitor): elif self.math_output == 'html': if self.math_output_options and not self.math_header: self.math_header = [self.stylesheet_call( - utils.find_file_in_dirs(s, self.settings.stylesheet_dirs)) + utils.find_file_in_dirs(s, self.settings.stylesheet_dirs), + adjust_path=True) for s in self.math_output_options[0].split(',')] # TODO: fix display mode in matrices and fractions math2html.DocumentParameters.displaymode = (math_env != '') @@ -1157,7 +1269,7 @@ class HTMLTranslator(nodes.NodeVisitor): 'with math-output "MathML"') except OSError: raise OSError('is "latexmlmath" in your PATH?') - except SyntaxError, err: + except SyntaxError as err: err_node = self.document.reporter.error(err, base_node=node) self.visit_system_message(err_node) self.body.append(self.starttag(node, 'p')) @@ -1188,7 +1300,6 @@ class HTMLTranslator(nodes.NodeVisitor): pass # never reached def visit_math_block(self, node): - # print node.astext().encode('utf8') math_env = pick_math_environment(node.astext()) self.visit_math(node, math_env=math_env) @@ -1261,9 +1372,9 @@ class HTMLTranslator(nodes.NodeVisitor): # # The HTML4CSS1 writer does this to "produce # visually compact lists (less vertical whitespace)". This writer - # relies on CSS rules for"visual compactness". + # relies on CSS rules for visual compactness. # - # * In XHTML 1.1, e.g. a <blockquote> element may not contain + # * 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 @@ -1294,12 +1405,15 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_raw(self, node): if 'html' in node.get('format', '').split(): - t = isinstance(node.parent, nodes.TextElement) and 'span' or 'div' + if isinstance(node.parent, nodes.TextElement): + tagname = 'span' + else: + tagname = 'div' if node['classes']: - self.body.append(self.starttag(node, t, suffix='')) + self.body.append(self.starttag(node, tagname, suffix='')) self.body.append(node.astext()) if node['classes']: - self.body.append('</%s>' % t) + self.body.append('</%s>' % tagname) # Keep non-HTML raw text out of output: raise nodes.SkipNode @@ -1317,6 +1431,8 @@ class HTMLTranslator(nodes.NodeVisitor): 'References must have "refuri" or "refid" attribute.' atts['href'] = '#' + node['refid'] atts['class'] += ' internal' + if len(node) == 1 and isinstance(node[0], nodes.image): + atts['class'] += ' image-reference' if not isinstance(node.parent, nodes.TextElement): assert len(node) == 1 and isinstance(node[0], nodes.image) atts['class'] += ' image-reference' @@ -1347,7 +1463,6 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_rubric(self, node): self.body.append('</p>\n') - # TODO: use the new HTML 5 element <section>? def visit_section(self, node): self.section_level += 1 self.body.append( @@ -1357,7 +1472,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.section_level -= 1 self.body.append('</div>\n') - # TODO: use the new HTML5 element <aside>? (Also for footnote text) + # TODO: use the new HTML5 element <aside> def visit_sidebar(self, node): self.body.append( self.starttag(node, 'div', CLASS='sidebar')) @@ -1395,20 +1510,20 @@ class HTMLTranslator(nodes.NodeVisitor): # 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/html/sections.html#headings-and-sections + # -- 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' + classes = ['sidebar-subtitle'] elif isinstance(node.parent, nodes.document): - classes = 'subtitle' - self.in_document_title = len(self.body) + 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', '', CLASS=classes)) + classes = ['section-subtitle'] + self.body.append(self.starttag(node, 'p', '', classes=classes)) def depart_subtitle(self, node): self.body.append('</p>\n') - if self.in_document_title: + 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) @@ -1422,7 +1537,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</sup>') def visit_system_message(self, node): - self.body.append(self.starttag(node, 'div', CLASS='system-message')) + self.body.append(self.starttag(node, 'aside', CLASS='system-message')) self.body.append('<p class="system-message-title">') backref_text = '' if len(node['backrefs']): @@ -1448,18 +1563,15 @@ class HTMLTranslator(nodes.NodeVisitor): self.encode(node['source']), line, backref_text)) def depart_system_message(self, node): - self.body.append('</div>\n') - - # tables - # ------ - # no hard-coded border setting in the table head:: + self.body.append('</aside>\n') def visit_table(self, node): - classes = [cls.strip(u' \t\n') - for cls in self.settings.table_style.split(',')] + atts = {'classes': self.settings.table_style.replace(',', ' ').split()} if 'align' in node: - classes.append('align-%s' % node['align']) - tag = self.starttag(node, 'table', CLASS=' '.join(classes)) + 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): @@ -1484,13 +1596,17 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</tbody>\n') def visit_term(self, node): - self.body.append(self.starttag(node, 'dt', '')) + if "details" in node.parent.parent['classes']: + self.body.append(self.starttag(node, 'summary', '')) + else: + # The parent node (definition_list_item) is omitted in HTML. + self.body.append(self.starttag(node, 'dt', '', + classes=node.parent['classes'], + ids=node.parent['ids'])) def depart_term(self, node): - """ - Leave the end tag to `self.visit_definition()`, in case there's a - classifier. - """ + # Leave the end tag to `self.visit_definition()`, + # in case there's a classifier. pass def visit_tgroup(self, node): @@ -1506,16 +1622,35 @@ class HTMLTranslator(nodes.NodeVisitor): 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): - """Only 6 section levels are supported by HTML.""" - check_id = 0 # TODO: is this a bool (False) or a counter? close_tag = '</p>\n' if isinstance(node.parent, nodes.topic): self.body.append( - self.starttag(node, 'p', '', CLASS='topic-title first')) + self.starttag(node, 'p', '', CLASS='topic-title')) + # TODO: use role="heading" or <h1>? (HTML5 only) elif isinstance(node.parent, nodes.sidebar): self.body.append( self.starttag(node, 'p', '', CLASS='sidebar-title')) + # TODO: use role="heading" or <h1>? (HTML5 only) elif isinstance(node.parent, nodes.Admonition): self.body.append( self.starttag(node, 'p', '', CLASS='admonition-title')) @@ -1529,22 +1664,9 @@ class HTMLTranslator(nodes.NodeVisitor): self.in_document_title = len(self.body) else: assert isinstance(node.parent, nodes.section) - h_level = self.section_level + self.initial_header_level - 1 - atts = {} - if (len(node.parent) >= 2 and - isinstance(node.parent[1], nodes.subtitle)): - atts['CLASS'] = 'with-subtitle' - self.body.append( - self.starttag(node, 'h%s' % h_level, '', **atts)) - atts = {} - if node.hasattr('refid'): - atts['class'] = 'toc-backref' - atts['href'] = '#' + node['refid'] - if atts: - self.body.append(self.starttag({}, 'a', '', **atts)) - close_tag = '</a></h%s>\n' % (h_level) - else: - close_tag = '</h%s>\n' % (h_level) + # 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): @@ -1562,17 +1684,11 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_title_reference(self, node): self.body.append('</cite>') - # TODO: use the new HTML5 element <aside>? (Also for footnote text) def visit_topic(self, node): self.body.append(self.starttag(node, 'div', CLASS='topic')) - self.topic_classes = node['classes'] - # TODO: replace with :: - # self.in_contents = 'contents' in node['classes'] def depart_topic(self, node): self.body.append('</div>\n') - self.topic_classes = [] - # TODO self.in_contents = False def visit_transition(self, node): self.body.append(self.emptytag(node, 'hr', CLASS='docutils')) @@ -1606,20 +1722,16 @@ class SimpleListChecker(nodes.GenericNodeVisitor): raise nodes.NodeFound def visit_list_item(self, node): - # print "visiting list item", node.__class__ children = [child for child in node.children if not isinstance(child, nodes.Invisible)] - # print "has %s visible children" % len(children) 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() - # print "%s children remain" % len(children) if len(children) <= 1: return else: - # print "found", child.__class__, "in", node.__class__ raise nodes.NodeFound def pass_node(self, node): diff --git a/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py b/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py index 79f3dbd..8dab16d 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py +++ b/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py @@ -1,4 +1,4 @@ -# $Id: docutils_xml.py 7966 2016-08-18 13:06:09Z milde $ +# $Id: docutils_xml.py 8860 2021-10-22 16:39:59Z milde $ # Author: David Goodger, Paul Tremblay, Guenter Milde # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -11,25 +11,20 @@ http://docutils.sourceforge.net/docs/ref/docutils.dtd. __docformat__ = 'reStructuredText' import sys - -# Work around broken PyXML and obsolete python stdlib behaviour. (The stdlib -# replaces its own xml module with PyXML if the latter is installed. However, -# PyXML is no longer maintained and partially incompatible/buggy.) Reverse -# the order in which xml module and submodules are searched to import stdlib -# modules if they exist and PyXML modules if they do not exist in the stdlib. -# -# See http://sourceforge.net/tracker/index.php?func=detail&aid=3552403&group_id=38414&atid=422030 -# and http://lists.fedoraproject.org/pipermail/python-devel/2012-July/000406.html -import xml -if "_xmlplus" in xml.__path__[0]: # PyXML sub-module - xml.__path__.reverse() # If both are available, prefer stdlib over PyXML - import xml.sax.saxutils -from StringIO import StringIO import docutils from docutils import frontend, writers, nodes +if sys.version_info >= (3, 0): + from io import StringIO # noqa +else: + from StringIO import StringIO # noqa + + +if sys.version_info >= (3, 0): + unicode = str # noqa + class RawXmlError(docutils.ApplicationError): pass @@ -87,7 +82,7 @@ class XMLTranslator(nodes.GenericNodeVisitor): generator = '<!-- Generated by Docutils %s -->\n' xmlparser = xml.sax.make_parser() - """SAX parser instance to check/exctract raw XML.""" + """SAX parser instance to check/extract raw XML.""" xmlparser.setFeature( "http://xml.org/sax/features/external-general-entities", True) @@ -182,11 +177,11 @@ class XMLTranslator(nodes.GenericNodeVisitor): self.output.append(xml_string) self.default_departure(node) # or not? # Check validity of raw XML: - if isinstance(xml_string, unicode) and sys.version_info < (3,): + if isinstance(xml_string, unicode) and sys.version_info < (3, 0): xml_string = xml_string.encode('utf8') try: self.xmlparser.parse(StringIO(xml_string)) - except xml.sax._exceptions.SAXParseException, error: + except xml.sax._exceptions.SAXParseException as error: col_num = self.the_handle.locator.getColumnNumber() line_num = self.the_handle.locator.getLineNumber() srcline = node.line @@ -198,7 +193,7 @@ class XMLTranslator(nodes.GenericNodeVisitor): raise nodes.SkipNode # content already processed -class TestXml(xml.sax.ContentHandler): +class TestXml(xml.sax.handler.ContentHandler): def setDocumentLocator(self, locator): self.locator = locator diff --git a/docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py index 567eec8..6769e33 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 8035 2017-02-13 22:01:47Z milde $ +# $Id: __init__.py 8860 2021-10-22 16:39:59Z milde $ # Author: David Goodger # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -15,10 +15,13 @@ for proper viewing with a modern graphical browser. __docformat__ = 'reStructuredText' import os.path +import re +import sys import docutils from docutils import frontend, nodes, writers, io from docutils.transforms import writer_aux from docutils.writers import _html_base +from docutils.writers._html_base import PIL, url2pathname class Writer(writers._html_base.Writer): @@ -27,59 +30,55 @@ class Writer(writers._html_base.Writer): default_stylesheets = ['html4css1.css'] default_stylesheet_dirs = ['.', - os.path.abspath(os.path.dirname(__file__)), - # for math.css - os.path.abspath(os.path.join( - os.path.dirname(os.path.dirname(__file__)), 'html5_polyglot')) - ] - - default_template = 'template.txt' - default_template_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), default_template) - - settings_spec = ( - 'HTML-Specific Options', - None, - (('Specify the template file (UTF-8 encoded). Default is "%s".' - % default_template_path, + 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') + + settings_spec = frontend.filter_settings_spec( + writers._html_base.Writer.settings_spec, + # update specs with changed defaults or help string + template = + ('Template file. (UTF-8 encoded, default: "%s")' % default_template, ['--template'], - {'default': default_template_path, '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}), + {'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), + '(default: "%s")' % ','.join(default_stylesheets), ['--stylesheet-path'], {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', 'validator': frontend.validate_comma_separated_list, 'default': default_stylesheets}), - ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' - 'files must be accessible during processing. This is the default.', - ['--embed-stylesheet'], - {'default': 1, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Link to the stylesheet(s) in the output HTML file. ' - 'Default: embed stylesheets.', - ['--link-stylesheet'], - {'dest': 'embed_stylesheet', 'action': 'store_false'}), + stylesheet_dirs = ('Comma-separated list of directories where stylesheets are found. ' 'Used by --stylesheet-path when expanding relative path arguments. ' - 'Default: "%s"' % default_stylesheet_dirs, + '(default: "%s")' % ','.join(default_stylesheet_dirs), ['--stylesheet-dirs'], {'metavar': '<dir[,dir,...]>', 'validator': frontend.validate_comma_separated_list, 'default': default_stylesheet_dirs}), - ('Specify the initial header level. Default is 1 for "<h1>". ' - 'Does not affect document title & subtitle (see --no-doc-title).', + 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>'}), - ('Specify the maximum width (in characters) for one-column field ' + 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".', @@ -93,51 +92,10 @@ class Writer(writers._html_base.Writer): ['--option-limit'], {'default': 14, 'metavar': '<level>', 'validator': frontend.validate_nonnegative_int}), - ('Format for footnote references: one of "superscript" or ' - '"brackets". Default is "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 is "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: enabled.', - ['--compact-lists'], - {'default': 1, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Disable compact simple bullet and enumerated lists.', - ['--no-compact-lists'], - {'dest': 'compact_lists', 'action': 'store_false'}), - ('Remove extra vertical whitespace between items of simple field ' - 'lists. Default: enabled.', - ['--compact-field-lists'], - {'default': 1, '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". Default: ""', - ['--table-style'], - {'default': ''}), - ('Math output format, one of "MathML", "HTML", "MathJax" ' - 'or "LaTeX". Default: "HTML math.css"', - ['--math-output'], - {'default': 'HTML math.css'}), - ('Omit the XML declaration. Use with caution.', - ['--no-xml-declaration'], - {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false', - 'validator': frontend.validate_boolean}), - ('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}),)) + )) config_section = 'html4css1 writer' + config_section_dependencies = ('writers', 'html writers') def __init__(self): self.parts = {} @@ -219,6 +177,9 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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): @@ -226,6 +187,9 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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): @@ -247,7 +211,7 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): def depart_authors(self, node): self.depart_docinfo_item() - # use "width" argument insted of "style: 'width'": + # 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: @@ -278,7 +242,7 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): or (self.settings.compact_lists and 'open' not in node['classes'] and (self.compact_simple - or self.topic_classes == ['contents'] + or 'contents' in node.parent['classes'] # TODO: self.in_contents or self.check_simple_list(node)))) @@ -296,21 +260,61 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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>') + # 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('</dt>\n') 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 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', '')) @@ -358,6 +362,9 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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) @@ -365,6 +372,9 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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): """ @@ -484,10 +494,9 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): self.context.append('<a class="fn-backref" href="#%s">' % backrefs[0]) else: - # Python 2.4 fails with enumerate(backrefs, 1) - for (i, backref) in enumerate(backrefs): + for (i, backref) in enumerate(backrefs, 1): backlinks.append('<a class="fn-backref" href="#%s">%s</a>' - % (backref, i+1)) + % (backref, i)) self.context.append('<em>(%s)</em> ' % ', '.join(backlinks)) self.context += ['', ''] else: @@ -505,7 +514,7 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): self.body.append('</td></tr>\n' '</tbody>\n</table>\n') - # insert markers in text as pseudo-classes are not supported in CSS1: + # 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 @@ -526,11 +535,82 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): def visit_generated(self, node): pass - # Image types to place in an <object> element - # SVG not supported by IE up to version 8 - # (html4css1 strives for IE6 compatibility) + # 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'} + # + 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 not ('width' in node and 'height' in node) + and self.settings.file_insertion_enabled): + imagepath = url2pathname(uri) + try: + img = PIL.Image.open( + imagepath.encode(sys.getfilesystemencoding())) + except (IOError, UnicodeEncodeError): + pass # TODO: warn? + 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] + del img + 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) + if (isinstance(node.parent, nodes.TextElement) or + (isinstance(node.parent, nodes.reference) and + not isinstance(node.parent.parent, nodes.TextElement))): + # Inline context or surrounded by <a>...</a>. + suffix = '' + else: + suffix = '\n' + 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. @@ -541,18 +621,20 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): def depart_label(self, node): self.body.append(']%s</td><td>%s' % (self.context.pop(), 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.get('classes', []) + classes = node['classes'] if 'code' in classes: # filter 'code' from class arguments node['classes'] = [cls for cls in classes if cls != 'code'] @@ -580,11 +662,14 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): # Content already processed: raise nodes.SkipNode - # add newline after opening tag, don't use <code> for code + 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')) - # add newline def depart_literal_block(self, node): self.body.append('\n</pre>\n') @@ -673,6 +758,10 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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): @@ -754,15 +843,21 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): % (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), border="1")) + self.starttag(node, 'table', CLASS=' '.join(classes), **atts)) def depart_table(self, node): self.compact_p = self.context.pop() @@ -775,6 +870,15 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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): + pass + # hard-coded vertical alignment def visit_thead(self, node): self.body.append(self.starttag(node, 'thead', valign='bottom')) @@ -782,6 +886,28 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): 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): @@ -812,14 +938,11 @@ class SimpleListChecker(writers._html_base.SimpleListChecker): # def visit_enumerated_list(self, node): # pass - # def visit_paragraph(self, node): - # raise nodes.SkipNode + 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 - - def visit_definition_list(self, node): - raise nodes.NodeFound diff --git a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/__init__.py index 9835447..9fe7da9 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/__init__.py @@ -1,5 +1,5 @@ -# .. coding: utf8 -# $Id: __init__.py 8041 2017-03-01 11:02:33Z milde $ +# .. coding: utf-8 +# $Id: __init__.py 8881 2021-11-09 23:54:33Z milde $ # :Author: Günter Milde <milde@users.sf.net> # Based on the html4css1 writer by David Goodger. # :Maintainer: docutils-develop@lists.sourceforge.net @@ -12,7 +12,7 @@ # 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 +# .. _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/ @@ -20,14 +20,16 @@ """ Plain HyperText Markup Language document tree Writer. -The output conforms to the `HTML5` specification. +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' +import mimetypes import os.path + import docutils from docutils import frontend, nodes, writers, io from docutils.transforms import writer_aux @@ -35,108 +37,73 @@ from docutils.writers import _html_base class Writer(writers._html_base.Writer): - supported = ('html', 'html5', 'html4', 'xhtml', 'xhtml10') + supported = ('html', 'html5', 'xhtml') """Formats this writer supports.""" - default_stylesheets = ['minimal.css','plain.css'] + default_stylesheets = ['minimal.css', 'plain.css'] default_stylesheet_dirs = ['.', os.path.abspath(os.path.dirname(__file__))] + default_template = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'template.txt') - default_template = 'template.txt' - default_template_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), default_template) - - settings_spec = ( - 'HTML-Specific Options', - None, - (('Specify the template file (UTF-8 encoded). Default is "%s".' - % default_template_path, + settings_spec = frontend.filter_settings_spec( + writers._html_base.Writer.settings_spec, + # update specs with changed defaults or help string + template = + ('Template file. (UTF-8 encoded, default: "%s")' % default_template, ['--template'], - {'default': default_template_path, '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}), + {'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), + '(default: "%s")' % ','.join(default_stylesheets), ['--stylesheet-path'], {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', 'validator': frontend.validate_comma_separated_list, 'default': default_stylesheets}), - ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' - 'files must be accessible during processing. This is the default.', - ['--embed-stylesheet'], - {'default': 1, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Link to the stylesheet(s) in the output HTML file. ' - 'Default: embed stylesheets.', - ['--link-stylesheet'], - {'dest': 'embed_stylesheet', 'action': 'store_false'}), + stylesheet_dirs = ('Comma-separated list of directories where stylesheets are found. ' 'Used by --stylesheet-path when expanding relative path arguments. ' - 'Default: "%s"' % default_stylesheet_dirs, + '(default: "%s")' % ','.join(default_stylesheet_dirs), ['--stylesheet-dirs'], {'metavar': '<dir[,dir,...]>', 'validator': frontend.validate_comma_separated_list, 'default': default_stylesheet_dirs}), - ('Specify the initial header level. Default is 1 for "<h1>". ' - 'Does not affect document title & subtitle (see --no-doc-title).', + 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': '1', + {'choices': '1 2 3 4 5 6'.split(), 'default': '2', 'metavar': '<level>'}), - ('Format for footnote references: one of "superscript" or ' - '"brackets". Default is "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 is "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: enabled.', - ['--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: enabled.', - ['--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. ' - 'Default: ""', - ['--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'}), - ('Prepend an XML declaration. (Thwarts HTML5 conformance.) ' - 'Default: False', - ['--xml-declaration'], - {'default': False, 'action': 'store_true', - 'validator': frontend.validate_boolean}), + no_xml_declaration = ('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_spec = settings_spec + ( + 'HTML5 Writer Options', + '', + (('Obsoleted by "--image-loading".', + ['--embed-images'], + {'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('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': 0, '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' @@ -155,60 +122,329 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): and examples. """ - # <acronym> tag not supported in HTML5. Use the <abbr> tag instead. + # meta tag to fix rendering in mobile browsers + viewport = ('<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 meta tag in HTML5 + # 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.add_meta('<meta name="author" content="%s" />\n' % + self.attval(subnode.astext())) + def depart_authors(self, node): self.depart_docinfo_item() - # no meta tag in HTML5 + # 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 = set(('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('</%s>\n' % 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.add_meta('<meta name="dcterms.rights" content="%s" />\n' + % self.attval(node.astext())) + def depart_copyright(self, node): self.depart_docinfo_item() - # no meta tag in HTML5 + # no standard meta tag name in HTML5, use dcterms.date def visit_date(self, node): self.visit_docinfo_item(node, 'date', meta=False) + self.add_meta('<meta name="dcterms.date" content="%s" />\n' + % self.attval(node.astext())) + def depart_date(self, node): self.depart_docinfo_item() - # TODO: use HTML5 <footer> element? - # def visit_footer(self, node): - # def depart_footer(self, node): + def visit_document(self, node): + title = (node.get('title', '') or os.path.basename(node['source']) + or 'untitled Docutils document') + self.head.append('<title>%s</title>\n' % self.encode(title)) + + 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.meta.insert(0, self.viewport) + self.head.insert(0, self.viewport) + self.meta.insert(0, self.content_type % self.settings.output_encoding) + self.head.insert(0, self.content_type % self.settings.output_encoding) + 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, 'main')) + self.body_suffix.insert(0, '</main>\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, 'len(context) = %s' % len(self.context) + + # use new HTML5 <figure> and <figcaption> elements + def visit_figure(self, node): + atts = {} + 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, 'figure', **atts)) - # TODO: use the new HTML5 element <aside>? (Also for footnote text) - # def visit_footnote(self, node): - # def depart_footnote(self, node): + 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('\n</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:] + + # MIME types supported by the HTML5 <video> element + videotypes = ('video/mp4', 'video/webm', 'video/ogg') + + def visit_image(self, node): + atts = {} + uri = node['uri'] + mimetype = mimetypes.guess_type(uri)[0] + if mimetype not in self.videotypes: + return super(HTMLTranslator, self).visit_image(node) + # image size + if 'width' in node: + atts['width'] = node['width'].replace('px', '') + if 'height' in node: + atts['height'] = node['height'].replace('px', '') + if 'align' in node: + atts['class'] = 'align-%s' % node['align'] + if 'controls' in node['classes']: + atts['controls'] = 'controls' + atts['title'] = node.get('alt', uri) + if getattr(self.settings, 'image_loading', None) == 'lazy': + atts['loading'] = 'lazy' + # No newline in inline context or if surrounded by <a>...</a>. + if (isinstance(node.parent, nodes.TextElement) or + (isinstance(node.parent, nodes.reference) and + not isinstance(node.parent.parent, nodes.TextElement))): + suffix = '' + else: + suffix = '\n' + self.body.append('%s<a href="%s">%s</a>%s</video>%s' + % (self.starttag(node, 'video', suffix, src=uri, **atts), + uri, node.get('alt', uri), suffix, suffix)) + + def depart_image(self, node): + pass + + # use HTML text-level tags if matching class value found + supported_inline_tags = set(('code', 'kbd', 'dfn', 'samp', 'var', + 'bdi', 'del', 'ins', 'mark', 'small', + 'b', 'i', 'q', 's', 'u')) + def visit_inline(self, node): + # Use `supported_inline_tags` if found in class values + classes = node['classes'] + tags = [cls for cls in self.supported_inline_tags + if cls in classes] + if len(tags): + node.html5tagname = tags[0] + classes.remove(tags[0]) + elif (classes == ['ln'] and isinstance(node.parent, nodes.literal_block) + and 'code' in node.parent.get('classes')): + if self.body[-1] == '<code>': + del(self.body[-1]) + else: + self.body.append('</code>') + node.html5tagname = 'small' + else: + node.html5tagname = 'span' + self.body.append(self.starttag(node, node.html5tagname, '')) + + def depart_inline(self, node): + self.body.append('</%s>' % node.html5tagname) + if (node.html5tagname == 'small' and node.get('classes') == ['ln'] + and isinstance(node.parent, nodes.literal_block)): + self.body.append('<code data-lineno="%s">' % 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 HTML text-level tags if matching class value found + def visit_literal(self, node): + classes = node['classes'] + tags = [cls for cls in self.supported_inline_tags + if cls in classes] + if len(tags): + tagname = tags[0] + classes.remove(tags[0]) + else: + tagname = 'span' + if tagname == 'code': + self.body.append(self.starttag(node, 'code', '')) + return + self.body.append( + self.starttag(node, tagname, '', 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('<span class="pre">%s</span>' + % self.encode(token)) + else: + self.body.append(self.encode(token)) + self.body.append('</%s>' % tagname) + # Content already processed: + raise nodes.SkipNode + + def depart_literal(self, node): + # skipped unless literal element is from "code" role: + self.body.append('</code>') # 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'] - # del(node['lang']) meta = self.emptytag(node, 'meta', **node.non_default_attributes()) self.add_meta(meta) def depart_meta(self, node): pass - # no meta tag in HTML5 + # 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() - # TODO: use the new HTML5 element <section>? - # def visit_section(self, node): - # def depart_section(self, node): + # 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.html_tagname = '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.html_tagname = 'div' + atts['role'] = 'doc-abstract' + elif 'dedication' in node['classes']: + node.html_tagname = 'div' + atts['role'] = 'doc-dedication' + else: + node.html_tagname = 'aside' + self.body.append(self.starttag(node, node.html_tagname, **atts)) + + def depart_topic(self, node): + self.body.append('</%s>\n'%node.html_tagname) + del(node.html_tagname) - # TODO: use the new HTML5 element <aside>? - # def visit_topic(self, node): - # def depart_topic(self, node): + # append self-link + def section_title_tags(self, node): + start_tag, close_tag = super(HTMLTranslator, + self).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"' + ' href="#%s"></a>' % ids[0]) + close_tag = close_tag.replace('</h', self_link + '</h') + return start_tag, close_tag diff --git a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/math.css b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/math.css index e6eec65..1891a79 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/math.css +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/math.css @@ -2,6 +2,7 @@ * 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, @@ -13,16 +14,19 @@ * * Based on eLyXer: convert LyX source files to HTML output. * http://elyxer.nongnu.org/ -*/ -/* --end-- +* +* * 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; - font-family: "Droid Serif", "DejaVu Serif", "STIX", serif; margin: 1.2em 0; + line-height: 1.4; } span.formula { white-space: nowrap; @@ -46,6 +50,9 @@ span.unknown { span.ignored, span.arraydef { display: none; } +.phantom { + visibility: hidden; +} .formula i { letter-spacing: 0.1ex; } @@ -62,73 +69,111 @@ span.ignored, span.arraydef { } /* Structures */ +span.hspace { + display: inline-block; +} span.overline, span.bar { text-decoration: overline; } -.fraction, .fullfraction { +.fraction, .fullfraction, .textfraction { display: inline-block; vertical-align: middle; text-align: center; } -.fraction .fraction { +span.formula .fraction, +.textfraction, +span.smallmatrix { font-size: 80%; - line-height: 100%; + 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 { - font-size: 70%; vertical-align: 80%; } sub.denominator, sub.unit { - font-size: 70%; 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 { - font-size: 70%; position: relative; left: 1.4ex; } span.radical { display: inline-block; padding: 0ex; - font-size: 150%; + /* 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; } -span.symbol { - line-height: 125%; - font-size: 125%; +div.formula .bigoperator, +.displaystyle .bigoperator, +.displaystyle .bigoperator { + line-height: 120%; + font-size: 140%; + padding-right: 0.2ex; } -span.bigsymbol { - line-height: 150%; - font-size: 150%; +span.fraction .bigoperator, +span.scriptstyle .bigoperator { + line-height: inherit; + font-size: inherit; + padding-right: 0; +} +span.bigdelimiter { + display: inline-block; } -span.largesymbol { - font-size: 175%; +span.bigdelimiter.size1 { + transform: scale(1, 1.2); + line-height: 1.2; } -span.hugesymbol { - font-size: 200%; +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; @@ -146,51 +191,42 @@ span.limits { sup.limit, sub.limit { line-height: 100%; } -span.symbolover { - display: inline-block; - text-align: center; - position: relative; - float: right; - right: 100%; - bottom: 0.5em; - width: 0px; -} -span.withsymbol { +span.embellished, +span.embellished > .base { display: inline-block; } -span.symbolunder { +span.embellished > sup, +span.embellished > sub { display: inline-block; - text-align: center; + font-size: 100%; position: relative; - float: right; - right: 80%; - top: 0.3em; + 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; - border-collapse: collapse; - margin: 0em; vertical-align: middle; } span.arrayrow, span.binomrow { display: table-row; - padding: 0ex; - border: 0ex; + padding: 0; + border: 0; } span.arraycell, span.bracket, span.case, span.binomcell, span.environmentcell { display: table-cell; padding: 0ex 0.2ex; - line-height: 99%; + line-height: 1; /* 99%; */ border: 0ex; } -/* -* CSS file for LaTeX formulas, extra stuff: -* binomials, vertical braces, stackrel, fonts and colors. -*/ +.environment.align > .arrayrow > .arraycell.align-l { + padding-right: 2em; +} /* Inline binomials */ span.binom { @@ -233,19 +269,30 @@ span.downstackrel { } /* 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-style: normal; font-family: sans-serif; } span.mathrm, span.textrm { - font-style: normal; - font-family: serif; + font-family: STIX, "DejaVu Serif", "DejaVu Math TeX Gyre", serif; } -span.text, span.textnormal { - font-style: normal; +span.mathtt, span.texttt { + font-family: monospace; } -span.textipa { - color: #008080; +span.text, span.textnormal, +span.mathsf, span.mathtt, span.mathrm { + font-style: normal; } span.fraktur { font-family: "Lucida Blackletter", eufm10, blackletter; @@ -257,6 +304,16 @@ 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 { @@ -273,4 +330,3 @@ span.boxed, span.framebox { border: thin solid black; padding: 5px; } - diff --git a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/minimal.css b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/minimal.css index 180213b..69bf8f8 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/minimal.css +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/minimal.css @@ -1,8 +1,8 @@ /* Minimal style sheet for the HTML output of Docutils. */ /* */ /* :Author: Günter Milde, based on html4css1.css by David Goodger */ -/* :Id: $Id: minimal.css 8036 2017-02-14 13:05:46Z milde $ */ -/* :Copyright: © 2015 Günter Milde. */ +/* :Id: $Id: minimal.css 8783 2021-06-30 07:47:46Z milde $ */ +/* :Copyright: © 2015, 2021 Günter Milde. */ /* :License: Released under the terms of the `2-Clause BSD license`_, */ /* in short: */ /* */ @@ -14,89 +14,73 @@ /* */ /* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ -/* This CSS2.1_ stylesheet defines rules for Docutils elements without */ -/* HTML equivalent. It is required to make the document semantic visible. */ -/* */ -/* .. _CSS2.1: http://www.w3.org/TR/CSS2 */ -/* .. _validates: http://jigsaw.w3.org/css-validator/validator$link */ - -/* alignment of text and inline objects inside block objects*/ -.align-left { text-align: left; } -.align-right { text-align: right; } -.align-center { clear: both; text-align: center; } -.align-top { vertical-align: top; } -.align-middle { vertical-align: middle; } -.align-bottom { vertical-align: bottom; } +/* 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 */ -h1.title, p.subtitle { - text-align: center; -} -p.admonition-title, p.topic-title, -p.sidebar-title, -p.rubric, +p.admonition-title, p.system-message-title { font-weight: bold; } -h1 + p.subtitle, -h1 + p.section-subtitle { - font-size: 1.6em; +p.sidebar-title, +p.rubric { + font-weight: bold; + font-size: larger; +} +p.rubric { + color: maroon; } -h2 + p.section-subtitle { font-size: 1.28em; } p.subtitle, p.section-subtitle, p.sidebar-subtitle { font-weight: bold; margin-top: -0.5em; } -p.sidebar-title, -p.rubric { - font-size: larger; +h1 + p.subtitle { + font-size: 1.6em; } -p.rubric { color: maroon; } a.toc-backref { - color: black; - text-decoration: none; } + color: inherit; + text-decoration: none; +} /* Warnings, Errors */ -div.caution p.admonition-title, -div.attention p.admonition-title, -div.danger p.admonition-title, -div.error p.admonition-title, -div.warning p.admonition-title, -div.system-messages h1, -div.error, -span.problematic, -p.system-message-title { +.system-messages h2, +.system-message-title, +span.problematic { color: red; } -/* inline literals */ -span.docutils.literal { +/* Inline Literals */ +.docutils.literal { font-family: monospace; white-space: pre-wrap; } -/* do not wraph at hyphens and similar: */ +/* do not wrap at hyphens and similar: */ .literal > span.pre { white-space: nowrap; } /* Lists */ /* compact and simple lists: no margin between items */ -.simple li, .compact li, -.simple ul, .compact ul, -.simple ol, .compact ol, -.simple > li p, .compact > li p, -dl.simple > dd, dl.compact > dd { +.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 */ -div.topic.contents { margin: 0; } -ul.auto-toc { +.contents ul.auto-toc { /* section numbers present */ list-style-type: none; - padding-left: 1.5em; } +} /* Enumerated Lists */ ol.arabic { list-style: decimal } @@ -105,32 +89,41 @@ ol.upperalpha { list-style: upper-alpha } ol.lowerroman { list-style: lower-roman } ol.upperroman { list-style: upper-roman } -dt span.classifier { font-style: italic } -dt span.classifier:before { +/* Definition Lists and Derivatives */ +dt .classifier { font-style: italic } +dt .classifier:before { font-style: normal; margin: 0.5em; content: ":"; } - -/* Field Lists and drivatives */ +/* 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, -dl.footnote > dt, -dl.citation > dt { +dl.docinfo > dt { font-weight: bold; clear: left; float: left; margin: 0; padding: 0; - padding-right: 0.5em; + padding-right: 0.2em; } /* 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 */ + 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, @@ -140,121 +133,143 @@ dl.option-list > dd > *:first-child width: 100%; margin: 0; } -/* field names followed by a colon */ -dl.field-list > dt:after, -dl.docinfo > dt:after { - content: ":"; -} /* Bibliographic Fields (docinfo) */ -pre.address { font: inherit; } -dd.authors > p { margin: 0; } +dl.docinfo pre.address { + font: inherit; + margin: 0.5em 0; +} +dl.docinfo > dd.authors > p { margin: 0; } /* Option Lists */ -dl.option-list { margin-left: 40px; } dl.option-list > dt { font-weight: normal; } span.option { white-space: nowrap; } /* Footnotes and Citations */ -dl.footnote.superscript > dd {margin-left: 1em; } -dl.footnote.brackets > dd {margin-left: 2em; } -dl > dt.label { font-weight: normal; } -a.footnote-reference.brackets:before, -dt.label > span.brackets:before { content: "["; } -a.footnote-reference.brackets:after, -dt.label > span.brackets:after { content: "]"; } -a.footnote-reference.superscript, -dl.footnote.superscript > dt.label { - vertical-align: super; - font-size: smaller; -} -dt.label > span.fn-backref { margin-left: 0.2em; } -dt.label > span.fn-backref > a { font-style: italic; } -/* Line Blocks */ -div.line-block { display: block; } -div.line-block div.line-block { +.footnote, .citation { margin: 1em 0; } /* default paragraph skip (Firefox) */ +/* hanging indent */ +.citation { padding-left: 2em; } +.footnote { padding-left: 1.7em; } +.footnote.superscript { padding-left: 0.9em; } +.citation > .label { margin-left: -2em; } +.footnote > .label { margin-left: -1.7em; } +.footnote.superscript > .label { margin-left: -0.9em; } + +.footnote > .label + *, +.citation > .label + * { + display: inline-block; + margin-top: 0; + vertical-align: top; +} +.footnote > .backrefs + *, +.citation > .backrefs + * { margin-top: 0; - margin-bottom: 0; - margin-left: 40px; +} +.footnote > .label + p, .footnote > .backrefs + p, +.citation > .label + p, .citation > .backrefs + p { + display: inline; + vertical-align: inherit; } -/* Figures, Images, and Tables */ -.figure.align-left, -img.align-left, -object.align-left, -table.align-left { - margin-right: auto; +.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; } -.figure.align-center, -img.align-center, -object.align-center { - margin-left: auto; +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; - display: block; } -table.align-center { +.align-center { + text-align: center; margin-left: auto; margin-right: auto; } -.figure.align-right, -img.align-right, -object.align-right, -table.align-right { +.align-right { + text-align: right; margin-left: auto; } -/* reset inner alignment in figures and tables */ -/* div.align-left, div.align-center, div.align-right, */ -table.align-left, table.align-center, table.align-right -{ text-align: inherit } +.align-top { vertical-align: top; } +.align-middle { vertical-align: middle; } +.align-bottom { vertical-align: bottom; } -/* Admonitions and System Messages */ -div.admonition, -div.system-message, -div.sidebar{ - margin: 40px; - border: medium outset; - padding-right: 1em; - padding-left: 1em; +/* 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; } -/* Sidebar */ -div.sidebar { - width: 30%; - max-width: 26em; - float: right; - clear: right; +/* 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; } -/* Text Blocks */ -div.topic, -pre.literal-block, -pre.doctest-block, -pre.math, -pre.code { - margin-right: 40px; - margin-left: 40px; -} -pre.code .ln { color: gray; } /* line numbers */ +/* 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; } +table { + border-collapse: collapse; +} td, th { - border-style: solid; - border-color: silver; + border: thin solid silver; padding: 0 1ex; - border-width: thin; } -td > p:first-child, th > p:first-child { margin-top: 0; } -td > p, th > p { margin-bottom: 0; } +.borderless td, .borderless th { + border: 0; + padding: 0; + padding-right: 0.5em /* separate table cells */ +} table > caption { text-align: left; - margin-bottom: 0.25em + margin-top: 0.2em; + margin-bottom: 0.2em; +} +table.captionbelow { + caption-side: bottom; } -table.borderless td, table.borderless th { - border: 0; - padding: 0; - padding-right: 0.5em /* separate table cells */ +/* 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, header, footer, main, aside, nav, section, figure, video, details { + display: block; +} +/* inline images */ +p img, p video, figure img, figure video { + display: inline; } diff --git a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/plain.css b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/plain.css index a2d27ba..3f90a26 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/plain.css +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/plain.css @@ -1,8 +1,8 @@ /* CSS31_ style sheet for the output of Docutils HTML writers. */ -/* Rules for easy reading and pre-defined style variants. */ +/* Rules for easy reading and pre-defined style variants. */ /* */ /* :Author: Günter Milde, based on html4css1.css by David Goodger */ -/* :Id: $Id: plain.css 8120 2017-06-22 21:02:40Z milde $ */ +/* :Id: $Id: plain.css 8805 2021-08-10 14:13:35Z milde $ */ /* :Copyright: © 2015 Günter Milde. */ /* :License: Released under the terms of the `2-Clause BSD license`_, */ /* in short: */ @@ -10,11 +10,11 @@ /* 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: http://www.w3.org/TR/CSS3 */ +/* .. _CSS3: http://www.w3.org/TR/CSS3 */ /* Document Structure */ @@ -22,23 +22,45 @@ /* "page layout" */ body { - padding: 0 5%; - margin: 8px 0; + margin: 0; + background-color: #dbdbdb; + --field-indent: 9em; /* default indent of fields in field lists */ } -div.document { - line-height:1.3; - counter-reset: table; - /* counter-reset: figure; */ +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: 50em; + 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; +} -/* Sections */ +/* 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; @@ -46,50 +68,59 @@ hr.docutils { clear: both; } -/* Paragraphs */ -/* ========== */ +/* Paragraphs */ /* vertical space (parskip) */ -p, ol, ul, dl, +p, ol, ul, dl, li, div.line-block, -table{ +.footnote, .citation, +div > math, +table { margin-top: 0.5em; margin-bottom: 0.5em; } + h1, h2, h3, h4, h5, h6, -dl > dd { +dd, details > p:last-child { margin-bottom: 0.5em; } -/* Lists */ -/* ========== */ - -/* Definition Lists */ +/* Lists */ +/* ===== */ -dl > dd > p:first-child { margin-top: 0; } -/* :last-child is not part of CSS 2.1 (introduced in CSS 3) */ -dl > dd > p:last-child { margin-bottom: 0; } - -/* lists nested in definition lists */ -/* :only-child is not part of CSS 2.1 (introduced in CSS 3) */ +/* 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.5em; + 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 { - margin-left: 5em; + --field-indent: 5em; } /* run-in: start field-body on same line after long field names */ dl.field-list.run-in > dd p { @@ -98,8 +129,8 @@ dl.field-list.run-in > dd p { /* Bibliographic Fields */ -/* generally, bibliographic fields use special definition list dl.docinfo */ -/* but dedication and abstract are placed into "topic" divs */ +/* generally, bibliographic fields use dl.docinfo */ +/* but dedication and abstract are placed into divs */ div.abstract p.topic-title { text-align: center; } @@ -112,44 +143,29 @@ div.dedication p.topic-title { font-style: normal; } -/* Citations */ -dl.citation dt.label { - font-weight: bold; -} -span.fn-backref { - font-weight: normal; -} +/* disclosures */ +details { padding-left: 1em; } +summary { margin-left: -1em; } -/* Text Blocks */ -/* ============ */ +/* Text Blocks */ +/* =========== */ -/* Literal Blocks */ +/* Literal Blocks */ pre.literal-block, pre.doctest-block, pre.math, pre.code { - margin-left: 1.5em; - margin-right: 1.5em + font-family: monospace; } -/* Block Quotes */ - -blockquote, -div.topic { - margin-left: 1.5em; - margin-right: 1.5em -} -blockquote > table, -div.topic > table { - margin-top: 0; - margin-bottom: 0; -} +/* Block Quotes and Topics */ +bockquote { margin: 1em 2em; } blockquote p.attribution, -div.topic p.attribution { +.topic p.attribution { text-align: right; margin-left: 20%; } -/* Tables */ -/* ====== */ +/* Tables */ +/* ====== */ /* th { vertical-align: bottom; } */ @@ -176,63 +192,88 @@ table.numbered > caption:before { font-weight: bold; } -/* Explicit Markup Blocks */ -/* ====================== */ +/* Explicit Markup Blocks */ +/* ====================== */ -/* Footnotes and Citations */ -/* ----------------------- */ +/* Footnotes and Citations */ +/* ----------------------- */ /* line on the left */ -dl.footnote { - padding-left: 1ex; - border-left: solid; - border-left-width: thin; +.footnote { + border-left: solid thin; + padding-left: 2.1em; + margin-bottom: 0; +} +.footnote + .footnote { + padding-top: 0.5em; + margin-top: 0; +} +.footnote.superscript { + padding-left: 1.2em; } -/* Directives */ -/* ---------- */ +/* Directives */ +/* ---------- */ -/* Body Elements */ -/* ~~~~~~~~~~~~~ */ +/* Body Elements */ +/* ~~~~~~~~~~~~~ */ /* Images and Figures */ /* let content flow to the side of aligned images and figures */ -.figure.align-left, +figure.align-left, img.align-left, +video.align-left, object.align-left { - display: block; clear: left; float: left; - margin-right: 1em + margin-right: 1em; } -.figure.align-right, +figure.align-right, img.align-right, +video.align-right, object.align-right { - display: block; clear: right; float: right; - margin-left: 1em + margin-left: 1em; } -/* Stop floating sidebars, images and figures at section level 1,2,3 */ -h1, h2, h3 { clear: both; } +/* Stop floating sidebars, images and figures */ +h1, h2, h3, h4, footer, header { clear: both; } -/* Sidebar */ +/* 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; +} -/* Move into the margin. In a layout with fixed margins, */ -/* it can be moved into the margin completely. */ -div.sidebar { +/* 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: -5.5%; - background-color: #ffffee ; + margin-right: -1%; + background-color: #fffffa; } -/* Code */ +/* Code */ +pre.code { padding: 0.7ex } pre.code, code { background-color: #eeeeee } -pre.code .ln { color: gray; } /* line numbers */ /* basic highlighting: for a complete scheme, see */ /* http://docutils.sourceforge.net/sandbox/stylesheets/ */ pre.code .comment, code .comment { color: #5C6576 } @@ -242,47 +283,38 @@ 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} -/* Math */ -/* styled separately (see math.css for math-output=HTML) */ - -/* Epigraph */ -/* Highlights */ -/* Pull-Quote */ -/* Compound Paragraph */ -/* Container */ - -/* can be styled in a custom stylesheet */ +/* Math */ +/* for math-output=MathML (for math-output=HTML, see math.css) */ +math .boldsymbol { + font-weight: bold; +} +mstyle.mathscr, mi.mathscr { + font-family: STIX; +} -/* Document Header and Footer */ +/* Epigraph */ +/* Highlights */ +/* Pull-Quote */ +/* Compound Paragraph */ +/* Container */ -div.footer, div.header { - clear: both; - font-size: smaller; -} +/* Inline Markup */ +/* ============= */ -/* Inline Markup */ -/* ============= */ +sup, sub { line-height: 0.8; } /* do not add leading for lines with sup/sub */ -/* Emphasis */ -/* em */ -/* Strong Emphasis */ -/* strong */ -/* Interpreted Text */ -/* span.interpreted */ -/* Title Reference */ -/* cite */ -/* Inline Literals */ +/* Inline Literals */ /* possible values: normal, nowrap, pre, pre-wrap, pre-line */ -/* span.docutils.literal { white-space: pre-wrap; } */ +/* span.docutils.literal { white-space: pre-wrap; } */ -/* Hyperlink References */ +/* Hyperlink References */ a { text-decoration: none; } -/* External Targets */ -/* span.target.external */ -/* Internal Targets */ -/* span.target.internal */ -/* Footnote References */ -/* a.footnote-reference */ -/* Citation References */ -/* a.citation-reference */ +/* External Targets */ +/* span.target.external */ +/* Internal Targets */ +/* span.target.internal */ +/* Footnote References */ +/* a[role="doc-noteref"] */ +/* Citation References */ +/* a.citation-reference */ diff --git a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/responsive.css b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/responsive.css new file mode 100644 index 0000000..a75e71a --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/responsive.css @@ -0,0 +1,488 @@ +/* CSS3_ style sheet for the output of Docutils HTML writers. */ +/* Generic responsive design for all screen sizes. */ +/* */ +/* :Author: Günter Milde */ +/* */ +/* :Id: $Id: responsive.css 8856 2021-10-15 16:03:47Z 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: http://www.w3.org/TR/CSS3 */ + + +/* 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, +div.line-block, +.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 */ + +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 */ +img { + display: block; +} +p > img, p > a > img, +figure > img, figure > a > img { + display: inline; +} + +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; +} +.literal-block, .doctest, span.literal { + background-color: #f6f9f8; +} +.system-message span.literal { + background-color: inherit; +} + +/* basic highlighting: for a complete scheme, see */ +/* http://docutils.sourceforge.net/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 { + 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 the target of the current URL */ +/* section:target > h2, section:target > h3, section:target > h4, */ +/* section:target > h5, section:target > h6, */ +.contents :target, +.contents:target > .topic-title, +[role="doc-biblioentry"]:target > .label, +[role="doc-biblioref"]:target, +[role="note"]:target > .label, +[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, +video.align-left, +figure.align-left, +table.align-left { + margin-left: 0; + padding-left: 0; + margin-right: 0.5em; + clear: left; + float: left; +} +img.align-right, +video.align-right, +figure.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%; +} + +/* Math */ +/* for math-output=MathML (for math-output=HTML, see math.css) */ +math .boldsymbol { + font-weight: bold; +} +mstyle.mathscr, mi.mathscr { + font-family: STIX; +} + +/* 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; + } + dl.field-list.narrow, dl.docinfo, dl.option-list { + --field-indent: 2.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/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/tuftig.css b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/tuftig.css new file mode 100644 index 0000000..b12c744 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/tuftig.css @@ -0,0 +1,554 @@ +/* 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 8780 2021-06-25 20:59:26Z 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: http://www.w3.org/TR/CSS3 */ +/* .. _tufte.css: https://edwardtufte.github.io/tufte-css/ */ +/* .. _tufte-latex_: https://www.ctan.org/pkg/tufte-latex */ + + +/* General Settings */ +/* ================ */ + +body { + font-family: et-book, Palatino, 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, +div.line-block, +.topic, +.footnote, .citation, +table { + margin-top: 0.5em; + margin-bottom: 0.5em; +} +h1, h2, h3, h4, h5, h6, +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; +} +h2, h3, h4 { + margin-top: 2.0em; +} +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; +} + +/* Images and Figures */ +img { + display: block; +} +p > img, p > a > img, +figure > img, figure > a > img { + display: inline; +} +/* 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 */ +/* http://docutils.sourceforge.net/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, +figure.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 { + 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-start] minmax(0, 6fr) + [content-end] 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-start / content-end; + } + .citation.align-left { + font-size: 1em; + } + figure > img { /* indent */ + margin: 0.5em 2%; + padding-left: 1em; + } + + /* Margin Elements */ + /* Sidebar, Footnotes, Citations, Captions */ + aside.sidebar, + .footnote, + .citation, + figcaption, + /* table > caption, does not work :(*/ + .marginal, + .admonition.marginal, + .topic.marginal { + /* background-color: Lavender; */ + grid-column: content-end / end; + 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; + } + .footnote { + padding-left: 0; + border-left: none; + } + .citation { + padding-left: 1em; + } + .citation .label { + margin-left: -1em; + } + + /* 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-start / 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/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py index b9720d2..7a73e84 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py @@ -1,11 +1,13 @@ # .. coding: utf-8 -# $Id: __init__.py 8058 2017-04-19 16:45:32Z milde $ +# $Id: __init__.py 8880 2021-11-05 11:11:18Z 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.""" +from __future__ import division + __docformat__ = 'reStructuredText' # code contributions from several people included, thanks to all. @@ -13,24 +15,36 @@ __docformat__ = 'reStructuredText' # # convention deactivate code by two # i.e. ##. -import sys import os -import time import re import string -import urllib +import sys +import warnings + +if sys.version_info < (3, 0): + from io import open + from urllib import url2pathname +else: + from urllib.request import url2pathname + try: import roman except ImportError: import docutils.utils.roman as roman -from docutils import frontend, nodes, languages, writers, utils, io + +import docutils +from docutils import frontend, nodes, languages, writers, utils from docutils.utils.error_reporting import SafeString from docutils.transforms import writer_aux from docutils.utils.math import pick_math_environment, unichar2tex +if sys.version_info >= (3, 0): + unicode = str # noqa + + class Writer(writers.Writer): - supported = ('latex','latex2e') + supported = ('latex', 'latex2e') """Formats this writer supports.""" default_template = 'default.tex' @@ -39,46 +53,43 @@ class Writer(writers.Writer): r'\usepackage{mathptmx} % Times', r'\usepackage[scaled=.90]{helvet}', r'\usepackage{courier}']) - table_style_values = ('standard', 'booktabs','nolines', 'borderless', - 'colwidths-auto', 'colwidths-given') + table_style_values = [# TODO: align-left, align-center, align-right, ?? + 'booktabs', 'borderless', 'colwidths-auto', + 'nolines', 'standard'] settings_spec = ( 'LaTeX-Specific Options', None, - (('Specify documentclass. Default is "article".', + (('Specify LaTeX documentclass. Default: "article".', ['--documentclass'], {'default': 'article', }), ('Specify document options. Multiple options can be given, ' - 'separated by commas. Default is "a4paper".', + 'separated by commas. Default: "a4paper".', ['--documentoptions'], {'default': 'a4paper', }), - ('Footnotes with numbers/symbols by Docutils. (default)', - ['--docutils-footnotes'], - {'default': True, 'action': 'store_true', - 'validator': frontend.validate_boolean}), ('Format for footnote references: one of "superscript" or ' - '"brackets". Default is "superscript".', + '"brackets". Default: "superscript".', ['--footnote-references'], {'choices': ['superscript', 'brackets'], 'default': 'superscript', 'metavar': '<format>', 'overrides': 'trim_footnote_reference_space'}), - ('Use \\cite command for citations. ', + ('Use \\cite command for citations. (future default)', ['--use-latex-citations'], - {'default': 0, 'action': 'store_true', + {'default': None, 'action': 'store_true', 'validator': frontend.validate_boolean}), ('Use figure floats for citations ' - '(might get mixed with real figures). (default)', + '(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 is "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. ' + '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,...]>', @@ -97,11 +108,11 @@ class Writer(writers.Writer): ('Embed the stylesheet(s) in the output file. ' 'Stylesheets must be accessible during processing. ', ['--embed-stylesheet'], - {'default': 0, 'action': 'store_true', + {'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: "."', + 'Default: ".".', ['--stylesheet-dirs'], {'metavar': '<dir[,dir,...]>', 'validator': frontend.validate_comma_separated_list, @@ -113,75 +124,71 @@ class Writer(writers.Writer): ('Specify the template file. Default: "%s".' % default_template, ['--template'], {'default': default_template, 'metavar': '<file>'}), - ('Table of contents by LaTeX. (default) ', + ('Table of contents by LaTeX. (default)', ['--use-latex-toc'], - {'default': 1, 'action': 'store_true', + {'default': True, 'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Table of contents by Docutils (without page numbers). ', + ('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': 0, 'action': 'store_true', + {'default': False, 'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Attach author and date to the document info table. (default) ', + ('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': 0, 'action': 'store_true', + {'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 environment for the document's abstract.", ['--use-latex-abstract'], - {'default': 0, 'action': 'store_true', + {'default': False, 'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Color of any hyperlinks embedded in text ' - '(default: "blue", "false" to disable).', + ('Color of any hyperlinks embedded in text. ' + 'Default: "blue" (use "false" to disable).', ['--hyperlink-color'], {'default': 'blue'}), - ('Additional options to the "hyperref" package ' - '(default: "").', + ('Additional options to the "hyperref" package.', ['--hyperref-options'], {'default': ''}), ('Enable compound enumerators for nested enumerated lists ' - '(e.g. "1.2.a.ii"). Default: disabled.', + '(e.g. "1.2.a.ii").', ['--compound-enumerators'], - {'default': None, 'action': 'store_true', + {'default': False, 'action': 'store_true', 'validator': frontend.validate_boolean}), ('Disable compound enumerators for nested enumerated lists. ' - 'This is the default.', + '(default)', ['--no-compound-enumerators'], {'action': 'store_false', 'dest': 'compound_enumerators'}), ('Enable section ("." subsection ...) prefixes for compound ' - 'enumerators. This has no effect without --compound-enumerators.' - 'Default: disabled.', + '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. ' - 'This is the default.', + ('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 is "-".', + 'for compound enumerated lists. Default: "-".', ['--section-enumerator-separator'], {'default': '-', 'metavar': '<char>'}), - ('When possibile, use the specified environment for literal-blocks. ' - 'Default is quoting of whitespace and special chars.', + ('When possible, use the specified environment for literal-blocks. ' + 'Default: "" (fall back to "alltt").', ['--literal-block-env'], {'default': ''}), - ('When possibile, use verbatim for literal-blocks. ' - 'Compatibility alias for "--literal-block-env=verbatim".', + ('Deprecated alias for "--literal-block-env=verbatim".', ['--use-verbatim-when-possible'], - {'default': 0, 'action': 'store_true', + {'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". ' + 'above and below the table and below the header, or "borderless". ' 'Default: "standard"', ['--table-style'], {'default': ['standard'], @@ -190,9 +197,9 @@ class Writer(writers.Writer): 'validator': frontend.validate_comma_separated_list, 'choices': table_style_values}), ('LaTeX graphicx package option. ' - 'Possible values are "dvips", "pdftex". "auto" includes LaTeX code ' - 'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. ' - 'Default is no option.', + 'Possible values are "dvipdfmx", "dvips", "dvisvgm", ' + '"luatex", "pdftex", and "xetex".' + 'Default: "".', ['--graphicx-option'], {'default': ''}), ('LaTeX font encoding. ' @@ -204,22 +211,53 @@ class Writer(writers.Writer): 'hyperreferences. Specify "ref*" or "pageref*" to get the section ' 'number or the page number.', ['--reference-label'], - {'default': None, }), + {'default': ''}), ('Specify style and database for bibtex, for example ' '"--use-bibtex=mystyle,mydb1,mydb2".', ['--use-bibtex'], - {'default': None, }), - ),) + {'default': ''}), + ('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}), + ),) settings_defaults = {'sectnum_depth': 0 # updated by SectNum transform } config_section = 'latex2e writer' - config_section_dependencies = ('writers',) + config_section_dependencies = ('writers', 'latex writers') head_parts = ('head_prefix', 'requirements', 'latex_preamble', - 'stylesheet', 'fallbacks', 'pdfsetup', - 'title', 'subtitle', 'titledata') - visitor_attributes = head_parts + ('body_pre_docinfo', 'docinfo', + 'stylesheet', 'fallbacks', 'pdfsetup', 'titledata') + visitor_attributes = head_parts + ('title', 'subtitle', + 'body_pre_docinfo', 'docinfo', 'dedication', 'abstract', 'body') output = None @@ -245,11 +283,12 @@ class Writer(writers.Writer): setattr(self, part, getattr(visitor, part)) # get template string from file try: - template_file = open(self.document.settings.template, 'rb') + template_file = open(self.document.settings.template, + encoding='utf8') except IOError: template_file = open(os.path.join(self.default_template_path, - self.document.settings.template), 'rb') - template = string.Template(unicode(template_file.read(), 'utf-8')) + self.document.settings.template), encoding= 'utf8') + template = string.Template(template_file.read()) template_file.close() # fill template self.assemble_parts() # create dictionary of parts @@ -360,7 +399,7 @@ class Babel(object): # zh-Latn: Chinese Pinyin } # normalize (downcase) keys - language_codes = dict([(k.lower(), v) for (k,v) in language_codes.items()]) + language_codes = dict([(k.lower(), v) for (k, v) in language_codes.items()]) warn_msg = 'Language "%s" not supported by LaTeX (babel)' @@ -453,8 +492,7 @@ class SortableDict(dict): """ def sortedkeys(self): """Return sorted list of keys""" - keys = self.keys() - keys.sort() + keys = sorted(self.keys()) return keys def sortedvalues(self): @@ -474,130 +512,14 @@ class SortableDict(dict): class PreambleCmds(object): """Building blocks for the latex preamble.""" -PreambleCmds.abstract = r""" -% abstract title -\providecommand*{\DUtitleabstract}[1]{\centerline{\textbf{#1}}}""" - -PreambleCmds.admonition = 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.caption = r"""% configure caption layout -## \usepackage{caption} -## \captionsetup{singlelinecheck=false}% no exceptions for one-liners""" +# Requirements and Setup PreambleCmds.color = r"""\usepackage{color}""" -PreambleCmds.docinfo = r""" -% docinfo (width of docinfo table) -\DUprovidelength{\DUdocinfowidth}{0.9\linewidth}""" -# PreambleCmds.docinfo._depends = 'providelength' - -PreambleCmds.dedication = r""" -% dedication topic -\providecommand*{\DUCLASSdedication}{% - \renewenvironment{quote}{\begin{center}}{\end{center}}% -}""" - -PreambleCmds.duclass = r""" -% 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]% - {\def\DocutilsClassFunctionName{DUCLASS#1}% arg cannot be used in end-part of environment. - \csname \DocutilsClassFunctionName \endcsname}% - {\csname end\DocutilsClassFunctionName \endcsname}% -\fi""" - -PreambleCmds.error = r""" -% error admonition title -\providecommand*{\DUtitleerror}[1]{\DUtitle{\color{red}#1}}""" - -PreambleCmds.fieldlist = r""" -% fieldlist environment -\ifthenelse{\isundefined{\DUfieldlist}}{ - \newenvironment{DUfieldlist}% - {\quote\description} - {\enddescription\endquote} -}{}""" - -PreambleCmds.float_settings = r"""\usepackage{float} % float configuration +PreambleCmds.float = r"""\usepackage{float} % extended float configuration \floatplacement{figure}{H} % place figures here definitely""" -PreambleCmds.footnotes = r"""% numeric or symbol footnotes with hyperlinks -\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% -}""" - -PreambleCmds.graphicx_auto = r"""% Check output format -\ifx\pdftexversion\undefined - \usepackage{graphicx} -\else - \usepackage[pdftex]{graphicx} -\fi""" - -PreambleCmds.highlight_rules = r"""% 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}}""" - -PreambleCmds.inline = r""" -% inline markup (custom roles) -% \DUrole{#1}{#2} tries \DUrole#1{#2} -\providecommand*{\DUrole}[2]{% - % backwards compatibility: try \docutilsrole#1{#2} - \ifcsname docutilsrole#1\endcsname% - \csname docutilsrole#1\endcsname{#2}% - \else - \csname DUrole#1\endcsname{#2}% - \fi% -}""" - -PreambleCmds.legend = r""" -% legend environment -\ifthenelse{\isundefined{\DUlegend}}{ - \newenvironment{DUlegend}{\small}{} -}{}""" - -PreambleCmds.lineblock = r""" -% lineblock 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} -}{}""" -# PreambleCmds.lineblock._depends = 'providelength' - -PreambleCmds.linking = r""" -%% hyperlinks: +PreambleCmds.linking = r"""%% hyperlinks: \ifthenelse{\isundefined{\hypersetup}}{ \usepackage[%s]{hyperref} \usepackage{bookmark} @@ -607,70 +529,41 @@ PreambleCmds.linking = r""" PreambleCmds.minitoc = r"""%% local table of contents \usepackage{minitoc}""" -PreambleCmds.optionlist = r""" -% optionlist environment -\providecommand*{\DUoptionlistlabel}[1]{\bf #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} -}{}""" -# PreambleCmds.optionlist._depends = 'providelength' - -PreambleCmds.providelength = r""" -% providelength (provide a length variable and set default, if it is new) -\providecommand*{\DUprovidelength}[2]{ - \ifthenelse{\isundefined{#1}}{\newlength{#1}\setlength{#1}{#2}}{} -}""" - -PreambleCmds.rubric = r""" -% rubric (informal heading) -\providecommand*{\DUrubric}[1]{% - \subsubsection*{\centering\textit{\textmd{#1}}}}""" - -PreambleCmds.sidebar = r""" -% sidebar (text outside the main text flow) -\providecommand{\DUsidebar}[1]{% - \begin{center} - \colorbox[gray]{0.80}{\parbox{0.9\linewidth}{#1}} - \end{center} -}""" - -PreambleCmds.subtitle = r""" -% subtitle (for topic/sidebar) -\providecommand*{\DUsubtitle}[1]{\par\emph{#1}\smallskip}""" - -PreambleCmds.documentsubtitle = r""" -% subtitle (in document title) -\providecommand*{\DUdocumentsubtitle}[1]{{\large #1}}""" - PreambleCmds.table = r"""\usepackage{longtable,ltcaption,array} \setlength{\extrarowheight}{2pt} \newlength{\DUtablewidth} % internal use in tables""" -# Options [force,almostfull] prevent spurious error messages, see -# de.comp.text.tex/2005-12/msg01855 -PreambleCmds.textcomp = """\ -\\usepackage{textcomp} % text symbol macros""" +PreambleCmds.table_columnwidth = r"""\newcommand{\DUcolumnwidth}[1]{\dimexpr#1\DUtablewidth-2\tabcolsep\relax}""" -PreambleCmds.textsubscript = r""" -% text mode subscript -\ifx\textsubscript\undefined - \usepackage{fixltx2e} % since 2015 loaded by default -\fi""" +PreambleCmds.textcomp = r"""\usepackage{textcomp} % text symbol macros""" +# TODO? Options [force,almostfull] prevent spurious error messages, +# see de.comp.text.tex/2005-12/msg01855 -PreambleCmds.titlereference = r""" -% titlereference role -\providecommand*{\DUroletitlereference}[1]{\textsl{#1}}""" +# backwards compatibility definitions -PreambleCmds.title = r""" +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: @@ -681,13 +574,47 @@ PreambleCmds.title = r""" \fi }""" -PreambleCmds.transition = r""" -% transition (break, fancybreak, anonymous section) -\providecommand*{\DUtransition}{% - \hspace*{\fill}\hrulefill\hspace*{\fill} - \vskip 0.5\baselineskip +PreambleCmds.toc_list = r""" +\providecommand*{\DUCLASScontents}{% + \renewenvironment{itemize}% + {\begin{list}{}{\setlength{\partopsep}{0pt} + \setlength{\parsep}{0pt}} + }% + {\end{list}}% }""" +## 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() + +_du_sty = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'docutils.sty') +with open(_du_sty, encoding='utf8') 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 # ------------------- @@ -698,165 +625,169 @@ class CharMaps(object): # characters that need escaping even in `alltt` environments: alltt = { - ord('\\'): ur'\textbackslash{}', - ord('{'): ur'\{', - ord('}'): ur'\}', + ord('\\'): u'\\textbackslash{}', + ord('{'): u'\\{', + ord('}'): u'\\}', } # characters that normally need escaping: special = { - ord('#'): ur'\#', - ord('$'): ur'\$', - ord('%'): ur'\%', - ord('&'): ur'\&', - ord('~'): ur'\textasciitilde{}', - ord('_'): ur'\_', - ord('^'): ur'\textasciicircum{}', + ord('#'): u'\\#', + ord('$'): u'\\$', + ord('%'): u'\\%', + ord('&'): u'\\&', + ord('~'): u'\\textasciitilde{}', + ord('_'): u'\\_', + ord('^'): u'\\textasciicircum{}', # straight double quotes are 'active' in many languages - ord('"'): ur'\textquotedbl{}', + ord('"'): u'\\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('['): ur'{[}', - ord(']'): ur'{]}', + ord('['): u'{[}', + ord(']'): u'{]}', # the soft hyphen is unknown in 8-bit text # and not properly handled by XeTeX - 0x00AD: ur'\-', # SOFT HYPHEN + 0x00AD: u'\\-', # 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: ur'\leavevmode\nobreak\vadjust{}~' - 0x2000: ur'\enskip', # EN QUAD - 0x2001: ur'\quad', # EM QUAD - 0x2002: ur'\enskip', # EN SPACE - 0x2003: ur'\quad', # EM SPACE - 0x2008: ur'\,', # PUNCTUATION SPACE - 0x200b: ur'\hspace{0pt}', # ZERO WIDTH SPACE - 0x202F: ur'\,', # NARROW NO-BREAK SPACE - # 0x02d8: ur'\\u{ }', # BREVE - 0x2011: ur'\hbox{-}', # NON-BREAKING HYPHEN - 0x212b: ur'\AA', # ANGSTROM SIGN - 0x21d4: ur'\ensuremath{\Leftrightarrow}', + # 0x00A0: u'\\leavevmode\\nobreak\\vadjust{}~' + 0x2000: u'\\enskip', # EN QUAD + 0x2001: u'\\quad', # EM QUAD + 0x2002: u'\\enskip', # EN SPACE + 0x2003: u'\\quad', # EM SPACE + 0x2008: u'\\,', # PUNCTUATION SPACE + 0x200b: u'\\hspace{0pt}', # ZERO WIDTH SPACE + 0x202F: u'\\,', # NARROW NO-BREAK SPACE + # 0x02d8: u'\\\u{ }', # BREVE + 0x2011: u'\\hbox{-}', # NON-BREAKING HYPHEN + 0x212b: u'\\AA', # ANGSTROM SIGN + 0x21d4: u'\\ensuremath{\\Leftrightarrow}', # LEFT RIGHT DOUBLE ARROW + 0x2260: u'\\ensuremath{\\neq}', # NOT EQUAL TO + 0x2261: u'\\ensuremath{\\equiv}', # IDENTICAL TO + 0x2264: u'\\ensuremath{\\le}', # LESS-THAN OR EQUAL TO + 0x2265: u'\\ensuremath{\\ge}', # GREATER-THAN OR EQUAL TO # Docutils footnote symbols: - 0x2660: ur'\ensuremath{\spadesuit}', - 0x2663: ur'\ensuremath{\clubsuit}', - 0xfb00: ur'ff', # LATIN SMALL LIGATURE FF - 0xfb01: ur'fi', # LATIN SMALL LIGATURE FI - 0xfb02: ur'fl', # LATIN SMALL LIGATURE FL - 0xfb03: ur'ffi', # LATIN SMALL LIGATURE FFI - 0xfb04: ur'ffl', # LATIN SMALL LIGATURE FFL + 0x2660: u'\\ensuremath{\\spadesuit}', + 0x2663: u'\\ensuremath{\\clubsuit}', + 0xfb00: u'ff', # LATIN SMALL LIGATURE FF + 0xfb01: u'fi', # LATIN SMALL LIGATURE FI + 0xfb02: u'fl', # LATIN SMALL LIGATURE FL + 0xfb03: u'ffi', # LATIN SMALL LIGATURE FFI + 0xfb04: u'ffl', # LATIN SMALL LIGATURE FFL } # Unicode chars that are recognized by LaTeX's utf8 encoding utf8_supported_unicode = { - 0x00A0: ur'~', # NO-BREAK SPACE - 0x00AB: ur'\guillemotleft{}', # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK - 0x00bb: ur'\guillemotright{}', # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - 0x200C: ur'\textcompwordmark{}', # ZERO WIDTH NON-JOINER - 0x2013: ur'\textendash{}', - 0x2014: ur'\textemdash{}', - 0x2018: ur'\textquoteleft{}', - 0x2019: ur'\textquoteright{}', - 0x201A: ur'\quotesinglbase{}', # SINGLE LOW-9 QUOTATION MARK - 0x201C: ur'\textquotedblleft{}', - 0x201D: ur'\textquotedblright{}', - 0x201E: ur'\quotedblbase{}', # DOUBLE LOW-9 QUOTATION MARK - 0x2030: ur'\textperthousand{}', # PER MILLE SIGN - 0x2031: ur'\textpertenthousand{}', # PER TEN THOUSAND SIGN - 0x2039: ur'\guilsinglleft{}', - 0x203A: ur'\guilsinglright{}', - 0x2423: ur'\textvisiblespace{}', # OPEN BOX - 0x2020: ur'\dag{}', - 0x2021: ur'\ddag{}', - 0x2026: ur'\dots{}', - 0x2122: ur'\texttrademark{}', + 0x00A0: u'~', # NO-BREAK SPACE + 0x00AB: u'\\guillemotleft{}', # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x00bb: u'\\guillemotright{}', # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x200C: u'\\textcompwordmark{}', # ZERO WIDTH NON-JOINER + 0x2013: u'\\textendash{}', + 0x2014: u'\\textemdash{}', + 0x2018: u'\\textquoteleft{}', + 0x2019: u'\\textquoteright{}', + 0x201A: u'\\quotesinglbase{}', # SINGLE LOW-9 QUOTATION MARK + 0x201C: u'\\textquotedblleft{}', + 0x201D: u'\\textquotedblright{}', + 0x201E: u'\\quotedblbase{}', # DOUBLE LOW-9 QUOTATION MARK + 0x2030: u'\\textperthousand{}', # PER MILLE SIGN + 0x2031: u'\\textpertenthousand{}', # PER TEN THOUSAND SIGN + 0x2039: u'\\guilsinglleft{}', + 0x203A: u'\\guilsinglright{}', + 0x2423: u'\\textvisiblespace{}', # OPEN BOX + 0x2020: u'\\dag{}', + 0x2021: u'\\ddag{}', + 0x2026: u'\\dots{}', + 0x2122: u'\\texttrademark{}', } # recognized with 'utf8', if textcomp is loaded textcomp = { # Latin-1 Supplement - 0x00a2: ur'\textcent{}', # ¢ CENT SIGN - 0x00a4: ur'\textcurrency{}', # ¤ CURRENCY SYMBOL - 0x00a5: ur'\textyen{}', # ¥ YEN SIGN - 0x00a6: ur'\textbrokenbar{}', # ¦ BROKEN BAR - 0x00a7: ur'\textsection{}', # § SECTION SIGN - 0x00a8: ur'\textasciidieresis{}', # ¨ DIAERESIS - 0x00a9: ur'\textcopyright{}', # © COPYRIGHT SIGN - 0x00aa: ur'\textordfeminine{}', # ª FEMININE ORDINAL INDICATOR - 0x00ac: ur'\textlnot{}', # ¬ NOT SIGN - 0x00ae: ur'\textregistered{}', # ® REGISTERED SIGN - 0x00af: ur'\textasciimacron{}', # ¯ MACRON - 0x00b0: ur'\textdegree{}', # ° DEGREE SIGN - 0x00b1: ur'\textpm{}', # ± PLUS-MINUS SIGN - 0x00b2: ur'\texttwosuperior{}', # ² SUPERSCRIPT TWO - 0x00b3: ur'\textthreesuperior{}', # ³ SUPERSCRIPT THREE - 0x00b4: ur'\textasciiacute{}', # ´ ACUTE ACCENT - 0x00b5: ur'\textmu{}', # µ MICRO SIGN - 0x00b6: ur'\textparagraph{}', # ¶ PILCROW SIGN # != \textpilcrow - 0x00b9: ur'\textonesuperior{}', # ¹ SUPERSCRIPT ONE - 0x00ba: ur'\textordmasculine{}', # º MASCULINE ORDINAL INDICATOR - 0x00bc: ur'\textonequarter{}', # 1/4 FRACTION - 0x00bd: ur'\textonehalf{}', # 1/2 FRACTION - 0x00be: ur'\textthreequarters{}', # 3/4 FRACTION - 0x00d7: ur'\texttimes{}', # × MULTIPLICATION SIGN - 0x00f7: ur'\textdiv{}', # ÷ DIVISION SIGN + 0x00a2: u'\\textcent{}', # ¢ CENT SIGN + 0x00a4: u'\\textcurrency{}', # ¤ CURRENCY SYMBOL + 0x00a5: u'\\textyen{}', # ¥ YEN SIGN + 0x00a6: u'\\textbrokenbar{}', # ¦ BROKEN BAR + 0x00a7: u'\\textsection{}', # § SECTION SIGN + 0x00a8: u'\\textasciidieresis{}', # ¨ DIAERESIS + 0x00a9: u'\\textcopyright{}', # © COPYRIGHT SIGN + 0x00aa: u'\\textordfeminine{}', # ª FEMININE ORDINAL INDICATOR + 0x00ac: u'\\textlnot{}', # ¬ NOT SIGN + 0x00ae: u'\\textregistered{}', # ® REGISTERED SIGN + 0x00af: u'\\textasciimacron{}', # ¯ MACRON + 0x00b0: u'\\textdegree{}', # ° DEGREE SIGN + 0x00b1: u'\\textpm{}', # ± PLUS-MINUS SIGN + 0x00b2: u'\\texttwosuperior{}', # ² SUPERSCRIPT TWO + 0x00b3: u'\\textthreesuperior{}', # ³ SUPERSCRIPT THREE + 0x00b4: u'\\textasciiacute{}', # ´ ACUTE ACCENT + 0x00b5: u'\\textmu{}', # µ MICRO SIGN + 0x00b6: u'\\textparagraph{}', # ¶ PILCROW SIGN # != \textpilcrow + 0x00b9: u'\\textonesuperior{}', # ¹ SUPERSCRIPT ONE + 0x00ba: u'\\textordmasculine{}', # º MASCULINE ORDINAL INDICATOR + 0x00bc: u'\\textonequarter{}', # 1/4 FRACTION + 0x00bd: u'\\textonehalf{}', # 1/2 FRACTION + 0x00be: u'\\textthreequarters{}', # 3/4 FRACTION + 0x00d7: u'\\texttimes{}', # × MULTIPLICATION SIGN + 0x00f7: u'\\textdiv{}', # ÷ DIVISION SIGN # others - 0x0192: ur'\textflorin{}', # LATIN SMALL LETTER F WITH HOOK - 0x02b9: ur'\textasciiacute{}', # MODIFIER LETTER PRIME - 0x02ba: ur'\textacutedbl{}', # MODIFIER LETTER DOUBLE PRIME - 0x2016: ur'\textbardbl{}', # DOUBLE VERTICAL LINE - 0x2022: ur'\textbullet{}', # BULLET - 0x2032: ur'\textasciiacute{}', # PRIME - 0x2033: ur'\textacutedbl{}', # DOUBLE PRIME - 0x2035: ur'\textasciigrave{}', # REVERSED PRIME - 0x2036: ur'\textgravedbl{}', # REVERSED DOUBLE PRIME - 0x203b: ur'\textreferencemark{}', # REFERENCE MARK - 0x203d: ur'\textinterrobang{}', # INTERROBANG - 0x2044: ur'\textfractionsolidus{}', # FRACTION SLASH - 0x2045: ur'\textlquill{}', # LEFT SQUARE BRACKET WITH QUILL - 0x2046: ur'\textrquill{}', # RIGHT SQUARE BRACKET WITH QUILL - 0x2052: ur'\textdiscount{}', # COMMERCIAL MINUS SIGN - 0x20a1: ur'\textcolonmonetary{}', # COLON SIGN - 0x20a3: ur'\textfrenchfranc{}', # FRENCH FRANC SIGN - 0x20a4: ur'\textlira{}', # LIRA SIGN - 0x20a6: ur'\textnaira{}', # NAIRA SIGN - 0x20a9: ur'\textwon{}', # WON SIGN - 0x20ab: ur'\textdong{}', # DONG SIGN - 0x20ac: ur'\texteuro{}', # EURO SIGN - 0x20b1: ur'\textpeso{}', # PESO SIGN - 0x20b2: ur'\textguarani{}', # GUARANI SIGN - 0x2103: ur'\textcelsius{}', # DEGREE CELSIUS - 0x2116: ur'\textnumero{}', # NUMERO SIGN - 0x2117: ur'\textcircledP{}', # SOUND RECORDING COYRIGHT - 0x211e: ur'\textrecipe{}', # PRESCRIPTION TAKE - 0x2120: ur'\textservicemark{}', # SERVICE MARK - 0x2122: ur'\texttrademark{}', # TRADE MARK SIGN - 0x2126: ur'\textohm{}', # OHM SIGN - 0x2127: ur'\textmho{}', # INVERTED OHM SIGN - 0x212e: ur'\textestimated{}', # ESTIMATED SYMBOL - 0x2190: ur'\textleftarrow{}', # LEFTWARDS ARROW - 0x2191: ur'\textuparrow{}', # UPWARDS ARROW - 0x2192: ur'\textrightarrow{}', # RIGHTWARDS ARROW - 0x2193: ur'\textdownarrow{}', # DOWNWARDS ARROW - 0x2212: ur'\textminus{}', # MINUS SIGN - 0x2217: ur'\textasteriskcentered{}', # ASTERISK OPERATOR - 0x221a: ur'\textsurd{}', # SQUARE ROOT - 0x2422: ur'\textblank{}', # BLANK SYMBOL - 0x25e6: ur'\textopenbullet{}', # WHITE BULLET - 0x25ef: ur'\textbigcircle{}', # LARGE CIRCLE - 0x266a: ur'\textmusicalnote{}', # EIGHTH NOTE - 0x26ad: ur'\textmarried{}', # MARRIAGE SYMBOL - 0x26ae: ur'\textdivorced{}', # DIVORCE SYMBOL - 0x27e8: ur'\textlangle{}', # MATHEMATICAL LEFT ANGLE BRACKET - 0x27e9: ur'\textrangle{}', # MATHEMATICAL RIGHT ANGLE BRACKET + 0x0192: u'\\textflorin{}', # LATIN SMALL LETTER F WITH HOOK + 0x02b9: u'\\textasciiacute{}', # MODIFIER LETTER PRIME + 0x02ba: u'\\textacutedbl{}', # MODIFIER LETTER DOUBLE PRIME + 0x2016: u'\\textbardbl{}', # DOUBLE VERTICAL LINE + 0x2022: u'\\textbullet{}', # BULLET + 0x2032: u'\\textasciiacute{}', # PRIME + 0x2033: u'\\textacutedbl{}', # DOUBLE PRIME + 0x2035: u'\\textasciigrave{}', # REVERSED PRIME + 0x2036: u'\\textgravedbl{}', # REVERSED DOUBLE PRIME + 0x203b: u'\\textreferencemark{}', # REFERENCE MARK + 0x203d: u'\\textinterrobang{}', # INTERROBANG + 0x2044: u'\\textfractionsolidus{}', # FRACTION SLASH + 0x2045: u'\\textlquill{}', # LEFT SQUARE BRACKET WITH QUILL + 0x2046: u'\\textrquill{}', # RIGHT SQUARE BRACKET WITH QUILL + 0x2052: u'\\textdiscount{}', # COMMERCIAL MINUS SIGN + 0x20a1: u'\\textcolonmonetary{}', # COLON SIGN + 0x20a3: u'\\textfrenchfranc{}', # FRENCH FRANC SIGN + 0x20a4: u'\\textlira{}', # LIRA SIGN + 0x20a6: u'\\textnaira{}', # NAIRA SIGN + 0x20a9: u'\\textwon{}', # WON SIGN + 0x20ab: u'\\textdong{}', # DONG SIGN + 0x20ac: u'\\texteuro{}', # EURO SIGN + 0x20b1: u'\\textpeso{}', # PESO SIGN + 0x20b2: u'\\textguarani{}', # GUARANI SIGN + 0x2103: u'\\textcelsius{}', # DEGREE CELSIUS + 0x2116: u'\\textnumero{}', # NUMERO SIGN + 0x2117: u'\\textcircledP{}', # SOUND RECORDING COPYRIGHT + 0x211e: u'\\textrecipe{}', # PRESCRIPTION TAKE + 0x2120: u'\\textservicemark{}', # SERVICE MARK + 0x2122: u'\\texttrademark{}', # TRADE MARK SIGN + 0x2126: u'\\textohm{}', # OHM SIGN + 0x2127: u'\\textmho{}', # INVERTED OHM SIGN + 0x212e: u'\\textestimated{}', # ESTIMATED SYMBOL + 0x2190: u'\\textleftarrow{}', # LEFTWARDS ARROW + 0x2191: u'\\textuparrow{}', # UPWARDS ARROW + 0x2192: u'\\textrightarrow{}', # RIGHTWARDS ARROW + 0x2193: u'\\textdownarrow{}', # DOWNWARDS ARROW + 0x2212: u'\\textminus{}', # MINUS SIGN + 0x2217: u'\\textasteriskcentered{}', # ASTERISK OPERATOR + 0x221a: u'\\textsurd{}', # SQUARE ROOT + 0x2422: u'\\textblank{}', # BLANK SYMBOL + 0x25e6: u'\\textopenbullet{}', # WHITE BULLET + 0x25ef: u'\\textbigcircle{}', # LARGE CIRCLE + 0x266a: u'\\textmusicalnote{}', # EIGHTH NOTE + 0x26ad: u'\\textmarried{}', # MARRIAGE SYMBOL + 0x26ae: u'\\textdivorced{}', # DIVORCE SYMBOL + 0x27e8: u'\\textlangle{}', # MATHEMATICAL LEFT ANGLE BRACKET + 0x27e9: u'\\textrangle{}', # MATHEMATICAL RIGHT ANGLE BRACKET } # Unicode chars that require a feature/package to render pifont = { - 0x2665: ur'\ding{170}', # black heartsuit - 0x2666: ur'\ding{169}', # black diamondsuit - 0x2713: ur'\ding{51}', # check mark - 0x2717: ur'\ding{55}', # check mark + 0x2665: u'\\ding{170}', # black heartsuit + 0x2666: u'\\ding{169}', # black diamondsuit + 0x2713: u'\\ding{51}', # check mark + 0x2717: u'\\ding{55}', # check mark } # TODO: greek alphabet ... ? # see also LaTeX codec @@ -886,15 +817,28 @@ class DocumentClass(object): """ if level <= len(self.sections): return self.sections[level-1] - else: # unsupported levels - return 'DUtitle[section%s]' % roman.toRoman(level) + # 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(object): """Manage a table while traversing. - Maybe change to a mixin defining the visit/departs, but then - class Table internal variables are in the Translator. - Table style might be :standard: horizontal and vertical lines @@ -903,18 +847,15 @@ class Table(object): :nolines: alias for borderless :colwidths-auto: column widths determined by LaTeX - :colwidths-given: use colum widths from rST source """ def __init__(self, translator, latex_type): self._translator = translator self._latex_type = latex_type - self._open = False - # miscellaneous attributes - self._attrs = {} - self._col_width = [] + self.legacy_column_widths = False + + self.close() + self._colwidths = [] self._rowspan = [] - self.stubs = [] - self.colwidths_auto = False self._in_thead = 0 def open(self): @@ -923,6 +864,7 @@ class Table(object): self.caption = [] self._attrs = {} self._in_head = False # maybe context with search + def close(self): self._open = False self._col_specs = None @@ -934,18 +876,20 @@ class Table(object): def is_open(self): return self._open - def set_table_style(self, table_style, classes): + 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 table_style+classes - if cls in ('standard','booktabs','borderless', 'nolines')] - try: - self.borders = borders[-1] - except IndexError: - self.borders = 'standard' - self.colwidths_auto = (('colwidths-auto' in classes - and 'colwidths-given' not in table_style) - or ('colwidths-auto' in table_style - and ('colwidths-given' not in classes))) + 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: @@ -953,9 +897,10 @@ class Table(object): return('longtable*') return self._latex_type - def set(self,attr,value): + def set(self, attr, value): self._attrs[attr] = value - def get(self,attr): + + def get(self, attr): if attr in self._attrs: return self._attrs[attr] return None @@ -966,14 +911,26 @@ class Table(object): return '' # horizontal lines are drawn below a row, - def get_opening(self): - align_map = {'left': 'l', - 'center': 'c', - 'right': 'r'} - align = align_map.get(self.get('align') or 'center') - opening = [r'\begin{%s}[%s]' % (self.get_latex_type(), align)] + 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: - opening.insert(0, r'\setlength{\DUtablewidth}{\linewidth}') + 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): @@ -983,6 +940,8 @@ class Table(object): # 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): @@ -992,53 +951,67 @@ class Table(object): def get_colspecs(self, node): """Return column specification for longtable. - - Assumes reST line length being 80 characters. - Table width is hairy. - - === === - ABC DEF - === === - - usually gets to narrow, therefore we add 1 (fiddlefactor). """ bar = self.get_vertical_bar() - self._rowspan= [0] * len(self._col_specs) - self._col_width = [] + self._rowspan = [0] * len(self._col_specs) if self.colwidths_auto: - latex_table_spec = (bar+'l')*len(self._col_specs) - return latex_table_spec+bar - width = 80 - total_width = 0.0 - # first see if we get too wide. - for node in self._col_specs: - colwidth = float(node['colwidth']+1) / width - total_width += colwidth - # donot make it full linewidth - factor = 0.93 - if total_width > 1.0: - factor /= total_width - latex_table_spec = '' - for node in self._col_specs: - colwidth = factor * float(node['colwidth']+1) / width - self._col_width.append(colwidth+0.005) - latex_table_spec += '%sp{%.3f\\DUtablewidth}' % (bar, colwidth+0.005) - return latex_table_spec+bar + 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: - return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row] + 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: - mc_width = sum([width - for width in ([self._col_width[start + co] - for co in range (len_)])]) - return 'p{%.2f\\DUtablewidth}' % mc_width + multicol_width = sum([width + for width in ([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' @@ -1073,11 +1046,17 @@ class Table(object): if 1 == self._translator.thead_depth(): a.append('\\endfirsthead\n') else: + n_c = len(self._col_specs) a.append('\\endhead\n') - a.append(r'\multicolumn{%d}{c}' % len(self._col_specs) + - r'{\hfill ... continued on next page} \\') - a.append('\n\\endfoot\n\\endlastfoot\n') - # for longtable one could add firsthead, foot and lastfoot + # 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 @@ -1105,17 +1084,17 @@ class Table(object): c_start = rowspans.pop() except: break - cline += '\\cline{%d-%d}\n' % (c_start,c_start) + cline += '\\cline{%d-%d}\n' % (c_start, c_start) res.append(cline) return res - def set_rowspan(self,cell,value): + def set_rowspan(self, cell, value): try: self._rowspan[cell] = value except: pass - def get_rowspan(self,cell): + def get_rowspan(self, cell): try: return self._rowspan[cell] except: @@ -1170,7 +1149,6 @@ class LaTeXTranslator(nodes.NodeVisitor): # ------------------- has_latex_toc = False # is there a toc in the doc? (needed by minitoc) - is_toc_list = False # is the current bullet_list a ToC? section_level = 0 # Flags to encode(): @@ -1183,7 +1161,7 @@ class LaTeXTranslator(nodes.NodeVisitor): alltt = False # inside `alltt` environment def __init__(self, document, babel_class=Babel): - nodes.NodeVisitor.__init__(self, document) + nodes.NodeVisitor.__init__(self, document) # TODO: use super() # Reporter # ~~~~~~~~ self.warn = self.document.reporter.warning @@ -1192,10 +1170,28 @@ class LaTeXTranslator(nodes.NodeVisitor): # Settings # ~~~~~~~~ self.settings = settings = document.settings + # warn of deprecated settings and changing defaults: + if settings.use_latex_citations is None: + 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 1.2. ' + '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.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 @@ -1205,18 +1201,18 @@ class LaTeXTranslator(nodes.NodeVisitor): self.section_enumerator_separator = ( settings.section_enumerator_separator.replace('_', r'\_')) # literal blocks: - self.literal_block_env = 'alltt' + self.literal_block_env = '' self.literal_block_options = '' - if settings.literal_block_env != '': + if settings.literal_block_env: (none, self.literal_block_env, self.literal_block_options, - none ) = re.split(r'(\w+)(.*)', settings.literal_block_env) + none) = re.split(r'(\w+)(.*)', settings.literal_block_env) elif settings.use_verbatim_when_possible: self.literal_block_env = 'verbatim' - # + if self.settings.use_bibtex: - self.bibtex = self.settings.use_bibtex.split(',',1) + self.bibtex = self.settings.use_bibtex.split(',', 1) # TODO avoid errors on not declared citations. else: self.bibtex = None @@ -1235,17 +1231,11 @@ class LaTeXTranslator(nodes.NodeVisitor): # graphic package options: if self.settings.graphicx_option == '': self.graphicx_package = r'\usepackage{graphicx}' - elif self.settings.graphicx_option.lower() == 'auto': - self.graphicx_package = PreambleCmds.graphicx_auto else: self.graphicx_package = (r'\usepackage[%s]{graphicx}' % self.settings.graphicx_option) - # footnotes: + # footnotes: TODO: implement LaTeX footnotes self.docutils_footnotes = settings.docutils_footnotes - # @@ table_style: list of values from fixed set: warn? - # for s in self.settings.table_style: - # if s not in Writer.table_style_values: - # self.warn('Ignoring value "%s" in "table-style" setting.' %s) # Output collection stacks # ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1284,9 +1274,10 @@ class LaTeXTranslator(nodes.NodeVisitor): self.date = [] # PDF properties: pdftitle, pdfauthor - # TODO?: pdfcreator, pdfproducer, pdfsubject, pdfkeywords - self.pdfinfo = [] 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. @@ -1303,7 +1294,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self._bibitems = [] - # object for a table while proccessing. + # object for a table while processing. self.table_stack = [] self.active_table = Table(self, 'longtable') @@ -1341,8 +1332,21 @@ class LaTeXTranslator(nodes.NodeVisitor): # 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 = [sheet for sheet in stylesheet_list + if sheet != 'docutils'] + if self.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 utils.get_stylesheet_list(settings)] + for path in stylesheet_list] # PDF setup if self.hyperlink_color in ('0', 'false', 'False', ''): @@ -1356,9 +1360,6 @@ class LaTeXTranslator(nodes.NodeVisitor): # LaTeX Toc # include all supported sections in toc and PDF bookmarks # (or use documentclass-default (as currently))? - ## if self.use_latex_toc: - ## self.requirements['tocdepth'] = (r'\setcounter{tocdepth}{%d}' % - ## len(self.d_class.sections)) # Section numbering if settings.sectnum_xform: # section numbering by Docutils @@ -1377,16 +1378,8 @@ class LaTeXTranslator(nodes.NodeVisitor): # 4 paragraph # 5 subparagraph if secnumdepth is not None: - # limit to supported levels - secnumdepth = min(secnumdepth, len(self.d_class.sections)) - # adjust to document class and use_part_section settings - if 'chapter' in self.d_class.sections: - secnumdepth -= 1 - if self.d_class.sections[0] == 'part': - secnumdepth -= 1 - PreambleCmds.secnumdepth = \ - r'\setcounter{secnumdepth}{%d}' % secnumdepth - + 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): @@ -1410,11 +1403,11 @@ class LaTeXTranslator(nodes.NodeVisitor): if is_package: path = base + '.sty' # ensure extension try: - content = io.FileInput(source_path=path, + content = docutils.io.FileInput(source_path=path, encoding='utf-8').read() self.settings.record_dependencies.add(path) - except IOError, err: - msg = u"Cannot embed stylesheet '%s':\n %s." % ( + except IOError as err: + msg = u'Cannot embed stylesheet %r:\n %s.' % ( path, SafeString(err.strerror)) self.document.reporter.error(msg) return '% ' + msg.replace('\n', '\n% ') @@ -1434,7 +1427,7 @@ class LaTeXTranslator(nodes.NodeVisitor): path = utils.relative_path(self.settings._destination, path) return cmd % path - def to_latex_encoding(self,docutils_encoding): + def to_latex_encoding(self, docutils_encoding): """Translate docutils encoding name into LaTeX's. Default method is remove "-" and "_" chars from docutils_encoding. @@ -1493,7 +1486,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if not self.alltt: table.update(CharMaps.special) # keep the underscore in citation references - if self.inside_citation_reference_label: + 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: @@ -1505,14 +1498,18 @@ class LaTeXTranslator(nodes.NodeVisitor): # the backslash doesn't work, so we use a mirrored slash. # \reflectbox is provided by graphicx: self.requirements['graphicx'] = self.graphicx_package - table[ord('\\')] = ur'\reflectbox{/}' + table[ord('\\')] = u'\\reflectbox{/}' # * ``< | >`` come out as different chars (except for cmtt): else: - table[ord('|')] = ur'\textbar{}' - table[ord('<')] = ur'\textless{}' - table[ord('>')] = ur'\textgreater{}' + table[ord('|')] = u'\\textbar{}' + table[ord('<')] = u'\\textless{}' + table[ord('>')] = u'\\textgreater{}' if self.insert_non_breaking_blanks: - table[ord(' ')] = ur'~' + table[ord(' ')] = u'~' + # 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')] = u'~' * self.settings.tab_width # Unicode replacements for 8-bit tex engines (not required with XeTeX/LuaTeX): if not self.is_xetex: if not self.latex_encoding.startswith('utf8'): @@ -1523,11 +1520,11 @@ class LaTeXTranslator(nodes.NodeVisitor): # Characters that require a feature/package to render for ch in text: cp = ord(ch) - if cp in CharMaps.textcomp: + 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 + # preamble-definitions for unsupported Unicode characters elif (self.latex_encoding == 'utf8' and cp in CharMaps.unsupported_unicode): self.requirements['_inputenc'+str(cp)] = ( @@ -1580,18 +1577,24 @@ class LaTeXTranslator(nodes.NodeVisitor): 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 explicitely + # 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): + 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. """ - labels = ['\\label{%s}' % id for id in node.get('ids', [])] + 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): @@ -1622,8 +1625,12 @@ class LaTeXTranslator(nodes.NodeVisitor): 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: - self.fallbacks['DUclass'] = PreambleCmds.duclass + if not self.fallback_stylesheet: + self.fallbacks['DUclass'] = PreambleCmds.duclass self.out.append('\\begin{DUclass}{%s}\n' % cls) def duclass_close(self, node): @@ -1632,10 +1639,13 @@ class LaTeXTranslator(nodes.NodeVisitor): if cls.startswith('language-'): language = self.babel.language_name(cls[9:]) if language: - self.babel.otherlanguages[language] = True self.out.append('\\end{selectlanguage}\n') + elif (isinstance(node, nodes.table) + and cls in Writer.table_style_values + ['colwidths-given']): + pass else: - self.fallbacks['DUclass'] = PreambleCmds.duclass + if not self.fallback_stylesheet: + self.fallbacks['DUclass'] = PreambleCmds.duclass self.out.append('\\end{DUclass}\n') def push_output_collector(self, new_out): @@ -1645,6 +1655,29 @@ class LaTeXTranslator(nodes.NodeVisitor): 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 # --------------- @@ -1675,18 +1708,31 @@ class LaTeXTranslator(nodes.NodeVisitor): self.depart_docinfo_item(node) def visit_admonition(self, node): - self.fallbacks['admonition'] = PreambleCmds.admonition - if 'error' in node['classes']: - self.fallbacks['error'] = PreambleCmds.error # strip the generic 'admonition' from the list of classes node['classes'] = [cls for cls in node['classes'] if cls != 'admonition'] - self.out.append('\n\\DUadmonition[%s]{' % ','.join(node['classes'])) + 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=None): - self.out.append('}\n') + 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): @@ -1701,45 +1747,34 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_block_quote(self, node): self.duclass_open(node) - self.out.append( '\\begin{quote}') + self.out.append('\\begin{quote}') def depart_block_quote(self, node): - self.out.append( '\\end{quote}\n') + self.out.append('\\end{quote}\n') self.duclass_close(node) def visit_bullet_list(self, node): self.duclass_open(node) - if self.is_toc_list: - self.out.append( '\\begin{list}{}{}' ) - else: - self.out.append( '\\begin{itemize}' ) + self.out.append('\\begin{itemize}') def depart_bullet_list(self, node): - if self.is_toc_list: - self.out.append( '\\end{list}\n' ) - else: - self.out.append( '\\end{itemize}\n' ) + self.out.append('\\end{itemize}\n') self.duclass_close(node) def visit_superscript(self, node): self.out.append(r'\textsuperscript{') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) def depart_superscript(self, node): - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) self.out.append('}') def visit_subscript(self, node): - self.fallbacks['textsubscript'] = PreambleCmds.textsubscript self.out.append(r'\textsubscript{') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) def depart_subscript(self, node): - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) self.out.append('}') def visit_caption(self, node): @@ -1749,28 +1784,26 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('}\n') def visit_title_reference(self, node): - self.fallbacks['titlereference'] = PreambleCmds.titlereference + if not self.fallback_stylesheet: + self.fallbacks['titlereference'] = PreambleCmds.titlereference self.out.append(r'\DUroletitlereference{') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) def depart_title_reference(self, node): - if node['classes']: - self.depart_inline(node) - self.out.append( '}' ) + self.depart_inline(node) + self.out.append('}') def visit_citation(self, node): - # TODO maybe use cite bibitems - if self._use_latex_citations: + if self.use_latex_citations: self.push_output_collector([]) else: - # TODO: do we need these? ## 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: + if self.use_latex_citations: + # TODO: normalize label label = self.out[0] text = ''.join(self.out[1:]) self._bibitems.append([label, text]) @@ -1779,14 +1812,14 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\\end{figure}\n') def visit_citation_reference(self, node): - if self._use_latex_citations: + if self.use_latex_citations: if not self.inside_citation_reference_label: self.out.append(r'\cite{') self.inside_citation_reference_label = 1 else: - assert self.body[-1] in (' ', '\n'),\ + assert self.out[-1] in (' ', '\n'),\ 'unexpected non-whitespace while in reference label' - del self.body[-1] + del self.out[-1] else: href = '' if 'refid' in node: @@ -1796,16 +1829,15 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\\hyperlink{%s}{[' % href) def depart_citation_reference(self, node): - if self._use_latex_citations: + # TODO: normalize labels + if self.use_latex_citations: followup_citation = False # check for a following citation separated by a space or newline - next_siblings = node.traverse(descend=False, siblings=True, - include_self=False) - if len(next_siblings) > 1: - next = next_siblings[0] - if (isinstance(next, nodes.Text) and - next.astext() in (' ', '\n')): - if next_siblings[1].__class__ == node.__class__: + 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(',') @@ -1816,10 +1848,10 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append(']}') def visit_classifier(self, node): - self.out.append( '(\\textbf{' ) + self.out.append('(\\textbf{') def depart_classifier(self, node): - self.out.append( '})' ) + self.out.append('})') def visit_colspec(self, node): self.active_table.visit_colspec(node) @@ -1885,10 +1917,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_definition_list(self, node): self.duclass_open(node) - self.out.append( '\\begin{description}\n' ) + self.out.append('\\begin{description}\n') def depart_definition_list(self, node): - self.out.append( '\\end{description}\n' ) + self.out.append('\\end{description}\n') self.duclass_close(node) def visit_definition_list_item(self, node): @@ -1912,8 +1944,9 @@ class LaTeXTranslator(nodes.NodeVisitor): if self.docinfo: # tabularx: automatic width of columns, no page breaks allowed. self.requirements['tabularx'] = r'\usepackage{tabularx}' - self.fallbacks['_providelength'] = PreambleCmds.providelength - self.fallbacks['docinfo'] = PreambleCmds.docinfo + 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' @@ -1922,8 +1955,6 @@ class LaTeXTranslator(nodes.NodeVisitor): '\\end{center}\n') def visit_docinfo_item(self, node, name): - if name == 'author': - self.pdfauthor.append(self.attval(node.astext())) if self.use_latex_docinfo: if name in ('author', 'organization', 'contact', 'address'): # We attach these to the last author. If any of them precedes @@ -1965,7 +1996,9 @@ class LaTeXTranslator(nodes.NodeVisitor): # titled document? if (self.use_latex_docinfo or len(node) and isinstance(node[0], nodes.title)): - self.title_labels += self.ids_to_labels(node, set_anchor=False) + protect = (self.settings.documentclass == 'memoir') + self.title_labels += self.ids_to_labels(node, set_anchor=False, + protect=protect) def depart_document(self, node): # Complete header with information gained from walkabout @@ -1989,14 +2022,12 @@ class LaTeXTranslator(nodes.NodeVisitor): # 'author', 'organization', 'contact', 'address' and 'date') if self.title or ( self.use_latex_docinfo and (self.author_stack or self.date)): - # with the default template, titledata is written to the preamble - self.titledata.append('%%% Title Data') # \title (empty \title prevents error with \maketitle) + title = [''.join(self.title)] if self.title: - self.title.insert(0, '\\phantomsection%\n ') - title = [''.join(self.title)] + self.title_labels + title += self.title_labels if self.subtitle: - title += [r'\\ % subtitle', + title += [r'\\', r'\DUdocumentsubtitle{%s}' % ''.join(self.subtitle) ] + self.subtitle_labels self.titledata.append(r'\title{%s}' % '%\n '.join(title)) @@ -2012,7 +2043,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # * bibliography # TODO insertion point of bibliography should be configurable. - if self._use_latex_citations and len(self._bibitems)>0: + if self.use_latex_citations and len(self._bibitems)>0: if not self.bibtex: widest_label = '' for bi in self._bibitems: @@ -2022,7 +2053,7 @@ class LaTeXTranslator(nodes.NodeVisitor): widest_label) for bi in self._bibitems: # cite_key: underscores must not be escaped - cite_key = bi[0].replace(r'\_','_') + cite_key = bi[0].replace(r'\_', '_') self.out.append('\\bibitem[%s]{%s}{%s}\n' % (bi[0], cite_key, bi[1])) self.out.append('\\end{thebibliography}\n') @@ -2036,12 +2067,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_emphasis(self, node): self.out.append('\\emph{') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) def depart_emphasis(self, node): - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) self.out.append('}') # Append column delimiters and advance column counter, @@ -2101,8 +2130,10 @@ class LaTeXTranslator(nodes.NodeVisitor): else: self.context.append('') - # if line ends with '{', mask line break to prevent spurious whitespace - if not self.active_table.colwidths_auto and self.out[-1].endswith("{"): + # 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 @@ -2127,11 +2158,12 @@ class LaTeXTranslator(nodes.NodeVisitor): 'upperalpha':'Alph', 'lowerroman':'roman', 'upperroman':'Roman'} - # the 4 default LaTeX enumeration labels: präfix, enumtype, suffix, - labels = [('', 'arabic', '.'), # 1. - ('(', 'alph', ')'), # (a) - ('', 'roman', '.'), # i. - ('', 'Alph', '.')] # A. + # default LaTeX enumeration labels: + default_labels = [# (präfix, enumtype, suffix) + ('', 'arabic', '.'), # 1. + ('(', 'alph', ')'), # (a) + ('', 'roman', '.'), # i. + ('', 'Alph', '.')] # A. prefix = '' if self.compound_enumerators: @@ -2142,11 +2174,9 @@ class LaTeXTranslator(nodes.NodeVisitor): ) + self.section_enumerator_separator if self._enumeration_counters: prefix += self._enumeration_counters[-1] - # TODO: use LaTeX default for unspecified label-type? - # (needs change of parser) prefix += node.get('prefix', '') - enumtype = types[node.get('enumtype' '')] - suffix = node.get('suffix', '') + enumtype = types[node.get('enumtype', 'arabic')] + suffix = node.get('suffix', '.') enumeration_level = len(self._enumeration_counters)+1 counter_name = 'enum' + roman.toRoman(enumeration_level).lower() @@ -2157,7 +2187,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if enumeration_level <= 4: self.out.append('\\begin{enumerate}') if (prefix, enumtype, suffix - ) != labels[enumeration_level-1]: + ) != default_labels[enumeration_level-1]: self.out.append('\n\\renewcommand{\\label%s}{%s}' % (counter_name, label)) else: @@ -2167,7 +2197,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('{\\usecounter{%s}}' % counter_name) if 'start' in node: self.out.append('\n\\setcounter{%s}{%d}' % - (counter_name,node['start']-1)) + (counter_name, node['start']-1)) def depart_enumerated_list(self, node): @@ -2179,21 +2209,15 @@ class LaTeXTranslator(nodes.NodeVisitor): self._enumeration_counters.pop() def visit_field(self, node): - # real output is done in siblings: _argument, _body, _name + # output is done in field_body, field_name pass def depart_field(self, node): pass - ##self.out.append('%[depart_field]\n') - - def visit_field_argument(self, node): - self.out.append('%[visit_field_argument]\n') - - def depart_field_argument(self, node): - self.out.append('%[depart_field_argument]\n') def visit_field_body(self, node): - pass + 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: @@ -2202,7 +2226,8 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_field_list(self, node): self.duclass_open(node) if self.out is not self.docinfo: - self.fallbacks['fieldlist'] = PreambleCmds.fieldlist + if not self.fallback_stylesheet: + self.fallbacks['fieldlist'] = PreambleCmds.fieldlist self.out.append('\\begin{DUfieldlist}') def depart_field_list(self, node): @@ -2225,7 +2250,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append(':}]') def visit_figure(self, node): - self.requirements['float_settings'] = PreambleCmds.float_settings + 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) @@ -2237,8 +2262,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\\begin{figure} %% align = "%s"\n' % alignment) else: self.out.append('\\begin{figure}\n') - if node.get('ids'): - self.out += self.ids_to_labels(node) + ['\n'] + self.out += self.ids_to_labels(node, newline=True) def depart_figure(self, node): self.out.append('\\end{figure}\n') @@ -2259,7 +2283,8 @@ class LaTeXTranslator(nodes.NodeVisitor): except IndexError: backref = node['ids'][0] # no backref, use self-ref instead if self.docutils_footnotes: - self.fallbacks['footnotes'] = PreambleCmds.footnotes + if not self.fallback_stylesheet: + self.fallbacks['footnotes'] = PreambleCmds.footnotes num = node[0].astext() if self.settings.footnote_references == 'brackets': num = '[%s]' % num @@ -2267,10 +2292,10 @@ class LaTeXTranslator(nodes.NodeVisitor): (node['ids'][0], backref, self.encode(num))) if node['ids'] == node['names']: self.out += self.ids_to_labels(node) - # mask newline to prevent spurious whitespace if paragraph follows: - if node[1:] and isinstance(node[1], nodes.paragraph): + # prevent spurious whitespace if footnote starts with paragraph: + if len(node) > 1 and isinstance(node[1], nodes.paragraph): self.out.append('%') - ## else: # TODO: "real" LaTeX \footnote{}s + # TODO: "real" LaTeX \footnote{}s (see visit_footnotes_reference()) def depart_footnote(self, node): self.out.append('}\n') @@ -2282,22 +2307,22 @@ class LaTeXTranslator(nodes.NodeVisitor): elif 'refname' in node: href = self.document.nameids[node['refname']] # if not self.docutils_footnotes: - # TODO: insert footnote content at (or near) this place - # print "footnote-ref to", node['refid'] - # footnotes = (self.document.footnotes + - # self.document.autofootnotes + - # self.document.symbol_footnotes) - # for footnote in footnotes: - # # print footnote['ids'] - # if node.get('refid', '') in footnote['ids']: - # print 'matches', footnote['ids'] + # # 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: - self.fallbacks['footnotes'] = PreambleCmds.footnotes + if not self.fallback_stylesheet: + self.fallbacks['footnotes'] = PreambleCmds.footnotes self.out.append(r'\DUfootnotemark{%s}{%s}{' % (node['ids'][0], href)) self.context.append('}') @@ -2311,7 +2336,7 @@ class LaTeXTranslator(nodes.NodeVisitor): raise nodes.SkipNode else: assert isinstance(node.parent, nodes.citation) - if not self._use_latex_citations: + if not self.use_latex_citations: self.out.append(bracket) def visit_label(self, node): @@ -2338,11 +2363,12 @@ class LaTeXTranslator(nodes.NodeVisitor): self.pop_output_collector() def to_latex_length(self, length_str, pxunit=None): - """Convert `length_str` with rst lenght to LaTeX length + """Convert `length_str` with rst length to LaTeX length """ if pxunit is not None: - sys.stderr.write('deprecation warning: LaTeXTranslator.to_latex_length()' - ' option `pxunit` will be removed.') + warnings.warn('LaTeXTranslator.to_latex_length(): The optional ' + 'argument `pxunit` is ignored and will be removed ' + 'in Docutils 1.1', DeprecationWarning, stacklevel=2) match = re.match(r'(\d*\.?\d*)\s*(\S*)', length_str) if not match: return length_str @@ -2357,7 +2383,8 @@ class LaTeXTranslator(nodes.NodeVisitor): # 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. - self.fallbacks['_providelength'] = PreambleCmds.providelength + 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 @@ -2366,7 +2393,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.requirements['graphicx'] = self.graphicx_package attrs = node.attributes # Convert image URI to a local file path - imagepath = urllib.url2pathname(attrs['uri']).replace('\\', '/') + imagepath = url2pathname(attrs['uri']).replace('\\', '/') # alignment defaults: if not 'align' in attrs: # Set default align of image in a figure to 'center' @@ -2418,8 +2445,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.extend(post) def depart_image(self, node): - if node.get('ids'): - self.out += self.ids_to_labels(node) + ['\n'] + self.out += self.ids_to_labels(node, newline=True) def visit_inline(self, node): # <span>, i.e. custom roles for cls in node['classes']: @@ -2429,22 +2455,16 @@ class LaTeXTranslator(nodes.NodeVisitor): self.babel.otherlanguages[language] = True self.out.append(r'\foreignlanguage{%s}{' % language) else: - self.fallbacks['inline'] = PreambleCmds.inline + 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_interpreted(self, node): - # @@@ Incomplete, pending a proper implementation on the - # Parser/Reader end. - self.visit_literal(node) - - def depart_interpreted(self, node): - self.depart_literal(node) - def visit_legend(self, node): - self.fallbacks['legend'] = PreambleCmds.legend + if not self.fallback_stylesheet: + self.fallbacks['legend'] = PreambleCmds.legend self.out.append('\\begin{DUlegend}') def depart_legend(self, node): @@ -2457,8 +2477,9 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\n') def visit_line_block(self, node): - self.fallbacks['_providelength'] = PreambleCmds.providelength - self.fallbacks['lineblock'] = PreambleCmds.lineblock + 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' @@ -2481,18 +2502,17 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_literal(self, node): self.literal = True - if 'code' in node['classes'] and ( - self.settings.syntax_highlight != 'none'): + if ('code' in node['classes'] and + self.settings.syntax_highlight != 'none'): self.requirements['color'] = PreambleCmds.color - self.fallbacks['code'] = PreambleCmds.highlight_rules + if not self.fallback_stylesheet: + self.fallbacks['code'] = PreambleCmds.highlight_rules self.out.append('\\texttt{') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) def depart_literal(self, node): self.literal = False - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) self.out.append('}') # Literal blocks are used for '::'-prefixed literal-indented @@ -2502,7 +2522,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # # In both cases, we want to use a typewriter/monospaced typeface. # For "real" literal-blocks, we can use \verbatim, while for all - # the others we must use \mbox or \alltt. + # 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 @@ -2515,49 +2535,86 @@ class LaTeXTranslator(nodes.NodeVisitor): return (len(node) == 1) and isinstance(node[0], nodes.Text) def visit_literal_block(self, node): - """Render a literal block.""" - # environments and packages to typeset literal blocks - packages = {'alltt': r'\usepackage{alltt}', + """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}', - 'lstlisting': r'\usepackage{listings}', 'Verbatim': r'\usepackage{fancyvrb}', - # 'verbatim': '', 'verbatimtab': r'\usepackage{moreverb}'} - if node.get('ids'): - self.out += ['\n'] + self.ids_to_labels(node) - + 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) - if not self.active_table.is_open(): - # no quote inside tables, to avoid vertical space between - # table border and literal block. - # TODO: fails if normal text precedes the literal block. - # check parent node instead? + 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'] = '\n'.join(['', + r'% character width in monospaced font', + r'\newlength{\ttemwidth}', + r'\settowidth{\ttemwidth}{\ttfamily M}']) + 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') - if self.is_plaintext(node): - environment = self.literal_block_env - self.requirements['literal_block'] = packages.get(environment, '') - if environment == 'alltt': - self.alltt = True - else: - self.verbatim = True + # 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' % - (environment, self.literal_block_options)) - self.context.append('\n\\end{%s}' % environment) + (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 - if 'code' in node['classes'] and ( - self.settings.syntax_highlight != 'none'): - self.requirements['color'] = PreambleCmds.color - self.fallbacks['code'] = PreambleCmds.highlight_rules - self.out.append('{\\ttfamily \\raggedright \\noindent\n') - self.context.append('\n}') + # \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 @@ -2569,44 +2626,43 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append(self.context.pop()) self.duclass_close(node) - ## def visit_meta(self, node): - ## self.out.append('[visit_meta]\n') - # TODO: set keywords for pdf? - # But: - # The reStructuredText "meta" directive creates a "pending" node, - # which contains knowledge that the embedded "meta" node can only - # be handled by HTML-compatible writers. The "pending" node is - # resolved by the docutils.transforms.components.Filter transform, - # which checks that the calling writer supports HTML; if it doesn't, - # the "pending" node (and enclosed "meta" node) is removed from the - # document. - # --- docutils/docs/peps/pep-0258.html#transformer - - ## def depart_meta(self, node): - ## self.out.append('[depart_meta]\n') + 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""" - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) self.requirements['amsmath'] = r'\usepackage{amsmath}' math_code = node.astext().translate(unichar2tex.uni2tex_table) - if node.get('ids'): - math_code = '\n'.join([math_code] + self.ids_to_labels(node)) if math_env == '$': if self.alltt: - wrapper = ur'\(%s\)' + wrapper = ['\\(', '\\)'] else: - wrapper = u'$%s$' + wrapper = ['$', '$'] else: - wrapper = u'\n'.join(['%%', - r'\begin{%s}' % math_env, - '%s', - r'\end{%s}' % math_env]) - # print repr(wrapper), repr(math_code) - self.out.append(wrapper % math_code) - if node['classes']: - self.depart_inline(node) + 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 @@ -2630,7 +2686,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.context[-1] += 1 def visit_option_argument(self, node): - """Append the delimiter betweeen an option and its argument to body.""" + """Append the delimiter between an option and its argument to body.""" self.out.append(node.get('delimiter', ' ')) def depart_option_argument(self, node): @@ -2646,8 +2702,9 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('] ') def visit_option_list(self, node): - self.fallbacks['_providelength'] = PreambleCmds.providelength - self.fallbacks['optionlist'] = PreambleCmds.optionlist + if not self.fallback_stylesheet: + self.fallbacks['_providelength'] = PreambleCmds.providelength + self.fallbacks['optionlist'] = PreambleCmds.optionlist self.duclass_open(node) self.out.append('\\begin{DUoptionlist}') @@ -2677,16 +2734,18 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_paragraph(self, node): # insert blank line, unless - # * the paragraph is first in a list item or compound, + # * 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.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) and - not isinstance(node.parent[index - 1], nodes.compound)): + 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 @@ -2696,14 +2755,11 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\n') else: self.out.append('\n') - if node.get('ids'): - self.out += self.ids_to_labels(node) + ['\n'] - if node['classes']: - self.visit_inline(node) + self.out += self.ids_to_labels(node, newline=True) + self.visit_inline(node) def depart_paragraph(self, node): - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) if not self.active_table.colwidths_auto: self.out.append('\n') @@ -2719,17 +2775,16 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_raw(self, node): if not 'latex' in node.get('format', '').split(): raise nodes.SkipNode - if not self.is_inline(node): + if not (self.is_inline(node) + or isinstance(node.parent, nodes.compound)): self.out.append('\n') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) # append "as-is" skipping any LaTeX-encoding self.verbatim = True def depart_raw(self, node): self.verbatim = False - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) if not self.is_inline(node): self.out.append('\n') @@ -2747,9 +2802,9 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_reference(self, node): # We need to escape #, \, and % if we use the URL in a command. - special_chars = {ord('#'): ur'\#', - ord('%'): ur'\%', - ord('\\'): ur'\\', + special_chars = {ord('#'): u'\\#', + ord('%'): u'\\%', + ord('\\'): u'\\\\', } # external reference (URL) if 'refuri' in node: @@ -2791,13 +2846,14 @@ class LaTeXTranslator(nodes.NodeVisitor): self.depart_docinfo_item(node) def visit_rubric(self, node): - self.fallbacks['rubric'] = PreambleCmds.rubric - self.duclass_open(node) - self.out.append('\\DUrubric{') + 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') - self.duclass_close(node) def visit_section(self, node): self.section_level += 1 @@ -2814,7 +2870,8 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_sidebar(self, node): self.duclass_open(node) self.requirements['color'] = PreambleCmds.color - self.fallbacks['sidebar'] = PreambleCmds.sidebar + if not self.fallback_stylesheet: + self.fallbacks['sidebar'] = PreambleCmds.sidebar self.out.append('\\DUsidebar{') def depart_sidebar(self, node): @@ -2843,12 +2900,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_strong(self, node): self.out.append('\\textbf{') - if node['classes']: - self.visit_inline(node) + self.visit_inline(node) def depart_strong(self, node): - if node['classes']: - self.depart_inline(node) + self.depart_inline(node) self.out.append('}') def visit_substitution_definition(self, node): @@ -2860,15 +2915,19 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_subtitle(self, node): if isinstance(node.parent, nodes.document): self.push_output_collector(self.subtitle) - self.fallbacks['documentsubtitle'] = PreambleCmds.documentsubtitle - self.subtitle_labels += self.ids_to_labels(node, set_anchor=False) + if not self.fallback_stylesheet: + self.fallbacks['documentsubtitle'] = PreambleCmds.documentsubtitle + 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: - self.fallbacks['subtitle'] = PreambleCmds.subtitle - self.out.append('\n\\DUsubtitle[%s]{' % node.parent.tagname) + 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): @@ -2878,16 +2937,22 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_system_message(self, node): self.requirements['color'] = PreambleCmds.color - self.fallbacks['title'] = PreambleCmds.title + 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) - self.out.append('\n\\DUtitle[system-message]{system-message}\n') + 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' % + 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: @@ -2900,14 +2965,17 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_system_message(self, node): self.out.append(self.context.pop()) - self.depart_admonition() + 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') + 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 @@ -2916,10 +2984,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.d_class.section(self.section_level).find('paragraph') != -1): self.out.append('\\leavevmode') self.active_table.open() - self.active_table.set_table_style(self.settings.table_style, - node['classes']) - if 'align' in node: - self.active_table.set('align', node['align']) + 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([]) @@ -2928,7 +2993,16 @@ class LaTeXTranslator(nodes.NodeVisitor): # wrap content in the right environment: content = self.out self.pop_output_collector() - self.out.append('\n' + self.active_table.get_opening()) + try: + width = self.to_latex_length(node['width']) + except KeyError: + width = r'\linewidth' + # 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() @@ -2936,8 +3010,8 @@ class LaTeXTranslator(nodes.NodeVisitor): self.active_table = self.table_stack.pop() # Insert hyperlabel after (long)table, as # other places (beginning, caption) result in LaTeX errors. - if node.get('ids'): - self.out += self.ids_to_labels(node, set_anchor=False) + ['\n'] + self.out += self.ids_to_labels(node, set_anchor=False, newline=True) + self.duclass_close(node) def visit_target(self, node): # Skip indirect targets: @@ -2948,8 +3022,7 @@ class LaTeXTranslator(nodes.NodeVisitor): return self.out.append('%\n') # do we need an anchor (\phantomsection)? - set_anchor = not(isinstance(node.parent, nodes.caption) or - isinstance(node.parent, nodes.title)) + 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) @@ -2973,13 +3046,14 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\\item[{') def depart_term(self, node): - # \leavevmode results in a line break if the - # term is followed by an item list. - self.out.append('}] \leavevmode ') + 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 not isinstance(next_node, nodes.classifier): + self.out.append(self.term_postfix(next_node)) def visit_tgroup(self, node): - #self.out.append(self.starttag(node, 'colgroup')) - #self.context.append('</colgroup>\n') pass def depart_tgroup(self, node): @@ -2993,7 +3067,7 @@ class LaTeXTranslator(nodes.NodeVisitor): 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) + self.active_table.set('preamble written', 1) self.out.append(self.active_table.get_caption()) self.out.extend(self.active_table.visit_thead()) @@ -3007,7 +3081,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_title(self, node): """Append section and other titles.""" # Document title - if node.parent.tagname == 'document': + if isinstance(node.parent, nodes.document): self.push_output_collector(self.title) self.context.append('') self.pdfinfo.append(' pdftitle={%s},' % @@ -3016,11 +3090,14 @@ class LaTeXTranslator(nodes.NodeVisitor): elif (isinstance(node.parent, nodes.topic) or isinstance(node.parent, nodes.admonition) or isinstance(node.parent, nodes.sidebar)): - self.fallbacks['title'] = PreambleCmds.title - classes = ','.join(node.parent['classes']) - if not classes: - classes = node.tagname - self.out.append('\n\\DUtitle[%s]{' % classes) + 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): @@ -3030,105 +3107,131 @@ class LaTeXTranslator(nodes.NodeVisitor): else: if hasattr(PreambleCmds, 'secnumdepth'): self.requirements['secnumdepth'] = PreambleCmds.secnumdepth - section_name = self.d_class.section(self.section_level) + 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)) + section_name, section_title)) else: self.out.append(r'\%s{' % section_name) - if self.section_level > len(self.d_class.sections): - # section level not supported by LaTeX - self.fallbacks['title'] = PreambleCmds.title - # self.out.append('\\phantomsection%\n ') + # label and ToC entry: bookmark = [''] # add sections with unsupported level to toc and pdfbookmarks? - ## if self.section_level > len(self.d_class.sections): + ## 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') - - # MAYBE postfix paragraph and subparagraph with \leavemode to + 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) or - node.parent.tagname == 'document'): + if isinstance(node.parent, (nodes.table, nodes.document)): self.pop_output_collector() - def minitoc(self, node, title, depth): - """Generate a local table of contents with LaTeX package minitoc""" - section_name = self.d_class.section(self.section_level) - # name-prefix for current section level - minitoc_names = {'part': 'part', 'chapter': 'mini'} - if 'chapter' not in self.d_class.sections: - minitoc_names['section'] = 'sect' - try: - minitoc_name = minitoc_names[section_name] - except KeyError: # minitoc only supports part- and toplevel - self.warn('Skipping local ToC at %s level.\n' % section_name + - ' Feature not supported with option "use-latex-toc"', - base_node=node) + 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 - # Requirements/Setup - self.requirements['minitoc'] = PreambleCmds.minitoc - self.requirements['minitoc-'+minitoc_name] = (r'\do%stoc' % - minitoc_name) - # depth: (Docutils defaults to unlimited depth) + + # ToC by LaTeX + depth = node.get('depth', 0) maxdepth = len(self.d_class.sections) - self.requirements['minitoc-%s-depth' % minitoc_name] = ( - r'\mtcsetdepth{%stoc}{%d}' % (minitoc_name, maxdepth)) - # Process 'depth' argument (!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}' % - (minitoc_name, depth + offset[minitoc_name])) - # title: - self.out.append('\\mtcsettitle{%stoc}{%s}\n' % (minitoc_name, title)) - # the toc-generating command: - self.out.append('\\%stoc\n' % minitoc_name) + 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.out.append('\n') - self.out += self.ids_to_labels(node) - # add contents to PDF bookmarks sidebar - if isinstance(node.next_node(), nodes.title): - self.out.append('\n\\pdfbookmark[%d]{%s}{%s}' % - (self.section_level+1, - node.next_node().astext(), - node.get('ids', ['contents'])[0] - )) - if self.use_latex_toc: - title = '' - if isinstance(node.next_node(), nodes.title): - title = self.encode(node.pop(0).astext()) - depth = node.get('depth', 0) - if 'local' in node['classes']: - self.minitoc(node, title, depth) - return - if depth: - self.out.append('\\setcounter{tocdepth}{%d}\n' % depth) - if title != 'Contents': - self.out.append('\n\\renewcommand{\\contentsname}{%s}' % - title) - self.out.append('\n\\tableofcontents\n') - self.has_latex_toc = True - else: # Docutils generated contents list - # set flag for visit_bullet_list() and visit_title() - self.is_toc_list = True + self.visit_contents(node) elif ('abstract' in node['classes'] and self.settings.use_latex_abstract): self.push_output_collector(self.abstract) @@ -3138,28 +3241,34 @@ class LaTeXTranslator(nodes.NodeVisitor): else: # special topics: if 'abstract' in node['classes']: - self.fallbacks['abstract'] = PreambleCmds.abstract + 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']: - self.fallbacks['dedication'] = PreambleCmds.dedication + 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): - self.is_toc_list = False if ('abstract' in node['classes'] and self.settings.use_latex_abstract): self.out.append('\\end{abstract}\n') - elif not 'contents' in node['classes']: + 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): - self.fallbacks['transition'] = PreambleCmds.transition + if not self.fallback_stylesheet: + self.fallbacks['transition'] = PreambleCmds.transition self.out.append('\n%' + '_' * 75 + '\n') self.out.append('\\DUtransition\n') diff --git a/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex b/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex index 98f6396..8a39d80 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex @@ -6,9 +6,9 @@ $latex_preamble %%% User specified packages and stylesheets $stylesheet %%% Fallback definitions for Docutils-specific commands -$fallbacks$pdfsetup -$titledata +$fallbacks +$pdfsetup %%% Body \begin{document} -$body_pre_docinfo$docinfo$dedication$abstract$body +$titledata$body_pre_docinfo$docinfo$dedication$abstract$body \end{document} diff --git a/docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils-05-compat.sty b/docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils-05-compat.sty deleted file mode 100644 index cc6b6a8..0000000 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils-05-compat.sty +++ /dev/null @@ -1,738 +0,0 @@ -% ================================================================== -% Changes to the Docutils latex2e writer since version 0.5 -% ================================================================== -% -% A backwards compatibility style sheet -% ************************************* -% -% :Author: Guenter Milde -% :Contact: milde@users.sourceforge.net -% :Revision: $Revision: 6156 $ -% :Date: $Date: 2009-02-24 $ -% :Copyright: © 2009 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. -% -% :Abstract: This file documents changes and provides a style for best -% possible compatibility to the behaviour of the `latex2e` -% writer of Doctutils release 0.5. -% -% .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause -% -% :: - -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{docutils-05-compat} -[2009/03/26 v0.1 compatibility with rst2latex from Docutils 0.5] - -% .. contents:: -% :depth: 3 -% -% Usage -% ===== -% -% * To get an (almost) identic look for your old documents, -% place ``docutils-05-compat.sty`` in the TEXINPUT path (e.g. -% the current work directory) and pass the -% ``--stylesheet=docutils-05-compat`` option to ``rst2latex.py``. -% -% * To use your custom stylesheets without change, add them to the -% compatibility style, e.g. -% ``--stylesheet="docutils-05-compat,mystyle.tex``. -% -% .. tip:: As the changes include bug fixes that are partly reverted by this -% style, it is recommended to adapt the stylesheets to the new version or -% copy just the relevant parts of this style into them. -% -% Changes since 0.5 -% ================= -% -% Bugfixes -% -------- -% -% * Newlines around comments, targets and references prevent run-together -% paragraphs. -% -% + An image directive with hyperlink reference or target did not start a -% new paragraph (e.g. the first two image examples in -% standalone_rst_latex.tex). -% -% + Paragraphs were not separated if there was a (hyper) target definition -% inbetween. -% -% + Paragraphs did run together, if separated by a comment-paragraph in the -% rst source. -% -% * Fixed missing and spurious internal links/targets. -% Internal links now take you to the correct place. -% -% * Verbose and linked system messages. -% -% * `Figure and image alignment`_ now conforms to the rst definition. -% -% * Put `header and footer directive`__ content in \DUheader respective -% \DUfooter macros (ignored by the default style/template). -% -% (They were put inside hard-coded markup at the top/bottom of the document -% without an option to get them on every page.) -% -% __ ../ref/rst/directives.html#document-header-footer -% -% * Render doctest blocks as literal blocks (fixes bug [1586058] doctest block -% nested in admonition). I.e. -% -% + indent doctest blocks by nesting in a quote environment. This is also -% the rendering by the HTML writer (html4css2.css). -% + apply the ``--literal-block-env`` setting also to doctest blocks. -% -% .. warning:: -% (``--literal-block-env=verbatim`` and -% ``--literal-block-env=lstlistings`` fail with literal or doctest -% blocks nested in an admonition. -% -% * Two-way hyperlinked footnotes and support for symbol footnotes and -% ``--footnote-references=brackets`` with ``--use-latex-footnotes``. -% -% * The packages `fixltx2e` (providing LaTeX patches and the \textsubscript -% command) and `cmap` (including character maps in the generated PDF for -% better search and copy-and-paste operations) are now always loaded -% (configurable with custom templates_). -% -% Backwards compatibility: -% "Bug for bug compatibility" is not provided. -% -% -% New configuration setting defaults -% ---------------------------------- -% -% - font-encoding: "T1" (formerly implicitely set by 'ae'). -% - use-latex-toc: true (ToC with page numbers). -% - use-latex-footnotes: true (no mixup with figures). -% -% Backwards compatibility: -% Reset to the former defaults with: -% -% | font-encoding: '' -% | use-latex-toc: False -% | use-latex-footnotes: False -% -% (in the config file) or the command line options: -% -% ``--figure-footnotes --use-docutils-toc --font-encoding=''`` -% -% -% Cleaner LaTeX source -% -------------------- -% -% New features: -% * Remove redundant "double protection" from the encoding of the "special -% printing characters" and square brackets, e.g. ``\%`` instead of -% ``{\%}``. -% * Remove some spurious whitespace, e.g. ``\item [what:] -> \item[what:]``. -% * Use conventional style for "named" macros, e.g. ``\dots{}`` instead of -% ``{\dots}`` -% -% Backwards compatibility: -% Changes do not affect the output. -% -% -% LaTeX style sheets -% ------------------ -% -% New Feature: -% LaTeX packages can be used as ``--stylesheet`` argument without -% restriction. -% -% Implementation: -% Use ``\usepackage`` if style sheet ends with ``.sty`` or has no -% extension and ``\input`` else. -% -% Rationale: -% while ``\input`` works with extension as well as without extension, -% ``\usepackage`` expects the package name without extension. (The latex2e -% writer will strip a ``.sty`` extension.) -% -% -% Backwards compatibility: -% Up to Docutils 0.5, if no filename extension is given in the -% ``stylesheet`` argument, ``.tex`` is assumed (by latex). -% -% Since Docutils 0.6, a stylesheet without filename extension is assumed to -% be a LaTeX package (``*.sty``) and referenced with the ``\usepackage`` -% command. -% -% .. important:: -% Always specify the extension if you want the style sheet to be -% ``\input`` by LaTeX. -% -% -% Templates -% --------- -% -% New Feature: -% Advanced configuration via custom templates. -% -% Implementation: -% A ``--template`` option and config setting allows specification of a -% template file. -% -% See the `LaTeX writer documentation`__ for details. -% -% __ latex.html#templates -% -% -% Custom roles -% ------------ -% -% New Feature: failsave implementation -% As with classes to HTML objects, class arguments are silently ignored if -% there is no styling rule for this class in a custom style sheet. -% -% New Feature: custom roles based on standard roles -% As class support needs to be handled by the LaTeX writer, this feature was -% not present "automatically" (as in HTML). Modified visit/depart_*() -% methods for the standard roles now call visit/depart_inline() if there are -% class arguments to the node. -% -% Backwards compatibility: -% The implementation is fully backwards compatible. (SVN versions 5742 to -% 5861 contained an implementation that did not work with commands expecting -% an argument.) -% -% Length units -% ------------ -% -% New Features: -% 1. Add default unit if none given. -% A poll on docutils-users favoured ``bp`` (Big Point: 1 bp = 1/72 in). -% -% 2. Do not change ``px`` to ``pt``. -% -% 3. Lengths specified in the document with unit "pt" will be written with -% unit "bp" to the LaTeX source. -% -% Rationale: -% 1. prevent LaTeX error "missing unit". -% -% 2. ``px`` is a valid unit in pdftex since version 1.3.0 released on -% 2005-02-04: -% -% 1px defaults to 1bp (or 72dpi), but can be changed with the -% ``\pdfpxdimen`` primitive.:: - - \pdfpxdimen=1in % 1 dpi - \divide\pdfpxdimen by 96 % 96 dpi - -% -- http://www.tug.org/applications/pdftex/NEWS -% -% Modern TeX distributions use pdftex also for dvi generation (i.e. -% ``latex`` actually calls ``pdftex`` with some options). -% -% 3. In Docutils (as well as CSS) the unit symbol "pt" denotes the -% `Postscript point` or `DTP point` while LaTeX uses "pt" for the `LaTeX -% point`, which is unknown to Docutils and 0.3 % smaller. -% -% The `DTP point` is available in LaTeX as "bp" (big point): -% -% 1 pt = 1/72.25 in < 1 bp = 1/72 in -% -% -% Backwards compatibility: -% Images with width specification in ``px`` come out slightly (0.3 %) larger: -% -% 1 px = 1 bp = 1/72 in > 1 pt = 1/72.25 in -% -% This can be reset with :: - - \pdfpxdimen=1pt - -% .. caution:: It is impossible to revert the change of lengths specified with -% "pt" or without unit in a style sheet, however the 0.3 % change will be -% imperceptible in most cases. -% -% .. admonition:: Error ``illegal unit px`` -% -% The unit ``px`` is not defined in "pure" LaTeX, but introduced by the -% `pdfTeX` converter on 2005-02-04. `pdfTeX` is used in all modern LaTeX -% distributions (since ca. 2006) also for conversion into DVI. -% -% If you convert the LaTeX source with a legacy program, you might get the -% error ``illegal unit px``. -% -% If updating LaTeX is not an option, just remove the ``px`` from the length -% specification. HTML/CSS will default to ``px`` while the `latexe2` writer -% will add the fallback unit ``bp``. -% -% -% Font encoding -% ------------- -% -% New feature: -% Do not mix font-encoding and font settings: do not load the obsolete -% `ae` and `aeguill` packages unless explicitely required via the -% ``--stylesheet`` option. -% -% :font-encoding = "": do not load `ae` and `aeguill`, i.e. -% -% * do not change font settings, -% * do not use the fontenc package -% (implicitely loaded via `ae`), -% * use LaTeX default font encoding (OT1) -% -% :font-encoding = "OT1": load `fontenc` with ``\usepackage[OT1]{fontenc}`` -% -% Example: -% ``--font-encoding=LGR,T1`` becomes ``\usepackage[LGR,T1]{fontenc}`` -% (Latin, Latin-1 Supplement, and Greek) -% -% -% Backwards compatibility: -% Load the ae and aeguill packages if fontenc is not used. -% -% .. tip:: Using `ae` is not recommended. A similar look (but better -% implementation) can be achieved with the packages `lmodern`, `cmsuper`, -% or `cmlgr` all providing Computer Modern look-alikes in vector format and -% T1 encoding, e.g. ``--font-encoding=T1 --stylesheet=lmodern``. -% -% Sub- and superscript as text -% ---------------------------- -% -% New feature: -% Set sub- and superscript role argument in text mode not as math. -% -% Pass the role content to ``\textsubscript`` or ``\textsuperscript``. -% -% Backwards compatibility: -% The old implementation set the role content in Math mode, where -% -% * whitespace is ignored, -% * a different command set and font setting scheme is active, -% * Latin letters are typeset italic but numbers upright. -% -% Although it is possible to redefine ``\textsubscript`` and -% ``\textsuperscript`` to typeset the content in math-mode, this can lead to -% errors with certain input and is therefore not done in this style sheet. -% -% .. tip:: To get italic subscripts, define and use in your document -% `custom roles`_ like ``.. role:: sub(subscript)`` and -% ``.. role:: super(superscript)`` and define the "role commands":: - - \newcommand{\DUrolesub}{\itshape} - \newcommand{\DUrolesuper}{\itshape} - -% Alternatively, if you want all sub- and superscripts in italic, redefine -% the macros:: - - %% \let\DUsup\textsubscript - %% \let\DUsuper\textsuperscript - %% \renewcommand*{\textsubscript}{\DUsub\itshape} - %% \renewcommand*{\textsuperscript}{\DUsuper\itshape} - -% This is not fully backwards compatible, as it will also set numbers in -% italic shape and not ignore whitespace. -% -% Page layout -% ----------- -% -% New features: -% * Margins are configurable via the ``DIV=...`` document option. -% -% * The ``\raggedbottom`` setting is no longer inserted into the document. It -% is the default for article and report classes. If requested in combination -% with a book class, it can be given in a custom style sheet. -% -% Backwards compatibility: -% Up to version 0.5, use of `typearea` and a DIV setting of 12 were -% hard-coded into the latex2e writer :: - - \usepackage{typearea} - \typearea{12} - -% and the vertical alignment of lower boundary of the text area in book -% classes disabled via :: - - \raggedbottom - - -% ToC and section numbers -% ----------------------- -% -% Better conformance to Docutils specifications. -% -% New feature: -% * The "depth" argument of the "contents" and "sectnum" directives is -% respected. -% -% * section numbering independent of 'use-latex-toc': -% -% + sections are only numbered if there is a "sectnum" directive in the -% document -% -% + section numbering by LaTeX if the "sectnum_xforms" config setting is -% False. -% -% Backwards compatibility: -% -% The previous behaviour was to always number sections if 'use-latex-toc' is -% true, using the document class defaults. It cannot be restored -% universally, the following code sets the default values of the "article" -% document class:: - - \setcounter{secnumdepth}{3} - \setcounter{tocdepth}{3} - -% .. TODO or not to do? (Back-compatibility problems) -% * The default "depth" of the LaTeX-created ToC and the LaTeX section -% numbering is increased to the number of supported section levels. -% -% New feature: -% If 'use-latex-toc' is set, local tables of content are typeset using the -% 'minitoc' package (instead of being ignored). -% -% Backwards compatibility: -% Disable the creation of local ToCs (ignoring all special commands) by -% replacing ``\usepackage{minitoc} with ``\usepackage{mtcoff}``. -% -% -% Default font in admonitions and sidebar -% --------------------------------------- -% -% New feature: -% Use default font in admonitions and sidebar. -% -% Backward compatibility: -% See the fallback definitions for admonitions_, `topic title`_ and -% `sidebar`_. -% -% -% Figure placement -% ---------------- -% -% New feature: -% Use ``\floatplacement`` from the `float` package instead of -% "hard-coded" optional argument for the global setting. -% -% Default to ``\floatplacement{figure}{H}`` (here definitely). This -% corresponds most closely to the source and HTML placement (principle of -% least surprise). -% -% Backwards compatibility: -% Set the global default back to the previous used value:: - - \usepackage{float} - \floatplacement{figure}{htbp} % here, top, bottom, extra-page - - -% Figure and image alignment -% -------------------------- -% -% New features: -% -% a) Fix behaviour of 'align' argument to a figure (do not align figure -% contents). -% -% As the 'figwidth' argument is still ignored and the "natural width" of a -% figure in LaTeX is 100% \textwidth, setting the 'align' argument of a -% figure has currently no effect on the LaTeX output. -% -% b) Set default align of image in a figure to 'center'. -% -% c) Also center images that are wider than textwidth. -% -% d) Align images with class "align-[right|center|left]" (allows setting the -% alignment of an image in a figure). -% -% Backwards compatibility: -% There is no "automatic" way to reverse these changes via a style sheet. -% -% a) The alignment of the image can be set with the "align-left", -% "align-center" and "align-right" class arguments. -% -% As previously, the caption of a figure is aligned according to the -% document class -- configurable with a style sheet using the "caption" -% package. -% -% b) See a) -% -% c) Set the alignment of "oversized" images to "left" to get back the -% old placement. -% -% Shorter preamble -% ---------------- -% -% New feature: -% The document preamble is pruned to contain only relevant commands and -% settings. -% -% Packages that are no longer required -% ```````````````````````````````````` -% -% The following packages where required in pre-0.5 versions and still loaded -% with version 0.5:: - -\usepackage{shortvrb} -\usepackage{amsmath} - - -% Packages that are conditionally loaded -% `````````````````````````````````````` -% -% Additional to the `typearea` for `page layout`_, the following packages are -% only loaded if actually required by doctree elements: -% -% Tables -% ^^^^^^ -% -% Standard package for tables across several pages:: - -\usepackage{longtable} - -% Extra space between text in tables and the line above them -% ('array' is implicitely loaded by 'tabularx', see below):: - -\usepackage{array} -\setlength{\extrarowheight}{2pt} - -% Table cells spanning multiple rows:: - -\usepackage{multirow} - -% Docinfo -% ^^^^^^^ -% -% One-page tables with auto-width columns:: - -\usepackage{tabularx} - -% Images -% ^^^^^^ -% Include graphic files:: - -\usepackage{graphicx} - -% Problematic, Sidebar -% ^^^^^^^^^^^^^^^^^^^^ -% Set text and/or background colour, coloured boxes with ``\colorbox``:: - -\usepackage{color} - -% Floats for footnotes settings -% ````````````````````````````` -% -% Settings for the use of floats for footnotes are only included if -% -% * the option "use-latex-footnotes" is False, and -% * there is at least one footnote in the document. -% -% :: - -% begin: floats for footnotes tweaking. -\setlength{\floatsep}{0.5em} -\setlength{\textfloatsep}{\fill} -\addtolength{\textfloatsep}{3em} -\renewcommand{\textfraction}{0.5} -\renewcommand{\topfraction}{0.5} -\renewcommand{\bottomfraction}{0.5} -\setcounter{totalnumber}{50} -\setcounter{topnumber}{50} -\setcounter{bottomnumber}{50} -% end floats for footnotes - - -% Special lengths, commands, and environments -% ------------------------------------------- -% -% Removed definitions -% ``````````````````` -% -% admonition width -% ^^^^^^^^^^^^^^^^ -% The ``admonitionwith`` lenght is replaced by the more powerful -% ``\DUadmonition`` command (see admonitions_). -% -% Backwards compatibility: -% The default value (90 % of the textwidth) is unchanged. -% -% To configure the admonition width, you must redefine the ``DUadmonition`` -% command instead of changing the ``admonitionwith`` length value. -% -% -% Renamed definitions (now conditional) -% ````````````````````````````````````` -% -% The names for special doctree elements are now prefixed with ``DU``. -% -% Up to version 0.5, all definitions were included in the preamble (before the -% style sheet) of every document -- even if not used in the body. Since -% version 0.6, fallback definitions are included after the style sheet and -% only if required. -% -% Customization is done by an alternative definition in a style sheet with -% ``\newcommand`` instead of the former ``\renewcommand``. -% -% The following code provides the old definitions and maps them (or their -% custom variants) to the new interface. -% -% docinfo width -% ^^^^^^^^^^^^^ -% :: - -\newlength{\docinfowidth} -\setlength{\docinfowidth}{0.9\textwidth} - -\newlength{\DUdocinfowidth} -\AtBeginDocument{\setlength{\DUdocinfowidth}{\docinfowidth}} - -% line block -% ^^^^^^^^^^ -% :: - -\newlength{\lineblockindentation} -\setlength{\lineblockindentation}{2.5em} -\newenvironment{lineblock}[1] -{\begin{list}{} - {\setlength{\partopsep}{\parskip} - \addtolength{\partopsep}{\baselineskip} - \topsep0pt\itemsep0.15\baselineskip\parsep0pt - \leftmargin#1} - \raggedright} -{\end{list}} - -\newlength{\DUlineblockindent} -\AtBeginDocument{\setlength{\DUlineblockindent}{\lineblockindentation}} -\newenvironment{DUlineblock}[1] - {\begin{lineblock}{#1}} - {\end{lineblock}} - -% local line width -% ^^^^^^^^^^^^^^^^ -% -% The ``\locallinewidth`` length for internal use in tables is replaced -% by ``\DUtablewidth``. It was never intended for customization:: - -\newlength{\locallinewidth} - -% option lists -% ^^^^^^^^^^^^ -% :: - -\newcommand{\optionlistlabel}[1]{\bf #1 \hfill} -\newenvironment{optionlist}[1] -{\begin{list}{} - {\setlength{\labelwidth}{#1} - \setlength{\rightmargin}{1cm} - \setlength{\leftmargin}{\rightmargin} - \addtolength{\leftmargin}{\labelwidth} - \addtolength{\leftmargin}{\labelsep} - \renewcommand{\makelabel}{\optionlistlabel}} -}{\end{list}} - -\newcommand{\DUoptionlistlabel}{\optionlistlabel} -\newenvironment{DUoptionlist} - {\begin{optionlist}{3cm}} - {\end{optionlist}} - -% rubric -% ^^^^^^ -% Now less prominent (not bold, normal size) restore with:: - -\newcommand{\rubric}[1]{\subsection*{~\hfill {\it #1} \hfill ~}} -\newcommand{\DUrubric}[2][class-arg]{\rubric{#2}} - -% title reference role -% ^^^^^^^^^^^^^^^^^^^^ -% :: - -\newcommand{\titlereference}[1]{\textsl{#1}} -\newcommand{\DUroletitlereference}[1]{\titlereference{#1}} - - -% New definitions -% ``````````````` -% -% New Feature: -% Enable customization of some more Docutils elements with special commands -% -% :admonition: ``DUadmonition`` command (replacing ``\admonitionwidth``), -% :field list: ``DUfieldlist`` environment, -% :legend: ``DUlegend`` environment, -% :sidebar: ``\DUsidebar``, ``\DUtitle``, and -% ``DUsubtitle`` commands, -% :topic: ``\DUtopic`` and ``\DUtitle`` commands, -% :transition: ``\DUtransition`` command. -% :footnotes: ``\DUfootnotemark`` and ``\DUfootnotetext`` commands with -% hyperlink support using the Docutils-provided footnote label. -% -% Backwards compatibility: -% In most cases, the default definition corresponds to the previously used -% construct. The following definitions restore the old behaviour in case of -% changes. -% -% admonitions -% ^^^^^^^^^^^ -% Use sans-serif fonts:: - -\newcommand{\DUadmonition}[2][class-arg]{% - \begin{center} - \fbox{\parbox{0.9\textwidth}{\sffamily #2}} - \end{center} -} - -% dedication -% ^^^^^^^^^^ -% Do not center:: - -\newcommand{\DUtopicdedication}[1]{#1} - -% But center the title:: - -\newcommand*{\DUtitlededication}[1]{\centerline{\textbf{#1}}} - -% sidebar -% ^^^^^^^ -% Use sans-serif fonts, a frame, and a darker shade of grey:: - -\providecommand{\DUsidebar}[2][class-arg]{% - \begin{center} - \sffamily - \fbox{\colorbox[gray]{0.80}{\parbox{0.9\textwidth}{#2}}} - \end{center} -} - -% sidebar sub-title -% ^^^^^^^^^^^^^^^^^ -% Bold instead of emphasized:: - -\providecommand*{\DUsubtitlesidebar}[1]{\hspace*{\fill}\\ - \textbf{#1}\smallskip} - -% topic -% ^^^^^ -% No quote but normal text:: - -\newcommand{\DUtopic}[2][class-arg]{% - \ifcsname DUtopic#1\endcsname% - \csname DUtopic#1\endcsname{#2}% - \else - #2 - \fi -} - -% topic title -% ^^^^^^^^^^^ -% Title for "topics" (admonitions, sidebar). -% -% Larger font size:: - -\providecommand*{\DUtitletopic}[1]{\textbf{\large #1}\smallskip} - -% transition -% ^^^^^^^^^^ -% Do not add vertical space after the transition. :: - -\providecommand*{\DUtransition}[1][class-arg]{% - \hspace*{\fill}\hrulefill\hspace*{\fill}} diff --git a/docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils.sty b/docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils.sty new file mode 100644 index 0000000..52386bb --- /dev/null +++ b/docutils/src/main/resources/docutils/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/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex b/docutils/src/main/resources/docutils/docutils/writers/latex2e/titlingpage.tex similarity index 56% copy from docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex copy to docutils/src/main/resources/docutils/docutils/writers/latex2e/titlingpage.tex index 98f6396..c2d3fbe 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/titlingpage.tex @@ -1,5 +1,5 @@ -$head_prefix% generated by Docutils <http://docutils.sourceforge.net/> -\usepackage{cmap} % fix search and cut-and-paste in Acrobat +% generated by Docutils <http://docutils.sourceforge.net/> +$head_prefix $requirements %%% Custom LaTeX preamble $latex_preamble @@ -10,5 +10,9 @@ $fallbacks$pdfsetup $titledata %%% Body \begin{document} -$body_pre_docinfo$docinfo$dedication$abstract$body +\begin{titlingpage} +\thispagestyle{empty} +$body_pre_docinfo$docinfo$dedication$abstract +\end{titlingpage} +$body \end{document} diff --git a/docutils/src/main/resources/docutils/docutils/writers/manpage.py b/docutils/src/main/resources/docutils/docutils/writers/manpage.py index 287c6f2..20ac9ce 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/manpage.py +++ b/docutils/src/main/resources/docutils/docutils/writers/manpage.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: manpage.py 8116 2017-06-18 19:09:40Z milde $ +# $Id: manpage.py 8893 2021-11-18 19:09:57Z grubert $ # Author: Engelbert Gruber <grubert@users.sourceforge.net> # Copyright: This module is put into the public domain. @@ -37,7 +37,7 @@ and AUTHOR . -A unix-like system keeps an index of the DESCRIPTIONs, which is accesable +A unix-like system keeps an index of the DESCRIPTIONs, which is accessible by the command whatis or apropos. """ @@ -45,6 +45,10 @@ by the command whatis or apropos. __docformat__ = 'reStructuredText' import re +import sys + +if sys.version_info < (3, 0): + range = xrange # NOQA: F821 # flake8 do not check undefined name import docutils from docutils import nodes, writers, languages @@ -129,9 +133,9 @@ class Table(object): 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')): + 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')): + while cell_lines and cell_lines[-1] in ('\n', '.sp\n'): del cell_lines[-1] def as_list(self): text = ['.TS\n'] @@ -181,17 +185,18 @@ class Translator(nodes.NodeVisitor): # 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" : "", + "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._in_literal = False self.header_written = 0 @@ -206,27 +211,28 @@ class Translator(nodes.NodeVisitor): # # 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. # Hopefully ``C`` courier too. self.defs = { - 'indent' : ('.INDENT %.1f\n', '.UNINDENT\n'), - 'definition_list_item' : ('.TP', ''), - 'field_name' : ('.TP\n.B ', '\n'), - 'literal' : ('\\fB', '\\fP'), - 'literal_block' : ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'), + 'indent': ('.INDENT %.1f\n', '.UNINDENT\n'), + 'definition_list_item': ('.TP', ''), # paragraph with hanging tag + 'field_name': ('.TP\n.B ', '\n'), + 'literal': ('\\fB', '\\fP'), + 'literal_block': ('.sp\n.nf\n.ft C\n', '\n.ft P\n.fi\n'), - 'option_list_item' : ('.TP\n', ''), + 'option_list_item': ('.TP\n', ''), - 'reference' : (r'\fI\%', r'\fP'), + 'reference': (r'\fI\%', r'\fP'), 'emphasis': ('\\fI', '\\fP'), - 'strong' : ('\\fB', '\\fP'), - 'term' : ('\n.B ', '\n'), - 'title_reference' : ('\\fI', '\\fP'), + 'strong': ('\\fB', '\\fP'), + 'term': ('\n.B ', '\n'), + 'title_reference': ('\\fI', '\\fP'), - 'topic-title' : ('.SS ',), - 'sidebar-title' : ('.SS ',), + 'topic-title': ('.SS ',), + 'sidebar-title': ('.SS ',), - 'problematic' : ('\n.nf\n', '\n.fi\n'), + 'problematic': ('\n.nf\n', '\n.fi\n'), } # NOTE do not specify the newline before a dot-command, but ensure # it is there. @@ -255,10 +261,10 @@ class Translator(nodes.NodeVisitor): # ensure we get a ".TH" as viewers require it. self.append_header() # filter body - for i in xrange(len(self.body)-1, 0, -1): + 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 '): + 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'): @@ -278,12 +284,13 @@ class Translator(nodes.NodeVisitor): def visit_Text(self, node): text = node.astext() - text = text.replace('\\','\\e') + text = text.replace('\\', '\\e') replace_pairs = [ - (u'-', ur'\-'), - (u'\'', ur'\(aq'), - (u'´', ur'\''), - (u'`', ur'\(ga'), + (u'-', u'\\-'), + (u'\'', u'\\(aq'), + (u'´', u"\\'"), + (u'`', u'\\(ga'), + (u'"', u'\\(dq'), # double quotes are a problem on macro lines ] for (in_char, out_markup) in replace_pairs: text = text.replace(in_char, out_markup) @@ -300,21 +307,21 @@ class Translator(nodes.NodeVisitor): pass def list_start(self, node): - class enum_char(object): + class EnumChar(object): enum_style = { - 'bullet' : '\\(bu', - 'emdash' : '\\(em', + 'bullet': '\\(bu', + 'emdash': '\\(em', } def __init__(self, style): self._style = style - if node.has_key('start'): + if 'start' in node: self._cnt = node['start'] - 1 else: self._cnt = 0 self._indent = 2 if style == 'arabic': - # indentation depends on number of childrens + # indentation depends on number of children # and start value. self._indent = len(str(len(node.children))) self._indent += len(str(self._cnt)) + 1 @@ -327,7 +334,7 @@ class Translator(nodes.NodeVisitor): elif style.endswith('roman'): self._indent = 5 - def next(self): + def __next__(self): if self._style == 'bullet': return self.enum_style[self._style] elif self._style == 'emdash': @@ -345,15 +352,19 @@ class Translator(nodes.NodeVisitor): return res.lower() else: return "%d." % self._cnt + + if sys.version_info < (3, 0): + next = __next__ + def get_width(self): return self._indent def __repr__(self): return 'enum_style-%s' % list(self._style) - if node.has_key('enumtype'): - self._list_char.append(enum_char(node['enumtype'])) + if 'enumtype' in node: + self._list_char.append(EnumChar(node['enumtype'])) else: - self._list_char.append(enum_char('bullet')) + self._list_char.append(EnumChar('bullet')) if len(self._list_char) > 1: # indent nested lists self.indent(self._list_char[-2].get_width()) @@ -365,7 +376,7 @@ class Translator(nodes.NodeVisitor): self._list_char.pop() def header(self): - tmpl = (".TH %(title_upper)s %(manual_section)s" + tmpl = (".TH \"%(title_upper)s\" %(manual_section)s" " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" ".SH NAME\n" "%(title)s \\- %(subtitle)s\n") @@ -375,10 +386,11 @@ class Translator(nodes.NodeVisitor): """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(self.header()) self.head.append(MACRO_DEF) + self.head.append(self.header()) self.header_written = 1 def visit_address(self, node): @@ -439,7 +451,7 @@ class Translator(nodes.NodeVisitor): pass def visit_block_quote(self, node): - # BUG/HACK: indent alway uses the _last_ indention, + # BUG/HACK: indent always uses the _last_ indentation, # thus we need two of them. self.indent(BLOCKQOUTE_INDENT) self.indent(0) @@ -466,7 +478,7 @@ class Translator(nodes.NodeVisitor): depart_caution = depart_admonition def visit_citation(self, node): - num, text = node.astext().split(None, 1) + num = node.astext().split(None, 1)[0] num = num.strip() self.body.append('.IP [%s] 5\n' % num) @@ -574,7 +586,7 @@ class Translator(nodes.NodeVisitor): 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 postboned + # writing header is postponed self.header_written = 0 def depart_document(self, node): @@ -646,7 +658,7 @@ class Translator(nodes.NodeVisitor): def visit_field_body(self, node): if self._in_docinfo: - name_normalized = self._field_name.lower().replace(" ","_") + 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 @@ -711,7 +723,7 @@ class Translator(nodes.NodeVisitor): pass def visit_header(self, node): - raise NotImplementedError, node.astext() + raise NotImplementedError(node.astext()) def depart_header(self, node): pass @@ -808,7 +820,7 @@ class Translator(nodes.NodeVisitor): def visit_list_item(self, node): # man 7 man argues to use ".IP" instead of ".TP" self.body.append('.IP %s %d\n' % ( - self._list_char[-1].next(), + next(self._list_char[-1]), self._list_char[-1].get_width(),)) def depart_list_item(self, node): @@ -821,7 +833,7 @@ class Translator(nodes.NodeVisitor): self.body.append(self.defs['literal'][1]) def visit_literal_block(self, node): - # BUG/HACK: indent alway uses the _last_ indention, + # BUG/HACK: indent always uses the _last_ indentation, # thus we need two of them. self.indent(LITERAL_BLOCK_INDENT) self.indent(0) @@ -850,11 +862,12 @@ class Translator(nodes.NodeVisitor): def depart_math_block(self, node): self.depart_literal_block(node) - def visit_meta(self, node): - raise NotImplementedError, node.astext() + # <meta> shall become an optional standard node: + # def visit_meta(self, node): + # raise NotImplementedError(node.astext()) - def depart_meta(self, node): - pass + # def depart_meta(self, node): + # pass def visit_note(self, node): self.visit_admonition(node, 'note') @@ -890,7 +903,7 @@ class Translator(nodes.NodeVisitor): # options with parameter bold italic, .BI, -f file # # we do not know if .B or .BI - self.context.append('.B') # blind guess + self.context.append('.B ') # blind guess. Add blank for sphinx see docutils/bugs/380 self.context.append(len(self.body)) # to be able to insert later self.context.append(0) # option counter @@ -904,7 +917,10 @@ class Translator(nodes.NodeVisitor): def visit_option(self, node): # each form of the option will be presented separately if self.context[-1] > 0: - self.body.append('\\fP,\\fB ') + 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(' ') @@ -953,13 +969,16 @@ class Translator(nodes.NodeVisitor): # ``.PP`` : Start standard indented paragraph. # ``.LP`` : Start block paragraph, all except the first. # ``.P [type]`` : Start paragraph type. - # NOTE dont use paragraph starts because they reset indentation. + # 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): @@ -979,6 +998,8 @@ class Translator(nodes.NodeVisitor): self.body.append(self.defs['reference'][0]) def depart_reference(self, node): + # TODO check node text is different from refuri + #self.body.append("\n'UR " + node['refuri'] + "\n'UE\n") self.body.append(self.defs['reference'][1]) def visit_revision(self, node): @@ -1137,7 +1158,7 @@ class Translator(nodes.NodeVisitor): pass def depart_rubric(self, node): - pass + self.body.append('\n') def visit_transition(self, node): # .PP Begin a new paragraph and reset prevailing indent. diff --git a/docutils/src/main/resources/docutils/docutils/writers/odf_odt/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/odf_odt/__init__.py index efe7967..9364c2f 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/odf_odt/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/odf_odt/__init__.py @@ -1,83 +1,65 @@ -# $Id: __init__.py 8131 2017-07-03 22:06:53Z dkuhlman $ -# Author: Dave Kuhlman <dkuhlman@rexx.com> +# $Id: __init__.py 8889 2021-11-15 19:40:31Z 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. """ - -VERSION = '1.0a' +from __future__ import absolute_import __docformat__ = 'reStructuredText' -import sys +import copy +import itertools import os import os.path +import re +import subprocess +import sys import tempfile -import zipfile -from xml.dom import minidom import time -import re -import StringIO -import copy -import urllib2 -import itertools -import docutils +import weakref +from xml.etree import ElementTree as etree +from xml.dom import minidom +import zipfile + try: - import locale # module missing in Jython + import locale # module missing in Jython except ImportError: pass + +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 +if sys.version_info >= (3, 0): + from configparser import ConfigParser + from io import StringIO + from urllib.request import urlopen + from urllib.error import HTTPError +else: + from ConfigParser import ConfigParser + from StringIO import StringIO + from urllib2 import HTTPError + from urllib2 import urlopen + FileNotFoundError = OSError -IMAGE_NAME_COUNTER = itertools.count() -WhichElementTree = '' -try: - # 1. Try to use lxml. - #from lxml import etree - #WhichElementTree = 'lxml' - raise ImportError('Ignoring lxml') -except ImportError, e: - try: - # 2. Try to use ElementTree from the Python standard library. - from xml.etree import ElementTree as etree - WhichElementTree = 'elementtree' - except ImportError, e: - try: - # 3. Try to use a version of ElementTree installed as a separate - # product. - from elementtree import ElementTree as etree - WhichElementTree = 'elementtree' - except ImportError, e: - s1 = 'Must install either a version of Python containing ' \ - 'ElementTree (Python version >=2.5) or install ElementTree.' - raise ImportError(s1) -# # Import pygments and odtwriter pygments formatters if possible. try: import pygments import pygments.lexers - from pygmentsformatter import OdtPygmentsProgFormatter, \ - OdtPygmentsLaTeXFormatter -except (ImportError, SyntaxError), exp: + from .pygmentsformatter import (OdtPygmentsProgFormatter, + OdtPygmentsLaTeXFormatter) +except (ImportError, SyntaxError): pygments = None -# check for the Python Imaging Library -try: - import PIL.Image -except ImportError: - try: # sometimes PIL modules are put in PYTHONPATH's root - import Image - class PIL(object): pass # dummy wrapper - PIL.Image = Image - except ImportError: - PIL = None - ## import warnings ## warnings.warn('importing IPShellEmbed', UserWarning) ## from IPython.Shell import IPShellEmbed @@ -87,27 +69,33 @@ except ImportError: ## 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. # -if WhichElementTree == 'elementtree': - import weakref - _parents = weakref.WeakKeyDictionary() - if isinstance(etree.Element, type): - _ElementInterface = etree.Element - else: - _ElementInterface = etree._ElementInterface - class _ElementInterfaceWrapper(_ElementInterface): - def __init__(self, tag, attrib=None): - _ElementInterface.__init__(self, tag, attrib) - _parents[self] = None - def setparent(self, parent): - _parents[self] = parent - def getparent(self): - return _parents[self] +_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] # @@ -120,7 +108,8 @@ FILL_PAT2 = re.compile(r' {2,}') TABLESTYLEPREFIX = 'rststyle-table-' TABLENAMEDEFAULT = '%s0' % TABLESTYLEPREFIX -TABLEPROPERTYNAMES = ('border', 'border-top', 'border-left', +TABLEPROPERTYNAMES = ( + 'border', 'border-top', 'border-left', 'border-right', 'border-bottom', ) GENERATOR_DESC = 'Docutils.org/odf_odt' @@ -128,7 +117,7 @@ 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', + #'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', @@ -154,10 +143,10 @@ CONTENT_NAMESPACE_DICT = CNSD = { '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', + #'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', @@ -179,14 +168,14 @@ STYLES_NAMESPACE_DICT = SNSD = { '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', + #'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, @@ -194,9 +183,8 @@ META_NAMESPACE_DICT = METNSD = { 'xlink': 'http://www.w3.org/1999/xlink', } -# -# Attribute dictionaries for use with ElementTree (not lxml), which -# does not support use of nsmap parameter on Element() and SubElement(). +# 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', @@ -211,7 +199,8 @@ CONTENT_NAMESPACE_ATTRIB = { '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: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', @@ -224,7 +213,7 @@ CONTENT_NAMESPACE_ATTRIB = { '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', @@ -239,7 +228,8 @@ STYLES_NAMESPACE_ATTRIB = { '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: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', @@ -249,7 +239,7 @@ STYLES_NAMESPACE_ATTRIB = { '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', @@ -278,44 +268,36 @@ def Element(tag, attrib=None, nsmap=None, nsdict=CNSD): if attrib is None: attrib = {} tag, attrib = fix_ns(tag, attrib, nsdict) - if WhichElementTree == 'lxml': - el = etree.Element(tag, attrib, nsmap=nsmap) - else: - el = _ElementInterfaceWrapper(tag, attrib) + el = _ElementInterfaceWrapper(tag, attrib) return el + def SubElement(parent, tag, attrib=None, nsmap=None, nsdict=CNSD): if attrib is None: attrib = {} tag, attrib = fix_ns(tag, attrib, nsdict) - if WhichElementTree == 'lxml': - el = etree.SubElement(parent, tag, attrib, nsmap=nsmap) - else: - el = _ElementInterfaceWrapper(tag, attrib) - parent.append(el) - el.setparent(parent) + 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 attrib.iteritems(): + for key, val in list(attrib.items()): nskey = add_ns(key, nsdict) nsattrib[nskey] = val return nstag, nsattrib + def add_ns(tag, nsdict=CNSD): - if WhichElementTree == 'lxml': - nstag, name = tag.split(':') - ns = nsdict.get(nstag) - if ns is None: - raise RuntimeError, 'Invalid namespace prefix: %s' % nstag - tag = '{%s}%s' % (ns, name,) return tag + def ToString(et): - outstream = StringIO.StringIO() - if sys.version_info >= (3, 2): + outstream = StringIO() + if sys.version_info >= (3, 0): et.write(outstream, encoding="unicode") else: et.write(outstream) @@ -330,30 +312,13 @@ def escape_cdata(text): text = text.replace(">", ">") ascii = '' for char in text: - if ord(char) >= ord("\x7f"): - ascii += "%X;" % ( ord(char), ) - else: - ascii += char + if ord(char) >= ord("\x7f"): + ascii += "%X;" % (ord(char), ) + else: + ascii += char return ascii - -WORD_SPLIT_PAT1 = re.compile(r'\b(\w*)\b\W*') - -def split_words(line): - # We need whitespace at the end of the string for our regexpr. - line += ' ' - words = [] - pos1 = 0 - mo = WORD_SPLIT_PAT1.search(line, pos1) - while mo is not None: - word = mo.groups()[0] - words.append(word) - pos1 = mo.end() - mo = WORD_SPLIT_PAT1.search(line, pos1) - return words - - # # Classes # @@ -363,19 +328,26 @@ class TableStyle(object): def __init__(self, border=None, backgroundcolor=None): self.border = border self.backgroundcolor = backgroundcolor + def get_border_(self): return self.border_ + def set_border_(self, border): self.border_ = border + border = property(get_border_, set_border_) + def get_backgroundcolor_(self): return self.backgroundcolor_ + def set_backgroundcolor_(self, backgroundcolor): self.backgroundcolor_ = backgroundcolor backgroundcolor = property(get_backgroundcolor_, set_backgroundcolor_) + BUILTIN_DEFAULT_TABLE_STYLE = TableStyle( - border = '0.0007in solid #000000') + border='0.0007in solid #000000') + # # Information about the indentation level for lists nested inside @@ -385,12 +357,24 @@ class ListLevel(object): 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 + + 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): @@ -417,135 +401,134 @@ class Writer(writers.Writer): 'ODF-Specific Options', None, ( - ('Specify a stylesheet. ' - 'Default: "%s"' % default_stylesheet_path, - ['--stylesheet'], - { - 'default': default_stylesheet_path, - 'dest': 'stylesheet' - }), - ('Specify a configuration/mapping file relative to the ' - 'current working ' - 'directory for additional ODF options. ' - 'In particular, this file may contain a section named ' - '"Formats" that maps default style names to ' - 'names to be used in the resulting output file allowing for ' - 'adhering to external standards. ' - 'For more info and the format of the configuration/mapping file, ' - 'see the odtwriter doc.', - ['--odf-config-file'], - {'metavar': '<file>'}), - ('Obfuscate email addresses to confuse harvesters while still ' - 'keeping email links usable with standards-compliant browsers.', - ['--cloak-email-addresses'], - {'default': False, - 'action': 'store_true', - 'dest': 'cloak_email_addresses', - 'validator': frontend.validate_boolean}), - ('Do not obfuscate email addresses.', - ['--no-cloak-email-addresses'], - {'default': False, - 'action': 'store_false', - 'dest': 'cloak_email_addresses', - 'validator': frontend.validate_boolean}), - ('Specify the thickness of table borders in thousands of a cm. ' - 'Default is 35.', - ['--table-border-thickness'], - {'default': None, - 'validator': frontend.validate_nonnegative_int}), - ('Add syntax highlighting in literal code blocks.', - ['--add-syntax-highlighting'], - {'default': False, - 'action': 'store_true', - 'dest': 'add_syntax_highlighting', - 'validator': frontend.validate_boolean}), - ('Do not add syntax highlighting in literal code blocks. (default)', - ['--no-syntax-highlighting'], - {'default': False, - 'action': 'store_false', - 'dest': 'add_syntax_highlighting', - 'validator': frontend.validate_boolean}), - ('Create sections for headers. (default)', - ['--create-sections'], - {'default': True, - 'action': 'store_true', - 'dest': 'create_sections', - 'validator': frontend.validate_boolean}), - ('Do not create sections for headers.', - ['--no-sections'], - {'default': True, - 'action': 'store_false', - 'dest': 'create_sections', - 'validator': frontend.validate_boolean}), - ('Create links.', - ['--create-links'], - {'default': False, - 'action': 'store_true', - 'dest': 'create_links', - 'validator': frontend.validate_boolean}), - ('Do not create links. (default)', - ['--no-links'], - {'default': False, - 'action': 'store_false', - 'dest': 'create_links', - 'validator': frontend.validate_boolean}), - ('Generate endnotes at end of document, not footnotes ' - 'at bottom of page.', - ['--endnotes-end-doc'], - {'default': False, - 'action': 'store_true', - 'dest': 'endnotes_end_doc', - 'validator': frontend.validate_boolean}), - ('Generate footnotes at bottom of page, not endnotes ' - 'at end of document. (default)', - ['--no-endnotes-end-doc'], - {'default': False, - 'action': 'store_false', - 'dest': 'endnotes_end_doc', - 'validator': frontend.validate_boolean}), - ('Generate a bullet list table of contents, not ' - 'an ODF/oowriter table of contents.', - ['--generate-list-toc'], - {'default': True, - 'action': 'store_false', - 'dest': 'generate_oowriter_toc', - 'validator': frontend.validate_boolean}), - ('Generate an ODF/oowriter table of contents, not ' - 'a bullet list. (default)', - ['--generate-oowriter-toc'], - {'default': True, - 'action': 'store_true', - 'dest': 'generate_oowriter_toc', - 'validator': frontend.validate_boolean}), - ('Specify the contents of an custom header line. ' - 'See odf_odt writer documentation for details ' - 'about special field character sequences.', - ['--custom-odt-header'], - { 'default': '', - 'dest': 'custom_header', + ('Specify a stylesheet. ' + 'Default: "%s"' % default_stylesheet_path, + ['--stylesheet'], + { + 'default': default_stylesheet_path, + 'dest': 'stylesheet' }), - ('Specify the contents of an custom footer line. ' - 'See odf_odt writer documentation for details ' - 'about special field character sequences.', - ['--custom-odt-footer'], - { 'default': '', - 'dest': 'custom_footer', - }), - ) + ('Specify a configuration/mapping file relative to the ' + 'current working ' + 'directory for additional ODF options. ' + 'In particular, this file may contain a section named ' + '"Formats" that maps default style names to ' + 'names to be used in the resulting output file allowing for ' + 'adhering to external standards. ' + 'For more info and the format of the ' + 'configuration/mapping file, ' + 'see the odtwriter doc.', + ['--odf-config-file'], + {'metavar': '<file>'}), + ('Obfuscate email addresses to confuse harvesters while still ' + 'keeping email links usable with ' + 'standards-compliant browsers.', + ['--cloak-email-addresses'], + {'default': False, + 'action': 'store_true', + 'dest': 'cloak_email_addresses', + 'validator': frontend.validate_boolean}), + ('Do not obfuscate email addresses.', + ['--no-cloak-email-addresses'], + {'default': False, + 'action': 'store_false', + 'dest': 'cloak_email_addresses', + 'validator': frontend.validate_boolean}), + ('Specify the thickness of table borders in thousands of a cm. ' + 'Default is 35.', + ['--table-border-thickness'], + {'default': None, + 'validator': frontend.validate_nonnegative_int}), + ('Add syntax highlighting in literal code blocks.', + ['--add-syntax-highlighting'], + {'default': False, + 'action': 'store_true', + 'dest': 'add_syntax_highlighting', + 'validator': frontend.validate_boolean}), + ('Do not add syntax highlighting in ' + 'literal code blocks. (default)', + ['--no-syntax-highlighting'], + {'default': False, + 'action': 'store_false', + 'dest': 'add_syntax_highlighting', + 'validator': frontend.validate_boolean}), + ('Create sections for headers. (default)', + ['--create-sections'], + {'default': True, + 'action': 'store_true', + 'dest': 'create_sections', + 'validator': frontend.validate_boolean}), + ('Do not create sections for headers.', + ['--no-sections'], + {'default': True, + 'action': 'store_false', + 'dest': 'create_sections', + 'validator': frontend.validate_boolean}), + ('Create links.', + ['--create-links'], + {'default': False, + 'action': 'store_true', + 'dest': 'create_links', + 'validator': frontend.validate_boolean}), + ('Do not create links. (default)', + ['--no-links'], + {'default': False, + 'action': 'store_false', + 'dest': 'create_links', + 'validator': frontend.validate_boolean}), + ('Generate endnotes at end of document, not footnotes ' + 'at bottom of page.', + ['--endnotes-end-doc'], + {'default': False, + 'action': 'store_true', + 'dest': 'endnotes_end_doc', + 'validator': frontend.validate_boolean}), + ('Generate footnotes at bottom of page, not endnotes ' + 'at end of document. (default)', + ['--no-endnotes-end-doc'], + {'default': False, + 'action': 'store_false', + 'dest': 'endnotes_end_doc', + 'validator': frontend.validate_boolean}), + ('Generate a bullet list table of contents, not ' + 'an ODF/oowriter table of contents.', + ['--generate-list-toc'], + {'default': True, + 'action': 'store_false', + 'dest': 'generate_oowriter_toc', + 'validator': frontend.validate_boolean}), + ('Generate an ODF/oowriter table of contents, not ' + 'a bullet list. (default)', + ['--generate-oowriter-toc'], + {'default': True, + 'action': 'store_true', + 'dest': 'generate_oowriter_toc', + 'validator': frontend.validate_boolean}), + ('Specify the contents of an custom header line. ' + 'See odf_odt writer documentation for details ' + 'about special field character sequences.', + ['--custom-odt-header'], + {'default': '', + 'dest': 'custom_header', }), + ('Specify the contents of an custom footer line. ' + 'See odf_odt writer documentation for details ' + 'about special field character sequences.', + ['--custom-odt-footer'], + {'default': '', + 'dest': 'custom_footer', }), ) + ) settings_defaults = { 'output_encoding_error_handler': 'xmlcharrefreplace', - } + } relative_path_settings = ( 'stylesheet_path', - ) + ) config_section = 'odf_odt writer' - config_section_dependencies = ( - 'writers', - ) + config_section_dependencies = ('writers',) def __init__(self): writers.Writer.__init__(self) @@ -566,7 +549,8 @@ class Writer(writers.Writer): 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, + 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) @@ -683,7 +667,7 @@ class Writer(writers.Writer): 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) << 16L + zinfo.external_attr = (0x81a4 & 0xFFFF) << 16 zinfo.compress_type = compress_type zfile.writestr(zinfo, bytes) @@ -694,7 +678,7 @@ class Writer(writers.Writer): continue try: zfile.write(source, destination) - except OSError, e: + except OSError: self.document.reporter.warning( "Can't open file %s." % (source, )) @@ -735,53 +719,43 @@ class Writer(writers.Writer): pass def create_manifest(self): - if WhichElementTree == 'lxml': - root = Element('manifest:manifest', - nsmap=MANIFEST_NAMESPACE_DICT, - nsdict=MANIFEST_NAMESPACE_DICT, - ) - else: - root = Element('manifest:manifest', - attrib=MANIFEST_NAMESPACE_ATTRIB, - nsdict=MANIFEST_NAMESPACE_DICT, - ) + 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) + }, nsdict=MANNSD) SubElement(root, 'manifest:file-entry', attrib={ 'manifest:media-type': 'text/xml', 'manifest:full-path': 'content.xml', - }, nsdict=MANNSD) + }, nsdict=MANNSD) SubElement(root, 'manifest:file-entry', attrib={ 'manifest:media-type': 'text/xml', 'manifest:full-path': 'styles.xml', - }, nsdict=MANNSD) + }, nsdict=MANNSD) SubElement(root, 'manifest:file-entry', attrib={ 'manifest:media-type': 'text/xml', 'manifest:full-path': 'settings.xml', - }, nsdict=MANNSD) + }, nsdict=MANNSD) SubElement(root, 'manifest:file-entry', attrib={ 'manifest:media-type': 'text/xml', 'manifest:full-path': 'meta.xml', - }, nsdict=MANNSD) + }, nsdict=MANNSD) s1 = ToString(doc) doc = minidom.parseString(s1) s1 = doc.toprettyxml(' ') return s1 def create_meta(self): - if WhichElementTree == 'lxml': - root = Element('office:document-meta', - nsmap=META_NAMESPACE_DICT, - nsdict=META_NAMESPACE_DICT, - ) - else: - root = Element('office:document-meta', - attrib=META_NAMESPACE_ATTRIB, - nsdict=META_NAMESPACE_DICT, - ) + 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) @@ -808,17 +782,23 @@ class Writer(writers.Writer): el1.text = title else: el1.text = '[no title]' - meta_dict = self.visitor.get_meta_dict() - keywordstr = meta_dict.get('keywords') - if keywordstr is not None: - keywords = split_words(keywordstr) - for keyword in keywords: - el1 = SubElement(root, 'meta:keyword', nsdict=METNSD) - el1.text = keyword - description = meta_dict.get('description') - if description is not None: - el1 = SubElement(root, 'dc:description', nsdict=METNSD) - el1.text = description + 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(' ') @@ -895,8 +875,6 @@ class ODFTranslator(nodes.GenericNodeVisitor): document.reporter) self.format_map = {} if self.settings.odf_config_file: - from ConfigParser import ConfigParser - parser = ConfigParser() parser.read(self.settings.odf_config_file) for rststyle, format in parser.items("Formats"): @@ -904,21 +882,17 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.document.reporter.warning( 'Style "%s" is not a style used by odtwriter.' % ( rststyle, )) - if sys.version_info.major == 2: + if sys.version_info >= (3, 0): + self.format_map[rststyle] = format + else: self.format_map[rststyle] = format.decode('utf-8') self.section_level = 0 self.section_count = 0 # Create ElementTree content and styles documents. - if WhichElementTree == 'lxml': - root = Element( - 'office:document-content', - nsmap=CONTENT_NAMESPACE_DICT, - ) - else: - root = Element( - 'office:document-content', - attrib=CONTENT_NAMESPACE_ATTRIB, - ) + root = Element( + 'office:document-content', + attrib=CONTENT_NAMESPACE_ATTRIB, + ) self.content_tree = etree.ElementTree(element=root) self.current_element = root SubElement(root, 'office:scripts') @@ -957,7 +931,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.footnote_list = [] self.footnote_chars_idx = 0 self.footnote_level = 0 - self.pending_ids = [ ] + self.pending_ids = [] self.in_paragraph = False self.found_doc_title = False self.bumped_list_level_stack = [] @@ -995,11 +969,13 @@ class ODFTranslator(nodes.GenericNodeVisitor): s2 = zfile.read('content.xml') zfile.close() else: - raise RuntimeError, 'stylesheet path (%s) must be %s or .xml file' %(stylespath, extension) + 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.dom_stylesheetcontent = etree.fromstring( + self.str_stylesheetcontent) self.table_styles = self.extract_table_styles(s2) def extract_table_styles(self, styles_str): @@ -1019,8 +995,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): if family == 'table': properties = stylenode.find( '{%s}table-properties' % (CNSD['style'], )) - property = properties.get('{%s}%s' % (CNSD['fo'], - 'background-color', )) + property = properties.get( + '{%s}%s' % (CNSD['fo'], 'background-color', )) if property is not None and property != 'none': tablestyle.backgroundcolor = property elif family == 'table-cell': @@ -1045,9 +1021,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): if text: self.title = text if not self.found_doc_title: - el = Element('text:p', attrib = { + 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) @@ -1059,17 +1035,14 @@ class ODFTranslator(nodes.GenericNodeVisitor): """ if ( el.tag == 'text:p' or - el.tag == 'text:h' - ): + el.tag == 'text:h'): return el - elif el.getchildren(): - for child in el.getchildren(): + else: + for child in el: el1 = self.find_first_text_p(child) if el1 is not None: return el1 return None - else: - return None def attach_page_style(self, el): """Attach the default page style. @@ -1106,8 +1079,10 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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): + if (len(self.header_content) > 0 or + len(self.footer_content) > 0 or + self.settings.custom_header or + self.settings.custom_footer): self.add_header_footer(self.dom_stylesheet) new_content = etree.tostring(self.dom_stylesheet) return new_content @@ -1117,24 +1092,28 @@ class ODFTranslator(nodes.GenericNodeVisitor): def setup_paper(self, root_el): try: - fin = os.popen("paperconf -s 2> /dev/null") - w, h = map(float, fin.read().split()) - fin.close() - except: + 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 \ - not el.attrib.has_key("{%s}page-width" % SNSD["fo"]): + "{%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-right" % SNSD["fo"]] = \ + "%.3fpt" % (.1 * w) el.attrib["{%s}margin-top" % SNSD["fo"]] = \ - el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \ - "%.3fpt" % (.1 * h) + el.attrib["{%s}margin-bottom" % SNSD["fo"]] = \ + "%.3fpt" % (.1 * h) else: - for subel in el.getchildren(): walk(subel) + for subel in el: + walk(subel) walk(root_el) def add_header_footer(self, root_el): @@ -1157,58 +1136,59 @@ class ODFTranslator(nodes.GenericNodeVisitor): return el1 = master_el if self.header_content or self.settings.custom_header: - if WhichElementTree == 'lxml': - el2 = SubElement(el1, 'style:header', nsdict=SNSD) - else: - el2 = SubElement(el1, 'style:header', - attrib=STYLES_NAMESPACE_ATTRIB, - nsdict=STYLES_NAMESPACE_DICT, - ) + 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: - elcustom = self.create_custom_headfoot(el2, + self.create_custom_headfoot( + el2, self.settings.custom_header, 'header', automatic_styles) if self.footer_content or self.settings.custom_footer: - if WhichElementTree == 'lxml': - el2 = SubElement(el1, 'style:footer', nsdict=SNSD) - else: - el2 = SubElement(el1, 'style:footer', - attrib=STYLES_NAMESPACE_ATTRIB, - nsdict=STYLES_NAMESPACE_DICT, - ) + 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: - elcustom = self.create_custom_headfoot(el2, + self.create_custom_headfoot( + el2, self.settings.custom_footer, 'footer', automatic_styles) - code_none, code_field, code_text = range(3) + 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): + 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'): + 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, + 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 + raise RuntimeError(msg) else: current_element = el1 else: @@ -1222,76 +1202,79 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ':' el3 = SubElement(el2, 'number:minutes', attrib={ - 'number:style': 'long', - }) + '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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ':' el3 = SubElement(el2, 'number:minutes', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ':' el3 = SubElement(el2, 'number:seconds', attrib={ - 'number:style': 'long', - }) + '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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ':' el3 = SubElement(el2, 'number:minutes', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ' ' el3 = SubElement(el2, 'number:am-pm') @@ -1300,26 +1283,27 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ':' el3 = SubElement(el2, 'number:minutes', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ':' el3 = SubElement(el2, 'number:seconds', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ' ' el3 = SubElement(el2, 'number:am-pm') @@ -1327,22 +1311,23 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = '/' el3 = SubElement(el2, 'number:day', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = '/' el3 = SubElement(el2, 'number:year') @@ -1350,119 +1335,120 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = '/' el3 = SubElement(el2, 'number:day', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = '/' el3 = SubElement(el2, 'number:year', attrib={ - 'number:style': 'long', - }) + '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, - }) + '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:textual': 'true', + }) el3 = SubElement(el2, 'number:text') el3.text = ' ' - el3 = SubElement(el2, 'number:day', attrib={ - }) + el3 = SubElement(el2, 'number:day', attrib={}) el3 = SubElement(el2, 'number:text') el3.text = ', ' el3 = SubElement(el2, 'number:year', attrib={ - 'number:style': 'long', - }) + '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, - }) + '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', - }) + 'number:textual': 'true', + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = ' ' - el3 = SubElement(el2, 'number:day', attrib={ - }) + el3 = SubElement(el2, 'number:day', attrib={}) el3 = SubElement(el2, 'number:text') el3.text = ', ' el3 = SubElement(el2, 'number:year', attrib={ - 'number:style': 'long', - }) + '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, - }) + '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', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = '-' el3 = SubElement(el2, 'number:month', attrib={ - 'number:style': 'long', - }) + 'number:style': 'long', + }) el3 = SubElement(el2, 'number:text') el3.text = '-' el3 = SubElement(el2, 'number:day', attrib={ - 'number:style': 'long', - }) + '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 - pos_end = len(text) while True: mo = ODFTranslator.field_pat.search(text, pos1) if mo: @@ -1477,7 +1463,6 @@ class ODFTranslator(nodes.GenericNodeVisitor): if trailing: yield (ODFTranslator.code_text, trailing) - def astext(self): root = self.content_tree.getroot() et = etree.ElementTree(root) @@ -1487,12 +1472,20 @@ class ODFTranslator(nodes.GenericNodeVisitor): def content_astext(self): return self.astext() - def set_title(self, title): self.title = title - def get_title(self): return self.title + 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 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: @@ -1506,9 +1499,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): el3 = copy.deepcopy(el1) el2.append(el3) else: - children = el2.getchildren() - if len(children) > 0: # and 'id' in el2.attrib: - child = children[0] + 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') @@ -1525,7 +1517,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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) + attribkey = add_ns( + 'text:reference-format', nsdict=SNSD) el2.attrib[attribkey] = 'page' el2.text = ref1 @@ -1535,15 +1528,12 @@ class ODFTranslator(nodes.GenericNodeVisitor): def append_child(self, tag, attrib=None, parent=None): if parent is None: parent = self.current_element - if attrib is None: - el = SubElement(parent, tag) - else: - el = SubElement(parent, tag, attrib) + el = SubElement(parent, tag, attrib) return el def append_p(self, style, text=None): result = self.append_child('text:p', attrib={ - 'text:style-name': self.rststyle(style)}) + 'text:style-name': self.rststyle(style)}) self.append_pending_ids(result) if text is not None: result.text = text @@ -1553,8 +1543,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): if self.settings.create_links: for id in self.pending_ids: SubElement(el, 'text:reference-mark', attrib={ - 'text:name': id}) - self.pending_ids = [ ] + 'text:name': id}) + self.pending_ids = [] def set_current_element(self, el): self.current_element = el @@ -1565,7 +1555,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): def generate_labeled_block(self, node, label): label = '%s:' % (self.language.labels[label], ) el = self.append_p('textbody') - el1 = SubElement(el, 'text:span', + el1 = SubElement( + el, 'text:span', attrib={'text:style-name': self.rststyle('strong')}) el1.text = label el = self.append_p('blockindent') @@ -1574,14 +1565,15 @@ class ODFTranslator(nodes.GenericNodeVisitor): def generate_labeled_line(self, node, label): label = '%s:' % (self.language.labels[label], ) el = self.append_p('textbody') - el1 = SubElement(el, 'text:span', + el1 = SubElement( + el, 'text:span', attrib={'text:style-name': self.rststyle('strong')}) el1.text = label el1.tail = node.astext() return el def encode(self, text): - text = text.replace(u'\u00a0', " ") + text = text.replace('\n', " ") return text # @@ -1613,11 +1605,11 @@ class ODFTranslator(nodes.GenericNodeVisitor): text = node.astext() # Are we in mixed content? If so, add the text to the # etree tail of the previous sibling element. - if len(self.current_element.getchildren()) > 0: - if self.current_element.getchildren()[-1].tail: - self.current_element.getchildren()[-1].tail += text + if len(self.current_element) > 0: + if self.current_element[-1].tail: + self.current_element[-1].tail += text else: - self.current_element.getchildren()[-1].tail = text + self.current_element[-1].tail = text else: if self.current_element.text: self.current_element.text += text @@ -1651,7 +1643,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_authors(self, node): label = '%s:' % (self.language.labels['authors'], ) el = self.append_p('textbody') - el1 = SubElement(el, 'text:span', + el1 = SubElement( + el, 'text:span', attrib={'text:style-name': self.rststyle('strong')}) el1.text = label @@ -1693,13 +1686,13 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.set_to_parent() def visit_revision(self, node): - el = self.generate_labeled_line(node, 'revision') + self.generate_labeled_line(node, 'revision') def depart_revision(self, node): pass def visit_version(self, node): - el = self.generate_labeled_line(node, 'version') + self.generate_labeled_line(node, 'version') #self.set_current_element(el) def depart_version(self, node): @@ -1707,7 +1700,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): pass def visit_attribution(self, node): - el = self.append_p('attribution', node.astext()) + self.append_p('attribution', node.astext()) def depart_attribution(self, node): pass @@ -1730,46 +1723,46 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.line_indent_level -= 1 def visit_bullet_list(self, node): - self.list_level +=1 + self.list_level += 1 if self.in_table_of_contents: if self.settings.generate_oowriter_toc: pass else: - if node.has_key('classes') and \ + 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) @@ -1783,7 +1776,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): else: self.set_to_parent() self.list_style_stack.pop() - self.list_level -=1 + self.list_level -= 1 def visit_caption(self, node): raise nodes.SkipChildren() @@ -1794,11 +1787,11 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_comment(self, node): el = self.append_p('textbody') - el1 = SubElement(el, 'office:annotation', attrib={}) - el2 = SubElement(el1, 'dc:creator', attrib={}) + 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 = SubElement(el1, 'text:p', attrib={}) el2.text = node.astext() def depart_comment(self, node): @@ -1828,13 +1821,13 @@ class ODFTranslator(nodes.GenericNodeVisitor): pass def visit_definition_list(self, node): - self.def_list_level +=1 + 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 + self.def_list_level -= 1 def visit_definition_list_item(self, node): pass @@ -1861,12 +1854,11 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.bumped_list_level_stack.pop() def visit_classifier(self, node): - els = self.current_element.getchildren() - if len(els) > 0: - el = els[-1] - el1 = SubElement(el, 'text:span', - attrib={'text:style-name': self.rststyle('emphasis') - }) + 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): @@ -1882,10 +1874,12 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.section_level += 1 self.section_count += 1 if self.settings.create_sections: - el = self.append_child('text:section', attrib={ + 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): @@ -1894,7 +1888,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.set_to_parent() def visit_emphasis(self, node): - el = SubElement(self.current_element, 'text:span', + el = SubElement( + self.current_element, 'text:span', attrib={'text:style-name': self.rststyle('emphasis')}) self.set_current_element(el) @@ -1906,23 +1901,23 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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) @@ -2000,7 +1995,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_field_name(self, node): el = self.append_p('textbody') - el1 = SubElement(el, 'text:span', + el1 = SubElement( + el, 'text:span', attrib={'text:style-name': self.rststyle('strong')}) el1.text = node.astext() @@ -2040,7 +2036,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): '++', '+++', '##', '###', '@@', '@@@', - ] + ] def visit_footnote_reference(self, node): if self.footnote_level <= 0: @@ -2055,29 +2051,29 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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): + 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() @@ -2105,7 +2101,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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: @@ -2127,9 +2123,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): if self.settings.create_links: el0 = SubElement(el, 'text:span') el0.text = '[' - el1 = self.append_child('text:reference-mark-start', attrib={ - 'text:name': '%s' % (self.citation_id, ), - }) + self.append_child('text:reference-mark-start', attrib={ + 'text:name': '%s' % (self.citation_id, ), + }) else: el.text = '[' @@ -2138,9 +2134,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): pass elif self.citation_id is not None: if self.settings.create_links: - el = self.append_child('text:reference-mark-end', attrib={ - 'text:name': '%s' % (self.citation_id, ), - }) + self.append_child('text:reference-mark-end', attrib={ + 'text:name': '%s' % (self.citation_id, ), + }) el0 = SubElement(self.current_element, 'text:span') el0.text = ']' else: @@ -2184,7 +2180,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): destination = 'Pictures/1%08x%s' % (self.image_count, filename, ) if source.startswith('http:') or source.startswith('https:'): try: - imgfile = urllib2.urlopen(source) + imgfile = urlopen(source) content = imgfile.read() imgfile.close() imgfile2 = tempfile.NamedTemporaryFile('wb', delete=False) @@ -2192,7 +2188,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): imgfile2.close() imgfilename = imgfile2.name source = imgfilename - except urllib2.HTTPError, e: + except HTTPError: self.document.reporter.warning( "Can't open image url %s." % (source, )) spec = (source, destination,) @@ -2204,19 +2200,22 @@ class ODFTranslator(nodes.GenericNodeVisitor): if self.in_paragraph: el1 = self.current_element else: - el1 = SubElement(self.current_element, 'text:p', + 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, + el3, el4, el5, caption = self.generate_figure( + node, source, destination, el2) attrib = {} - el6, width = self.generate_image(node, source, destination, + 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): - el3 = self.generate_image(node, source, destination, el2) + else: # if isinstance(node.parent, docutils.nodes.image): + self.generate_image(node, source, destination, el2) def depart_image(self, node): pass @@ -2238,8 +2237,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): size = float(size) / 100.0 unit = '%' else: - size, unit = convert_to_cm(size) - except ValueError, exp: + size, unit = self.convert_to_cm(size) + except ValueError as exp: self.document.reporter.warning( 'Invalid %s for image: "%s". ' 'Error: "%s".' % ( @@ -2262,7 +2261,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): elif size.endswith('pc'): size = float(size[:-2]) * 2.371 # convert pc to cm elif size.endswith('mm'): - size = float(size[:-2]) * 10.0 # convert mm to cm + size = float(size[:-2]) * 0.1 # convert mm to cm elif size.endswith('cm'): size = float(size[:-2]) else: @@ -2301,7 +2300,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): # dpi information can be (xdpi, ydpi) or xydpi try: iter(dpi) - except: + except TypeError: dpi = (dpi, dpi) else: imageobj = None @@ -2358,7 +2357,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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, exp: + except ValueError: self.document.reporter.warning( 'Stylesheet file contains invalid page width ' 'or margin size.') @@ -2384,17 +2383,17 @@ class ODFTranslator(nodes.GenericNodeVisitor): 'style:family': 'paragraph', 'style:name': 'Caption', 'style:parent-style-name': 'Standard', - } + } el1 = SubElement(self.automatic_styles, 'style:style', - attrib=attrib, nsdict=SNSD) + attrib=attrib, nsdict=SNSD) attrib = { 'fo:margin-bottom': '0.0835in', 'fo:margin-top': '0.0835in', 'text:line-number': '0', 'text:number-lines': 'false', - } - el2 = SubElement(el1, 'style:paragraph-properties', - attrib=attrib, nsdict=SNSD) + } + SubElement(el1, 'style:paragraph-properties', + attrib=attrib, nsdict=SNSD) attrib = { 'fo:font-size': '12pt', 'fo:font-style': 'italic', @@ -2404,28 +2403,19 @@ class ODFTranslator(nodes.GenericNodeVisitor): 'style:font-size-complex': '12pt', 'style:font-style-asian': 'italic', 'style:font-style-complex': 'italic', - } - el2 = SubElement(el1, 'style:text-properties', - attrib=attrib, nsdict=SNSD) + } + SubElement(el1, 'style:text-properties', + attrib=attrib, nsdict=SNSD) style_name = 'rstframestyle%d' % self.image_style_count - draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next() + 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) - halign = 'center' - valign = 'top' - 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 + } + el1 = SubElement(self.automatic_styles, + 'style:style', attrib=attrib, nsdict=SNSD) attrib = {} wrap = False classes = node.parent.attributes.get('classes') @@ -2435,26 +2425,26 @@ class ODFTranslator(nodes.GenericNodeVisitor): attrib['style:wrap'] = 'dynamic' else: attrib['style:wrap'] = 'none' - el2 = SubElement(el1, - 'style:graphic-properties', attrib=attrib, nsdict=SNSD) + 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): + 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 @@ -2463,9 +2453,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): '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) + } + el1 = SubElement(self.automatic_styles, + 'style:style', attrib=attrib, nsdict=SNSD) halign = None valign = None if 'align' in node.attributes: @@ -2491,14 +2481,14 @@ class ODFTranslator(nodes.GenericNodeVisitor): '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 + # 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') @@ -2511,30 +2501,30 @@ class ODFTranslator(nodes.GenericNodeVisitor): # If we are inside a table, add a no-wrap style. if self.is_in_table(node): attrib['style:wrap'] = 'none' - el2 = SubElement(el1, - 'style:graphic-properties', attrib=attrib, nsdict=SNSD) - draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next() + 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={ + 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 + 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) - el2 = SubElement(el1, 'draw:image', 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): @@ -2569,16 +2559,16 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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), - }) + 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', + el = SubElement( + self.current_element, 'text:span', attrib={'text:style-name': self.rststyle('inlineliteral')}) self.set_current_element(el) @@ -2624,11 +2614,13 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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=(): + fmtr = OdtPygmentsLaTeXFormatter( + lambda name, parameters=(): self.rststyle(name, parameters), escape_function=escape_cdata) else: - fmtr = OdtPygmentsProgFormatter(lambda name, parameters=(): + fmtr = OdtPygmentsProgFormatter( + lambda name, parameters=(): self.rststyle(name, parameters), escape_function=escape_cdata) outsource = pygments.highlight(insource, lexer, fmtr) @@ -2657,11 +2649,12 @@ class ODFTranslator(nodes.GenericNodeVisitor): wrapper1 = '<text:p text:style-name="%s">%%s</text:p>' % ( self.rststyle('codeblock'), ) source = node.astext() - if (pygments and - self.settings.add_syntax_highlighting - #and - #node.get('hilight', False) - ): + if ( + pygments and + self.settings.add_syntax_highlighting + #and + #node.get('hilight', False) + ): language = node.get('language', 'python') source = self._add_syntax_highlighting(source, language) else: @@ -2670,8 +2663,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): # 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">'] - + 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) @@ -2682,11 +2675,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): lines1.append(my_lines_str2) lines1.append('</wrappertag1>') s1 = ''.join(lines1) - if WhichElementTree != "lxml": - s1 = s1.encode("utf-8") + s1 = s1.encode("utf-8") el1 = etree.fromstring(s1) - children = el1.getchildren() - for child in children: + for child in el1: self.current_element.append(child) def depart_literal_block(self, node): @@ -2698,7 +2689,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): # placeholder for math (see docs/dev/todo.txt) def visit_math(self, node): self.document.reporter.warning('"math" role not supported', - base_node=node) + base_node=node) self.visit_literal(node) def depart_math(self, node): @@ -2706,7 +2697,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_math_block(self, node): self.document.reporter.warning('"math" directive not supported', - base_node=node) + base_node=node) self.visit_literal_block(node) def depart_math_block(self, node): @@ -2735,18 +2726,18 @@ class ODFTranslator(nodes.GenericNodeVisitor): '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: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: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, )), + '%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', @@ -2758,14 +2749,14 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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, )), + '%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, )), + '%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', @@ -2775,7 +2766,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): '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, )), + '%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', @@ -2788,29 +2779,29 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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', ))}) + '%s.%%c' % table_name, ('A', ))}) el1 = SubElement(el, 'table:table-column', attrib={ 'table:style-name': self.rststyle( - '%s.%%c' % table_name, ( 'B', ))}) + '%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, )), + '%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' + el4.text = 'Option' el3 = SubElement(el2, 'table:table-cell', attrib={ 'table:style-name': self.rststyle( - '%s.%%c%%d' % table_name, ( 'B', 1, )), + '%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' + el4.text = 'Description' self.set_current_element(el) def depart_option_list(self, node): @@ -2874,7 +2865,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): el = self.append_p('footer') else: style_name = self.paragraph_style_stack[-1] - el = self.append_child('text:p', + el = self.append_child( + 'text:p', attrib={'text:style-name': style_name}) self.append_pending_ids(el) self.set_current_element(el) @@ -2883,15 +2875,11 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.in_paragraph = False self.set_to_parent() if self.in_header: - self.header_content.append( - self.current_element.getchildren()[-1]) - self.current_element.remove( - self.current_element.getchildren()[-1]) + 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.getchildren()[-1]) - self.current_element.remove( - self.current_element.getchildren()[-1]) + self.footer_content.append( self.current_element[-1] ) + self.current_element.remove( self.current_element[-1] ) def visit_problematic(self, node): pass @@ -2905,15 +2893,14 @@ class ODFTranslator(nodes.GenericNodeVisitor): formatlist = formats.split() if 'odt' in formatlist: rawstr = node.astext() - attrstr = ' '.join(['%s="%s"' % (k, v, ) - for k,v in CONTENT_NAMESPACE_ATTRIB.items()]) + attrstr = ' '.join([ + '%s="%s"' % (k, v, ) + for k, v in list(CONTENT_NAMESPACE_ATTRIB.items())]) contentstr = '<stuff %s>%s</stuff>' % (attrstr, rawstr, ) - if WhichElementTree != "lxml": - contentstr = contentstr.encode("utf-8") + contentstr = contentstr.encode("utf-8") content = etree.fromstring(contentstr) - elements = content.getchildren() - if len(elements) > 0: - el1 = elements[0] + if len(content) > 0: + el1 = content[0] if self.in_header: pass elif self.in_footer: @@ -2931,36 +2918,38 @@ class ODFTranslator(nodes.GenericNodeVisitor): pass def visit_reference(self, node): - text = node.astext() + #text = node.astext() if self.settings.create_links: - if node.has_key('refuri'): - 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 node.has_key('refid'): + 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)): + 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 node.has_key('refuri'): + if 'refuri' in node: self.set_to_parent() def visit_rubric(self, node): @@ -2970,11 +2959,11 @@ class ODFTranslator(nodes.GenericNodeVisitor): class1 = classes[0] if class1: style_name = class1 - el = SubElement(self.current_element, 'text:h', attrib = { + 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) @@ -2988,7 +2977,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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): @@ -2998,7 +2987,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_strong(self, node): el = SubElement(self.current_element, 'text:span', - attrib={'text:style-name': self.rststyle('strong')}) + attrib={'text:style-name': self.rststyle('strong')}) self.set_current_element(el) def depart_strong(self, node): @@ -3019,13 +3008,11 @@ class ODFTranslator(nodes.GenericNodeVisitor): def get_table_style(self, node): table_style = None table_name = None - use_predefined_table_style = False 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 - use_predefined_table_style = True break if table_name is not None: table_style = self.table_styles.get(table_name) @@ -3034,15 +3021,16 @@ class ODFTranslator(nodes.GenericNodeVisitor): # and use the default table style. self.document.reporter.warning( 'Can\'t find table style "%s". Using default.' % ( - table_name, )) + 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, )) + 'Can\'t find default table style "%s". ' + 'Using built-in default.' % ( + table_name, )) table_style = BUILTIN_DEFAULT_TABLE_STYLE else: table_name = TABLENAMEDEFAULT @@ -3051,8 +3039,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): # 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, )) + 'Can\'t find default table style "%s". ' + 'Using built-in default.' % ( + table_name, )) table_style = BUILTIN_DEFAULT_TABLE_STYLE return table_style @@ -3062,59 +3051,59 @@ class ODFTranslator(nodes.GenericNodeVisitor): table_name = '%s%%d' % TABLESTYLEPREFIX el1 = SubElement(self.automatic_styles, 'style:style', attrib={ 'style:name': self.rststyle( - '%s' % table_name, ( self.table_count, )), + '%s' % table_name, (self.table_count, )), 'style:family': 'table', - }, nsdict=SNSD) + }, nsdict=SNSD) if table_style.backgroundcolor is None: - el1_1 = SubElement(el1, 'style:table-properties', attrib={ + 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) + }, nsdict=SNSD) else: - el1_1 = SubElement(el1, 'style:table-properties', attrib={ + 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) + }, 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, )), + '%s.%%c%%d' % table_name, (self.table_count, 'A', 1, )), 'style:family': 'table-cell', - }, nsdict=SNSD) + }, 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, ) - el2_1 = SubElement(el2, 'style:table-cell-properties', attrib={ + 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) + }, nsdict=SNSD) title = None for child in node.children: if child.tagname == 'title': title = child.astext() break if title is not None: - el3 = self.append_p('table-title', title) + 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, )), + '%s' % table_name, (self.table_count, )), 'table:style-name': self.rststyle( - '%s' % table_name, ( self.table_count, )), - }) + '%s' % table_name, (self.table_count, )), + }) self.set_current_element(el4) self.current_table_style = el1 self.table_width = 0.0 @@ -3138,19 +3127,17 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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) - el1_1 = SubElement(el1, 'style:table-column-properties', attrib={ - 'style:column-width': '%.4fin' % colwidth - }, - nsdict=SNSD) - el2 = self.append_child('table:table-column', attrib={ - 'table:style-name': colspec_name, - }) + }, 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): @@ -3178,13 +3165,13 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_entry(self, node): self.column_count += 1 cellspec_name = self.rststyle( - '%s%%d.%%c%%d' % TABLESTYLEPREFIX, + '%s%%d.%%c%%d' % TABLESTYLEPREFIX, (self.table_count, 'A', 1, ) - ) - attrib={ + ) + 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,) @@ -3208,8 +3195,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): # # I don't know how to implement targets in ODF. # How do we create a target in oowriter? A cross-reference? - if not (node.has_key('refuri') or node.has_key('refid') - or node.has_key('refname')): + if not ('refuri' in node or + 'refid' in node or + 'refname' in node): pass else: pass @@ -3227,11 +3215,12 @@ class ODFTranslator(nodes.GenericNodeVisitor): ' 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, )), + 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) @@ -3239,9 +3228,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): # text = self.settings.title #else: # text = node.astext() - el1 = SubElement(self.current_element, 'text:p', attrib = { + 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 @@ -3249,8 +3238,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.set_current_element(el1) def depart_title(self, node): - if (isinstance(node.parent, docutils.nodes.section) or - isinstance(node.parent, docutils.nodes.document)): + 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): @@ -3270,19 +3260,20 @@ class ODFTranslator(nodes.GenericNodeVisitor): def generate_table_of_content_entry_template(self, el1): for idx in range(1, 11): - el2 = SubElement(el1, - 'text:table-of-content-entry-template', + el2 = SubElement( + el1, + 'text:table-of-content-entry-template', attrib={ 'text:outline-level': "%d" % (idx, ), 'text:style-name': self.rststyle('contents-%d' % (idx, )), }) - el3 = SubElement(el2, 'text:index-entry-chapter') - el3 = SubElement(el2, 'text:index-entry-text') - el3 = SubElement(el2, 'text:index-entry-tab-stop', attrib={ + 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", - }) - el3 = SubElement(el2, 'text:index-entry-page-number') + }) + SubElement(el2, 'text:index-entry-page-number') def find_title_label(self, node, class_type, label_key): label = '' @@ -3300,29 +3291,30 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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') + 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, + }) + el2 = SubElement( + el1, 'text:table-of-content-source', attrib={ 'text:outline-level': '10', }) - el3 =SubElement(el2, 'text:index-title-template', attrib={ + 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 @@ -3330,24 +3322,29 @@ class ODFTranslator(nodes.GenericNodeVisitor): else: el = self.append_p('horizontalline') el = self.append_p('centeredtextbody') - el1 = SubElement(el, 'text:span', + 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', + el1 = SubElement( + el, 'text:span', attrib={'text:style-name': self.rststyle('strong')}) - label = self.find_title_label(node, docutils.nodes.title, + 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', + el1 = SubElement( + el, 'text:span', attrib={'text:style-name': self.rststyle('strong')}) - label = self.find_title_label(node, docutils.nodes.title, + label = self.find_title_label( + node, docutils.nodes.title, 'dedication') el1.text = label @@ -3359,7 +3356,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.table_of_content_index_body) self.set_current_element(self.save_current_element) else: - el = self.append_p('horizontalline') + self.append_p('horizontalline') self.in_table_of_contents = False def update_toc_page_numbers(self, el): @@ -3370,20 +3367,20 @@ class ODFTranslator(nodes.GenericNodeVisitor): def update_toc_collect(self, el, level, collection): collection.append((level, el)) level += 1 - for child_el in el.getchildren(): + 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'): + if ( + el1.tag == 'text:p' and + el1.text != 'Table of Contents'): el2 = SubElement(el1, 'text:tab') el2.tail = '9999' - def visit_transition(self, node): - el = self.append_p('horizontalline') + self.append_p('horizontalline') def depart_transition(self, node): pass @@ -3452,7 +3449,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): def generate_admonition(self, node, label, title=None): if hasattr(self.language, 'labels'): - translated_label = self.language.labels[label] + translated_label = self.language.labels.get(label, label) else: translated_label = label el1 = SubElement(self.current_element, 'text:p', attrib={ @@ -3472,7 +3469,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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): @@ -3481,12 +3478,30 @@ class ODFTranslator(nodes.GenericNodeVisitor): 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): @@ -3495,6 +3510,5 @@ class Reader(standalone.Reader): default = standalone.Reader.get_transforms(self) if self.settings.create_links: return default - return [ i - for i in default - if i is not references.DanglingReferences ] + return [i for i in default + if i is not references.DanglingReferences] diff --git a/docutils/src/main/resources/docutils/docutils/writers/pep_html/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/pep_html/__init__.py index c7ca79d..658031e 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/pep_html/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/pep_html/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7630 2013-03-15 22:27:04Z milde $ +# $Id: __init__.py 8722 2021-05-17 20:28:42Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -33,10 +33,10 @@ class Writer(html4css1.Writer): os.path.join(os.path.dirname(__file__), default_template)) settings_spec = html4css1.Writer.settings_spec + ( - 'PEP/HTML-Specific Options', + '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-Specific Options above.' + 'See HTML Writer Options above.' % (default_stylesheet_path, default_template_path), (('Python\'s home URL. Default is "http://www.python.org".', ['--python-home'], @@ -55,7 +55,8 @@ class Writer(html4css1.Writer): relative_path_settings = ('template',) config_section = 'pep_html writer' - config_section_dependencies = ('writers', 'html4css1 writer') + config_section_dependencies = ('writers', 'html writers', + 'html4css1 writer') def __init__(self): html4css1.Writer.__init__(self) diff --git a/docutils/src/main/resources/docutils/docutils/writers/pep_html/pep.css b/docutils/src/main/resources/docutils/docutils/writers/pep_html/pep.css index 0e4aad1..7d6fcaf 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/pep_html/pep.css +++ b/docutils/src/main/resources/docutils/docutils/writers/pep_html/pep.css @@ -1,7 +1,7 @@ /* :Author: David Goodger :Contact: goodger@python.org -:date: $Date: 2006-05-21 22:44:42 +0200 (So, 21 Mai 2006) $ +:date: $Date: 2006-05-21 22:44:42 +0200 (So, 21. Mai 2006) $ :version: $Revision: 4564 $ :copyright: This stylesheet has been placed in the public domain. diff --git a/docutils/src/main/resources/docutils/docutils/writers/pseudoxml.py b/docutils/src/main/resources/docutils/docutils/writers/pseudoxml.py index bb833d6..b2cfd60 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/pseudoxml.py +++ b/docutils/src/main/resources/docutils/docutils/writers/pseudoxml.py @@ -1,4 +1,4 @@ -# $Id: pseudoxml.py 7320 2012-01-19 22:33:02Z milde $ +# $Id: pseudoxml.py 8859 2021-10-21 22:59:24Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -9,13 +9,21 @@ Simple internal document tree Writer, writes indented pseudo-XML. __docformat__ = 'reStructuredText' -from docutils import writers +from docutils import writers, frontend class Writer(writers.Writer): supported = ('pprint', 'pformat', 'pseudoxml') """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',) diff --git a/docutils/src/main/resources/docutils/docutils/writers/s5_html/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/s5_html/__init__.py index 411e562..6d69dbb 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/s5_html/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/s5_html/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7720 2013-09-05 12:54:56Z milde $ +# $Id: __init__.py 8729 2021-05-18 21:33:58Z milde $ # Authors: Chris Liechti <cliechti@gmx.net>; # David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -17,7 +17,6 @@ import docutils from docutils import frontend, nodes, utils from docutils.writers import html4css1 from docutils.parsers.rst import directives -from docutils._compat import b themes_dir_path = utils.relative_path( os.path.join(os.getcwd(), 'dummy'), @@ -84,7 +83,8 @@ class Writer(html4css1.Writer): settings_default_overrides = {'toc_backlinks': 0} config_section = 's5_html writer' - config_section_dependencies = ('writers', 'html4css1 writer') + config_section_dependencies = ('writers', 'html writers', + 'html4css1 writer') def __init__(self): html4css1.Writer.__init__(self) @@ -143,7 +143,7 @@ class S5HTMLTranslator(html4css1.HTMLTranslator): """Names of theme files directly linked to in the output HTML""" indirect_theme_files = ( - 's5-core.css', 'framing.css', 'pretty.css', 'blank.gif', 'iepngfix.htc') + 's5-core.css', 'framing.css', 'pretty.css') """Names of files used indirectly; imported or used by files in `direct_theme_files`.""" @@ -216,7 +216,8 @@ class S5HTMLTranslator(html4css1.HTMLTranslator): 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): - lines = open(base_theme_file).readlines() + with open(base_theme_file) as f: + lines = f.readlines() for line in lines: line = line.strip() if line and not line.startswith('#'): @@ -268,8 +269,7 @@ class S5HTMLTranslator(html4css1.HTMLTranslator): src_file.close() dest_file = open(dest, 'wb') dest_dir = dest_dir.replace(os.sep, '/') - dest_file.write(src_data.replace( - b('ui/default'), + dest_file.write(src_data.replace(b'ui/default', dest_dir[dest_dir.rfind('ui/'):].encode( sys.getfilesystemencoding()))) dest_file.close() diff --git a/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/README.txt b/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/README.txt index 2e01b51..605d08f 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/README.txt +++ b/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/README.txt @@ -1,4 +1,4 @@ -Except where otherwise noted (default/iepngfix.htc), all files in this +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 diff --git a/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/blank.gif b/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/blank.gif deleted file mode 100644 index 75b945d..0000000 Binary files a/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/blank.gif and /dev/null differ diff --git a/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/iepngfix.htc b/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/iepngfix.htc deleted file mode 100644 index 9f3d628..0000000 --- a/docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/iepngfix.htc +++ /dev/null @@ -1,42 +0,0 @@ -<public:component> -<public:attach event="onpropertychange" onevent="doFix()" /> - -<script> - -// IE5.5+ PNG Alpha Fix v1.0 by Angus Turnbull http://www.twinhelix.com -// Free usage permitted as long as this notice remains intact. - -// This must be a path to a blank image. That's all the configuration you need here. -var blankImg = 'ui/default/blank.gif'; - -var f = 'DXImageTransform.Microsoft.AlphaImageLoader'; - -function filt(s, m) { - if (filters[f]) { - filters[f].enabled = s ? true : false; - if (s) with (filters[f]) { src = s; sizingMethod = m } - } else if (s) style.filter = 'progid:'+f+'(src="'+s+'",sizingMethod="'+m+'")'; -} - -function doFix() { - if ((parseFloat(navigator.userAgent.match(/MSIE (\S+)/)[1]) < 5.5) || - (event && !/(background|src)/.test(event.propertyName))) return; - - if (tagName == 'IMG') { - if ((/\.png$/i).test(src)) { - filt(src, 'image'); // was 'scale' - src = blankImg; - } else if (src.indexOf(blankImg) < 0) filt(); - } else if (style.backgroundImage) { - if (style.backgroundImage.match(/^url[("']+(.*\.png)[)"']+$/i)) { - var s = RegExp.$1; - style.backgroundImage = ''; - filt(s, 'crop'); - } else filt(); - } -} - -doFix(); - -</script> -</public:component> \ No newline at end of file diff --git a/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py index 1c82059..257e689 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py @@ -2,17 +2,17 @@ # -*- coding: utf-8 -*- # :Author: Günter Milde <milde@users.sourceforge.net> -# :Revision: $Revision: 8046 $ -# :Date: $Date: 2017-03-11 13:09:36 +0100 (Sa, 11 Mär 2017) $ +# :Revision: $Revision: 8722 $ +# :Date: $Date: 2021-05-17 22:28:42 +0200 (Mo, 17. Mai 2021) $ # :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: http://www.spdx.org/licenses/BSD-2-Clause +# +# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause """ XeLaTeX document tree Writer. @@ -35,34 +35,37 @@ from docutils.writers import latex2e class Writer(latex2e.Writer): """A writer for Unicode-aware LaTeX variants (XeTeX, LuaTeX)""" - supported = ('lxtex', 'xetex','xelatex','luatex', 'lualatex') + supported = ('lxtex', 'xetex', 'xelatex', 'luatex', 'lualatex') """Formats this writer supports.""" default_template = 'xelatex.tex' default_preamble = '\n'.join([ - r'% Linux Libertine (free, wide coverage, not only for Linux)', - r'\setmainfont{Linux Libertine O}', - r'\setsansfont{Linux Biolinum O}', - r'\setmonofont[HyphenChar=None,Scale=MatchLowercase]{DejaVu Sans Mono}', + r'% Linux Libertine (free, wide coverage, not only for Linux)', + r'\setmainfont{Linux Libertine O}', + r'\setsansfont{Linux Biolinum O}', + r'\setmonofont[HyphenChar=None,Scale=MatchLowercase]{DejaVu Sans Mono}', ]) config_section = 'xetex writer' - config_section_dependencies = ('writers', 'latex2e writer') + config_section_dependencies = ('writers', 'latex writers', + 'latex2e writer') # TODO: remove dependency on `latex2e writer`. settings_spec = frontend.filter_settings_spec( latex2e.Writer.settings_spec, - 'font_encoding', + 'font_encoding', # removed settings + # changed settings: template=('Template file. Default: "%s".' % default_template, - ['--template'], {'default': default_template, 'metavar': '<file>'}), + ['--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}), + 'Default: select "Linux Libertine" fonts.', + ['--latex-preamble'], + {'default': default_preamble}), ) def __init__(self): latex2e.Writer.__init__(self) - self.settings_defaults.update({'fontencoding': ''}) # use default (EU1 or EU2) + self.settings_defaults.update({'fontencoding': ''}) # use default (TU) self.translator_class = XeLaTeXTranslator @@ -93,7 +96,7 @@ class Babel(latex2e.Babel): # zh-Latn: ??? # Chinese Pinyin }) # normalize (downcase) keys - language_codes = dict([(k.lower(), v) for (k,v) in language_codes.items()]) + language_codes = dict([(k.lower(), v) for (k, v) in language_codes.items()]) # Languages without Polyglossia support: for key in ('af', # 'afrikaans', -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.