branch develop updated (0745c8b -> 1a5d981)
This is an automated email from the git hooks/post-receive script. New change to branch develop in repository jrst. See https://gitlab.nuiton.org/nuiton/jrst.git from 0745c8b Use new image new 9054256 #119: Update docutils to 0.18.1 new 1a5d981 #120: Update to doxia 1.11 and support JDK 17 #118: Update libs The 2 revisions listed above as "new" are entirely new to this repository and will be described in separate emails. The revisions listed as "adds" were already present in the repository and have only been added to this reference. Detailed log of new commits: commit 1a5d981927f81095f0f83e7b4aaf8d28411831dc Author: Eric Chatellier <chatellier@codelutin.com> Date: Fri Jul 8 14:58:14 2022 +0200 #120: Update to doxia 1.11 and support JDK 17 #118: Update libs commit 90542566a385d93aea25dfdbb7e0e015fe7bc7d8 Author: Eric Chatellier <chatellier@codelutin.com> Date: Fri Jul 8 14:57:04 2022 +0200 #119: Update docutils to 0.18.1 Summary of changes: .../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/{titlepage.tex => titlingpage.tex} | 8 +- .../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 +- .../test/java/org/nuiton/jrst/JrstParserTest.java | 1 - .../src/main/java/org/nuiton/jrst/JrstParser.java | 3 +- .../test/java/org/nuiton/jrst/JrstParserTest.java | 1 - .../java/org/nuiton/jrst/AbstractJrstParser.java | 17 +- .../org/nuiton/jrst/JRSTToXmlStrategyDocutils.java | 5 +- pom.xml | 48 +- 81 files changed, 9546 insertions(+), 8718 deletions(-) delete mode 100644 docutils/src/main/resources/docutils/docutils/_compat.py create mode 100644 docutils/src/main/resources/docutils/docutils/languages/ar.py create mode 100644 docutils/src/main/resources/docutils/docutils/languages/ko.py create mode 100644 docutils/src/main/resources/docutils/docutils/parsers/recommonmark_wrapper.py create mode 100644 docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ar.py create mode 100644 docutils/src/main/resources/docutils/docutils/parsers/rst/languages/ko.py mode change 100644 => 100755 docutils/src/main/resources/docutils/docutils/utils/math/math2html.py create mode 100644 docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/responsive.css create mode 100644 docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/tuftig.css delete mode 100644 docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils-05-compat.sty create mode 100644 docutils/src/main/resources/docutils/docutils/writers/latex2e/docutils.sty copy docutils/src/main/resources/docutils/docutils/writers/latex2e/{titlepage.tex => titlingpage.tex} (72%) delete mode 100644 docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/blank.gif delete mode 100644 docutils/src/main/resources/docutils/docutils/writers/s5_html/themes/default/iepngfix.htc -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.
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>.
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 1a5d981927f81095f0f83e7b4aaf8d28411831dc Author: Eric Chatellier <chatellier@codelutin.com> Date: Fri Jul 8 14:58:14 2022 +0200 #120: Update to doxia 1.11 and support JDK 17 #118: Update libs --- .../test/java/org/nuiton/jrst/JrstParserTest.java | 1 - .../src/main/java/org/nuiton/jrst/JrstParser.java | 3 +- .../test/java/org/nuiton/jrst/JrstParserTest.java | 1 - .../java/org/nuiton/jrst/AbstractJrstParser.java | 17 ++++++-- .../org/nuiton/jrst/JRSTToXmlStrategyDocutils.java | 5 +-- pom.xml | 48 +++++++++++----------- 6 files changed, 41 insertions(+), 34 deletions(-) diff --git a/doxia-module-jrst-legacy/src/test/java/org/nuiton/jrst/JrstParserTest.java b/doxia-module-jrst-legacy/src/test/java/org/nuiton/jrst/JrstParserTest.java index bf2b6e1..983a254 100644 --- a/doxia-module-jrst-legacy/src/test/java/org/nuiton/jrst/JrstParserTest.java +++ b/doxia-module-jrst-legacy/src/test/java/org/nuiton/jrst/JrstParserTest.java @@ -29,7 +29,6 @@ import org.apache.maven.doxia.AbstractModuleTest; import org.apache.maven.doxia.module.xdoc.XdocSink; import org.apache.maven.doxia.parser.Parser; import org.apache.maven.doxia.sink.Sink; -import org.junit.Ignore; /** * @author chatellier diff --git a/doxia-module-jrst/src/main/java/org/nuiton/jrst/JrstParser.java b/doxia-module-jrst/src/main/java/org/nuiton/jrst/JrstParser.java index 5fd8aba..00f4529 100644 --- a/doxia-module-jrst/src/main/java/org/nuiton/jrst/JrstParser.java +++ b/doxia-module-jrst/src/main/java/org/nuiton/jrst/JrstParser.java @@ -2,7 +2,7 @@ * #%L * JRst :: Doxia module * %% - * Copyright (C) 2009 - 2017 CodeLutin + * Copyright (C) 2009 - 2022 CodeLutin * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -47,5 +47,4 @@ public class JrstParser extends AbstractJrstParser { public JRSTToXmlStrategy getStrategy() { return jrstStrategy; } - } diff --git a/doxia-module-jrst/src/test/java/org/nuiton/jrst/JrstParserTest.java b/doxia-module-jrst/src/test/java/org/nuiton/jrst/JrstParserTest.java index ac3666a..88af67e 100644 --- a/doxia-module-jrst/src/test/java/org/nuiton/jrst/JrstParserTest.java +++ b/doxia-module-jrst/src/test/java/org/nuiton/jrst/JrstParserTest.java @@ -26,7 +26,6 @@ import org.apache.maven.doxia.AbstractModuleTest; import org.apache.maven.doxia.module.xdoc.XdocSink; import org.apache.maven.doxia.parser.Parser; import org.apache.maven.doxia.sink.Sink; -import org.junit.Ignore; import java.io.Reader; import java.io.StringWriter; diff --git a/jrst-site-util/src/main/java/org/nuiton/jrst/AbstractJrstParser.java b/jrst-site-util/src/main/java/org/nuiton/jrst/AbstractJrstParser.java index 5eeb67a..22b8882 100644 --- a/jrst-site-util/src/main/java/org/nuiton/jrst/AbstractJrstParser.java +++ b/jrst-site-util/src/main/java/org/nuiton/jrst/AbstractJrstParser.java @@ -2,7 +2,7 @@ * #%L * JRst :: Site util * %% - * Copyright (C) 2012 - 2017 CodeLutin + * Copyright (C) 2012 - 2022 CodeLutin * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -29,10 +29,13 @@ import java.io.StringReader; import org.apache.maven.doxia.logging.Log; import org.apache.maven.doxia.module.xdoc.XdocParser; +import org.apache.maven.doxia.parser.AbstractParser; import org.apache.maven.doxia.parser.ParseException; +import org.apache.maven.doxia.parser.Parser; import org.apache.maven.doxia.sink.Sink; import org.apache.maven.doxia.siterenderer.RenderingContext; import org.apache.maven.project.MavenProject; +import org.codehaus.plexus.component.annotations.Requirement; import org.codehaus.plexus.util.IOUtil; import org.dom4j.Document; import org.nuiton.util.FileUtil; @@ -43,7 +46,7 @@ import org.nuiton.util.FileUtil; * @author tchemit (chemit@codelutin.com) * @since 2.0.1 */ -public abstract class AbstractJrstParser extends XdocParser { +public abstract class AbstractJrstParser extends AbstractParser { public static final String JRST_PARSER_ID = "jrst"; @@ -55,6 +58,9 @@ public abstract class AbstractJrstParser extends XdocParser { protected boolean verbose; + @Requirement(role = Parser.class, hint = "xdoc") + protected XdocParser xdocParser; + @Override public void parse(Reader source, Sink sink) throws ParseException { @@ -71,7 +77,7 @@ public abstract class AbstractJrstParser extends XdocParser { // Give xsl result to XDoc parser Reader reader = new StringReader(doc.asXML()); - super.parse(reader, sink); + xdocParser.parse(reader, sink); } catch (Exception e) { throw new ParseException("Can't parse rst file", e); } finally { @@ -80,6 +86,11 @@ public abstract class AbstractJrstParser extends XdocParser { } } + @Override + public void parse(Reader source, Sink sink, String reference) throws ParseException { + parse(source, sink); + } + public void setRenderingContext(RenderingContext renderingContext) { this.renderingContext = renderingContext; } diff --git a/jrst/src/main/java/org/nuiton/jrst/JRSTToXmlStrategyDocutils.java b/jrst/src/main/java/org/nuiton/jrst/JRSTToXmlStrategyDocutils.java index 56d2940..514708c 100644 --- a/jrst/src/main/java/org/nuiton/jrst/JRSTToXmlStrategyDocutils.java +++ b/jrst/src/main/java/org/nuiton/jrst/JRSTToXmlStrategyDocutils.java @@ -2,7 +2,7 @@ * #%L * JRst :: Api * %% - * Copyright (C) 2004 - 2017 CodeLutin + * Copyright (C) 2004 - 2022 CodeLutin * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as @@ -26,7 +26,6 @@ import org.apache.commons.logging.LogFactory; import org.codehaus.plexus.component.annotations.Component; import org.dom4j.Document; import org.dom4j.DocumentException; -import org.dom4j.DocumentHelper; import org.dom4j.io.SAXReader; import org.nuiton.jrst.legacy.JRSTReader; import org.python.util.PythonInterpreter; @@ -110,7 +109,7 @@ public class JRSTToXmlStrategyDocutils implements JRSTToXmlStrategy { interp.cleanup(); // Transforms the output stream to a document - String xmlString = new String(out.toByteArray(), encoding); + String xmlString = out.toString(encoding); Document doc = null; try { diff --git a/pom.xml b/pom.xml index 472f22e..188ff76 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ #%L JRst %% - Copyright (C) 2004 - 2018 CodeLutin + Copyright (C) 2004 - 2022 CodeLutin %% This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -26,7 +26,7 @@ <parent> <groupId>org.nuiton</groupId> <artifactId>nuitonpom</artifactId> - <version>11.8</version> + <version>11.12</version> </parent> <artifactId>jrst</artifactId> @@ -110,9 +110,9 @@ <signatureArtifactId>java18</signatureArtifactId> <signatureVersion>1.0</signatureVersion> - <nuitonI18nVersion>4.0-SNAPSHOT</nuitonI18nVersion> + <nuitonI18nVersion>4.1</nuitonI18nVersion> <xalanVersion>2.7.1</xalanVersion> - <jaxxVersion>2.45</jaxxVersion> + <jaxxVersion>2.46</jaxxVersion> <!-- tests fail with 3.3.x --> <mavenVersion>3.2.5</mavenVersion> @@ -128,14 +128,14 @@ <dependency> <groupId>org.nuiton</groupId> <artifactId>nuiton-utils</artifactId> - <version>3.1-rc-3-SNAPSHOT</version> + <version>3.1</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.nuiton</groupId> <artifactId>nuiton-config</artifactId> - <version>3.5-SNAPSHOT</version> + <version>3.5</version> <scope>compile</scope> </dependency> @@ -230,13 +230,13 @@ <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-utils</artifactId> - <version>3.3.0</version> + <version>3.4.2</version> </dependency> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-component-annotations</artifactId> - <version>2.1.0</version> + <version>2.1.1</version> </dependency> <dependency> @@ -326,41 +326,41 @@ <dependency> <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-module-xdoc</artifactId> - <version>1.9.1</version> + <version>1.11.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-core</artifactId> - <version>1.9.1</version> + <version>1.11.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-sink-api</artifactId> - <version>1.9.1</version> + <version>1.11.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-logging-api</artifactId> - <version>1.9.1</version> + <version>1.11.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-decoration-model</artifactId> - <version>1.9.2</version> + <version>1.11.1</version> </dependency> <dependency> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-container-default</artifactId> - <version>2.1.0</version> + <version>2.1.1</version> <scope>provided</scope> </dependency> @@ -402,14 +402,14 @@ <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-core</artifactId> <type>test-jar</type> - <version>1.9.1</version> + <version>1.11.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.maven.doxia</groupId> <artifactId>doxia-site-renderer</artifactId> - <version>1.9.2</version> + <version>1.11.1</version> </dependency> <dependency> @@ -421,7 +421,7 @@ <dependency> <groupId>org.eclipse.sisu</groupId> <artifactId>org.eclipse.sisu.plexus</artifactId> - <version>0.3.4</version> + <version>0.3.5</version> <scope>provided</scope> </dependency> @@ -435,12 +435,12 @@ <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf</artifactId> - <version>9.1.20</version> + <version>9.1.22</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-core</artifactId> - <version>9.1.20</version> + <version>9.1.22</version> </dependency> <dependency> @@ -473,14 +473,14 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.13</version> + <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>29.0-jre</version> + <version>31.1-jre</version> </dependency> <dependency> @@ -492,7 +492,7 @@ <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>2.7</version> + <version>2.11.0</version> </dependency> <dependency> @@ -510,7 +510,7 @@ <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> - <version>3.10</version> + <version>3.12.0</version> </dependency> <dependency> @@ -541,7 +541,7 @@ <plugin> <groupId>org.codehaus.plexus</groupId> <artifactId>plexus-component-metadata</artifactId> - <version>1.5.5</version> + <version>2.1.1</version> </plugin> </plugins> -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.
participants (1)
-
nuiton.org scm