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 2231b2f36ab91c4a5bdbf5f590a545ee098821ab Author: Eric Chatellier <chatellier@codelutin.com> Date: Thu May 17 15:05:05 2018 +0200 refs #116: Update docutils to 0.14 --- .../main/resources/docutils/docutils/__init__.py | 67 ++- .../src/main/resources/docutils/docutils/core.py | 8 +- .../main/resources/docutils/docutils/frontend.py | 50 +- .../src/main/resources/docutils/docutils/io.py | 19 +- .../resources/docutils/docutils/languages/sv.py | 15 +- .../docutils/docutils/parsers/rst/__init__.py | 12 +- .../docutils/parsers/rst/directives/__init__.py | 8 +- .../docutils/parsers/rst/directives/tables.py | 33 +- .../docutils/docutils/parsers/rst/languages/de.py | 28 +- .../docutils/docutils/parsers/rst/languages/sv.py | 107 ++--- .../docutils/docutils/parsers/rst/states.py | 52 ++- .../docutils/docutils/transforms/frontmatter.py | 10 +- .../resources/docutils/docutils/transforms/peps.py | 4 +- .../docutils/docutils/transforms/references.py | 16 +- .../docutils/docutils/transforms/universal.py | 39 +- .../resources/docutils/docutils/utils/__init__.py | 72 ++- .../docutils/docutils/utils/code_analyzer.py | 2 +- .../docutils/docutils/utils/error_reporting.py | 4 +- .../docutils/docutils/utils/math/latex2mathml.py | 6 +- .../docutils/docutils/utils/math/math2html.py | 4 +- .../docutils/docutils/utils/punctuation_chars.py | 331 ++----------- .../docutils/docutils/utils/smartquotes.py | 518 +++++++++++++-------- .../docutils/docutils/writers/_html_base.py | 128 +++-- .../docutils/writers/html4css1/__init__.py | 62 +-- .../docutils/writers/html5_polyglot/__init__.py | 42 +- .../docutils/writers/html5_polyglot/minimal.css | 4 +- .../docutils/writers/html5_polyglot/plain.css | 6 +- .../docutils/docutils/writers/latex2e/__init__.py | 361 ++++++++------ .../docutils/docutils/writers/latex2e/default.tex | 1 - .../docutils/docutils/writers/latex2e/xelatex.tex | 6 +- .../resources/docutils/docutils/writers/manpage.py | 14 +- .../docutils/docutils/writers/odf_odt/__init__.py | 309 +++++++++--- .../docutils/docutils/writers/pep_html/pep.css | 2 +- .../docutils/docutils/writers/xetex/__init__.py | 19 +- 34 files changed, 1408 insertions(+), 951 deletions(-) diff --git a/docutils/src/main/resources/docutils/docutils/__init__.py b/docutils/src/main/resources/docutils/docutils/__init__.py index 79ed8fb..f4a81e3 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 7984 2016-12-09 09:48:27Z grubert $ +# $Id: __init__.py 8147 2017-08-03 09:01:16Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -50,20 +50,64 @@ Subpackages: - writers: Format-specific output translators. """ +import sys + + __docformat__ = 'reStructuredText' -__version__ = '0.13.1' -"""``major.minor.micro`` version number. The micro number is bumped for API -changes, for new functionality, and for interim project releases. The minor -number is bumped whenever there is a significant project release. The major -number will be bumped when the project is feature-complete, and perhaps if -there is a major change in the design.""" +__version__ = '0.14' +"""Docutils version identifier (complies with PEP 440):: -__version_details__ = 'release' -"""Extra version details (e.g. 'snapshot 2005-05-29, r3410', 'repository', -'release'), modified automatically & manually.""" + 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__` +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: +""" +from collections import namedtuple +VersionInfo = namedtuple( + 'VersionInfo', 'major minor micro releaselevel serial release') +__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 + ) + +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__ = '' +"""Optional extra version details (e.g. 'snapshot 2005-05-29, r3410'). +(For development and release status see `__version_info__`.) +""" -import sys class ApplicationError(StandardError): # Workaround: @@ -74,6 +118,7 @@ class ApplicationError(StandardError): def __unicode__(self): return u', '.join(self.args) + class DataError(ApplicationError): pass diff --git a/docutils/src/main/resources/docutils/docutils/core.py b/docutils/src/main/resources/docutils/docutils/core.py index e3f4c9b..3dc12e8 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 7466 2012-06-25 14:56:51Z milde $ +# $Id: core.py 8126 2017-06-23 09:34:28Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -279,9 +279,11 @@ class Publisher: print >>self._stderr, ("""\ Exiting due to error. Use "--traceback" to diagnose. Please report errors to <docutils-users@lists.sf.net>. -Include "--traceback" output, Docutils version (%s [%s]), +Include "--traceback" output, Docutils version (%s%s), Python version (%s), your OS type & version, and the -command line used.""" % (__version__, __version_details__, +command line used.""" % (__version__, + docutils.__version_details__ and + ' [%s]'%docutils.__version_details__ or '', sys.version.split()[0])) def report_SystemMessage(self, error): diff --git a/docutils/src/main/resources/docutils/docutils/frontend.py b/docutils/src/main/resources/docutils/docutils/frontend.py index f837c62..1aeae5c 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 7584 2013-01-01 20:00:21Z milde $ +# $Id: frontend.py 8126 2017-06-23 09:34:28Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -40,7 +40,8 @@ from optparse import SUPPRESS_HELP import docutils import docutils.utils import docutils.nodes -from docutils.utils.error_reporting import locale_encoding, ErrorOutput, ErrorString +from docutils.utils.error_reporting import (locale_encoding, SafeString, + ErrorOutput, ErrorString) def store_multiple(option, opt, value, parser, *args, **kwargs): @@ -205,10 +206,45 @@ def validate_strip_class(setting, value, option_parser, for cls in value: normalized = docutils.nodes.make_id(cls) if cls != normalized: - raise ValueError('invalid class value %r (perhaps %r?)' + raise ValueError('Invalid class value %r (perhaps %r?)' % (cls, normalized)) return value +def validate_smartquotes_locales(setting, value, option_parser, + config_parser=None, config_section=None): + """Check/normalize a comma separated list of smart quote definitions. + + Return a list of (language-tag, quotes) string tuples.""" + + # value is a comma separated string list: + value = validate_comma_separated_list(setting, value, option_parser, + config_parser, config_section) + # validate list elements + lc_quotes = [] + for item in value: + try: + lang, quotes = item.split(':', 1) + except AttributeError: + # this function is called for every option added to `value` + # -> ignore if already a tuple: + lc_quotes.append(item) + continue + except ValueError: + raise ValueError(u'Invalid value "%s".' + ' Format is "<language>:<quotes>".' + % item.encode('ascii', 'backslashreplace')) + # parse colon separated string list: + quotes = quotes.strip() + multichar_quotes = quotes.split(':') + if len(multichar_quotes) == 4: + quotes = multichar_quotes + elif len(quotes) != 4: + raise ValueError('Invalid value "%s". Please specify 4 quotes\n' + ' (primary open/close; secondary open/close).' + % item.encode('ascii', 'backslashreplace')) + lc_quotes.append((lang,quotes)) + return lc_quotes + def make_paths_absolute(pathdict, keys, base_path=None): """ Interpret filesystem path settings relative to the `base_path` given. @@ -534,8 +570,10 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): config_section = 'general' - version_template = ('%%prog (Docutils %s [%s], Python %s, on %s)' - % (docutils.__version__, docutils.__version_details__, + version_template = ('%%prog (Docutils %s%s, Python %s, on %s)' + % (docutils.__version__, + docutils.__version_details__ and + ' [%s]'%docutils.__version_details__ or '', sys.version.split()[0], sys.platform)) """Default version message.""" @@ -568,7 +606,7 @@ class OptionParser(optparse.OptionParser, docutils.SettingsSpec): try: config_settings = self.get_standard_config_settings() except ValueError, error: - self.error(error) + self.error(SafeString(error)) self.set_defaults_from_dict(config_settings.__dict__) def populate_from_components(self, components): diff --git a/docutils/src/main/resources/docutils/docutils/io.py b/docutils/src/main/resources/docutils/docutils/io.py index 234e14c..8428318 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 7864 2015-04-12 09:57:05Z milde $ +# $Id: io.py 8129 2017-06-27 14:55:22Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -122,7 +122,7 @@ class Input(TransformSpec): '%s.\n(%s)' % (', '.join([repr(enc) for enc in encodings]), ErrorString(error))) - coding_slug = re.compile(b("coding[:=]\s*([-\w.]+)")) + coding_slug = re.compile(b(r"coding[:=]\s*([-\w.]+)")) """Encoding declaration pattern.""" byte_order_marks = ((codecs.BOM_UTF8, 'utf-8'), # 'utf-8-sig' new in v2.5 @@ -204,7 +204,7 @@ class FileInput(Input): """ def __init__(self, source=None, source_path=None, encoding=None, error_handler='strict', - autoclose=True, handle_io_errors=None, mode='rU'): + autoclose=True, mode='rU', **kwargs): """ :Parameters: - `source`: either a file-like object (which is read directly), or @@ -214,7 +214,6 @@ class FileInput(Input): - `error_handler`: the encoding error handler to use. - `autoclose`: close automatically after read (except when `sys.stdin` is the source). - - `handle_io_errors`: ignored, deprecated, will be removed. - `mode`: how the file is to be opened (see standard function `open`). The default 'rU' provides universal newline support for text files. @@ -222,6 +221,16 @@ class FileInput(Input): 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: @@ -381,7 +390,7 @@ class FileOutput(Output): try: self.destination.buffer.write(data) except AttributeError: - if check_encoding(self.destination, + if check_encoding(self.destination, self.encoding) is False: raise ValueError('Encoding of %s (%s) differs \n' ' from specified encoding (%s)' % diff --git a/docutils/src/main/resources/docutils/docutils/languages/sv.py b/docutils/src/main/resources/docutils/docutils/languages/sv.py index b35b7ac..dfc0aeb 100644 --- a/docutils/src/main/resources/docutils/docutils/languages/sv.py +++ b/docutils/src/main/resources/docutils/docutils/languages/sv.py @@ -1,4 +1,5 @@ -# $Id: sv.py 4564 2006-05-21 20:44:42Z wiemann $ +# -*- coding: utf-8 -*- +# $Id: sv.py 8006 2016-12-22 23:02:44Z milde $ # Author: Adam Chodorowski <chodorowski@users.sourceforge.net> # Copyright: This module has been placed in the public domain. @@ -14,8 +15,8 @@ Swedish language mappings for language-dependent features of Docutils. __docformat__ = 'reStructuredText' labels = { - 'author': u'F\u00f6rfattare', - 'authors': u'F\u00f6rfattare', + 'author': u'Författare', + 'authors': u'Författare', 'organization': u'Organisation', 'address': u'Adress', 'contact': u'Kontakt', @@ -27,20 +28,20 @@ labels = { 'dedication': u'Dedikation', 'abstract': u'Sammanfattning', 'attention': u'Observera!', - 'caution': u'Varning!', + 'caution': u'Akta!', # 'Varning' already used for 'warning' 'danger': u'FARA!', 'error': u'Fel', - 'hint': u'V\u00e4gledning', + 'hint': u'Vink', 'important': u'Viktigt', 'note': u'Notera', 'tip': u'Tips', 'warning': u'Varning', - 'contents': u'Inneh\u00e5ll' } + 'contents': u'Innehåll' } """Mapping of node class name to label text.""" bibliographic_fields = { # 'Author' and 'Authors' identical in Swedish; assume the plural: - u'f\u00f6rfattare': 'authors', + u'författare': 'authors', u' n/a': 'author', u'organisation': 'organization', u'adress': 'address', 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 6bf1e52..35e6f55 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 7961 2016-07-28 22:02:47Z milde $ +# $Id: __init__.py 8068 2017-05-08 22:10:39Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -141,11 +141,17 @@ class Parser(docutils.parsers.Parser): ('Change straight quotation marks to typographic form: ' 'one of "yes", "no", "alt[ernative]" (default "no").', ['--smart-quotes'], - {'default': False, 'validator': frontend.validate_ternary}), + {'default': False, 'metavar': '<yes/no/alt>', + 'validator': frontend.validate_ternary}), + ('Characters to use as "smart quotes" for <language>. ', + ['--smartquotes-locales'], + {'metavar': '<language:quotes[,language:quotes,...]>', + 'action': 'append', + 'validator': frontend.validate_smartquotes_locales}), ('Inline markup recognized at word boundaries only ' '(adjacent to punctuation or whitespace). ' 'Force character-level inline markup recognition with ' - '"\ " (backslash + space). Default.', + '"\\ " (backslash + space). Default.', ['--word-level-inline-markup'], {'action': 'store_false', 'dest': 'character_level_inline_markup'}), ('Inline markup recognized anywhere, regardless of surrounding ' 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 b1ede9c..5109058 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 7785 2015-02-08 23:51:45Z grubert $ +# $Id: __init__.py 8024 2017-02-06 00:41:48Z goodger $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -13,6 +13,7 @@ import codecs import sys from docutils import nodes +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__ @@ -189,7 +190,7 @@ def path(argument): def uri(argument): """ - Return the URI argument with whitespace removed. + Return the URI argument with unescaped whitespace removed. (Directive option conversion function.) Raise ``ValueError`` if no argument is found. @@ -197,7 +198,8 @@ def uri(argument): if argument is None: raise ValueError('argument required but none supplied') else: - uri = ''.join(argument.split()) + parts = split_escaped_whitespace(escape2null(argument)) + uri = ' '.join(''.join(unescape(part).split()) for part in parts) return uri def nonnegative_int(argument): 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 0e890b6..f1977e4 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 7958 2016-07-28 21:52:14Z milde $ +# $Id: tables.py 8039 2017-02-28 12:19:20Z milde $ # Authors: David Goodger <goodger@python.org>; David Priest # Copyright: This module has been placed in the public domain. @@ -45,6 +45,8 @@ class Table(Directive): text_nodes, messages = self.state.inline_text(title_text, self.lineno) title = nodes.title(title_text, '', *text_nodes) + (title.source, + title.line) = self.state_machine.get_source_and_line(self.lineno) else: title = None messages = [] @@ -112,13 +114,7 @@ class Table(Directive): 'No table data detected in CSV file.', nodes.literal_block( self.block_text, self.block_text), line=self.lineno) raise SystemMessagePropagation(error) - if self.widths == 'auto': - widths = 'auto' - elif self.widths: # "grid" or list of integers - widths = 'given' - else: - widths = self.widths - return widths, col_widths + return col_widths def extend_short_rows_with_empty_cells(self, columns, parts): for part in parts: @@ -253,7 +249,7 @@ class CSVTable(Table): self.check_table_dimensions(rows, header_rows, stub_columns) table_head.extend(rows[:header_rows]) table_body = rows[header_rows:] - widths, col_widths = self.get_column_widths(max_cols) + col_widths = self.get_column_widths(max_cols) self.extend_short_rows_with_empty_cells(max_cols, (table_head, table_body)) except SystemMessagePropagation, detail: @@ -269,7 +265,7 @@ class CSVTable(Table): return [error] table = (col_widths, table_head, table_body) table_node = self.state.build_table(table, self.content_offset, - stub_columns, widths=widths) + stub_columns, widths=self.widths) table_node['classes'] += self.options.get('class', []) if 'align' in self.options: table_node['align'] = self.options.get('align') @@ -413,7 +409,7 @@ class ListTable(Table): node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) try: - num_cols, widths, col_widths = self.check_list_content(node) + num_cols, col_widths = self.check_list_content(node) table_data = [[item.children for item in row_list[0]] for row_list in node[0]] header_rows = self.options.get('header-rows', 0) @@ -421,7 +417,7 @@ class ListTable(Table): self.check_table_dimensions(table_data, header_rows, stub_columns) except SystemMessagePropagation, detail: return [detail.args[0]] - table_node = self.build_table_from_list(table_data, widths, col_widths, + 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') @@ -467,14 +463,15 @@ class ListTable(Table): raise SystemMessagePropagation(error) else: num_cols = len(item[0]) - widths, col_widths = self.get_column_widths(num_cols) - return num_cols, widths, col_widths + col_widths = self.get_column_widths(num_cols) + return num_cols, col_widths - def build_table_from_list(self, table_data, widths, col_widths, header_rows, - stub_columns): + def build_table_from_list(self, table_data, col_widths, header_rows, stub_columns): table = nodes.table() - if widths: - table['classes'] += ['colwidths-%s' % widths] + if self.widths == 'auto': + table['classes'] += ['colwidths-auto'] + elif self.widths: # "grid" or list of integers + table['classes'] += ['colwidths-given'] tgroup = nodes.tgroup(cols=len(col_widths)) table += tgroup for col_width in col_widths: diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/de.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/de.py index a187876..92ea234 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/de.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/de.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: de.py 7223 2011-11-21 16:43:06Z milde $ +# $Id: de.py 8006 2016-12-22 23:02:44Z milde $ # Authors: Engelbert Gruber <grubert@users.sourceforge.net>; # Lea Wiemann <LeWiemann@gmail.com> # Copyright: This module has been placed in the public domain. @@ -30,13 +30,14 @@ directives = { 'warnung': 'warning', 'ermahnung': 'admonition', 'kasten': 'sidebar', - 'seitenkasten': 'sidebar', + 'seitenkasten': 'sidebar', # kept for backwards compatibiltity + 'seitenleiste': 'sidebar', 'thema': 'topic', - 'zeilen-block': 'line-block', + 'zeilenblock': 'line-block', 'parsed-literal (translation required)': 'parsed-literal', 'rubrik': 'rubric', 'epigraph': 'epigraph', - 'highlights (translation required)': 'highlights', + 'highlights': 'highlights', u'pull-quote': 'pull-quote', # commonly used in German too u'seitenansprache': 'pull-quote', # cf. http://www.typografie.info/2/wiki.php?title=Seitenansprache 'zusammengesetzt': 'compound', @@ -45,7 +46,7 @@ directives = { #'fragen': 'questions', 'tabelle': 'table', 'csv-tabelle': 'csv-table', - 'list-table (translation required)': 'list-table', + 'listentabelle': 'list-table', u'mathe': 'math', u'formel': 'math', 'meta': 'meta', @@ -62,14 +63,14 @@ directives = { 'datum': 'date', 'klasse': 'class', 'rolle': 'role', - u'default-role (translation required)': 'default-role', - u'title (translation required)': 'title', + u'standardrolle': 'default-role', + u'titel': 'title', 'inhalt': 'contents', - 'kapitel-nummerierung': 'sectnum', - 'abschnitts-nummerierung': 'sectnum', - u'linkziel-fußfnoten': 'target-notes', - u'header (translation required)': 'header', - u'footer (translation required)': 'footer', + u'kapitelnummerierung': 'sectnum', + u'abschnittsnummerierung': 'sectnum', + u'linkziel-fußnoten': 'target-notes', + u'kopfzeilen': 'header', + u'fußzeilen': 'footer', #u'fußfnoten': 'footnotes', #'zitate': 'citations', } @@ -86,7 +87,8 @@ roles = { 'titel-referenz': 'title-reference', 'pep-referenz': 'pep-reference', 'rfc-referenz': 'rfc-reference', - 'betonung': 'emphasis', + 'betonung': 'emphasis', # for backwards compatibility + 'betont': 'emphasis', 'fett': 'strong', u'wörtlich': 'literal', u'mathe': 'math', diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/sv.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/sv.py index 7025f5f..01363bd 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/sv.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/sv.py @@ -1,4 +1,5 @@ -# $Id: sv.py 7119 2011-09-02 13:00:23Z milde $ +# -*- coding: utf-8 -*- +# $Id: sv.py 8012 2017-01-03 23:08:19Z milde $ # Author: Adam Chodorowski <chodorowski@users.sourceforge.net> # Copyright: This module has been placed in the public domain. @@ -13,55 +14,55 @@ Swedish language mappings for language-dependent features of reStructuredText. __docformat__ = 'reStructuredText' - directives = { u'observera': 'attention', - u'caution (translation required)': 'caution', - u'code (translation required)': 'code', + u'akta': 'caution', # also 'försiktigt' + u'kod': 'code', u'fara': 'danger', u'fel': 'error', - u'v\u00e4gledning': 'hint', + u'vink': 'hint', # also 'hint' u'viktigt': 'important', u'notera': 'note', u'tips': 'tip', u'varning': 'warning', - u'admonition (translation required)': 'admonition', - u'sidebar (translation required)': 'sidebar', - u'\u00e4mne': 'topic', - u'line-block (translation required)': 'line-block', - u'parsed-literal (translation required)': 'parsed-literal', - u'mellanrubrik': 'rubric', - u'epigraph (translation required)': 'epigraph', - u'highlights (translation required)': 'highlights', + u'anmärkning': 'admonition', # literal 'tillrättavisning', 'förmaning' + u'sidorad': 'sidebar', + u'ämne': 'topic', + u'tema': 'topic', + u'rad-block': 'line-block', + u'parsed-literal (translation required)': 'parsed-literal', # 'tolkad-bokstavlig'? + u'rubrik': 'rubric', + u'epigraf': 'epigraph', + u'höjdpunkter': 'highlights', u'pull-quote (translation required)': 'pull-quote', - u'compound (translation required)': 'compound', - u'container (translation required)': 'container', - # u'fr\u00e5gor': 'questions', + u'sammansatt': 'compound', + u'container': 'container', + # u'frågor': 'questions', # NOTE: A bit long, but recommended by http://www.nada.kth.se/dataterm/: - # u'fr\u00e5gor-och-svar': 'questions', - # u'vanliga-fr\u00e5gor': 'questions', - u'table (translation required)': 'table', - u'csv-table (translation required)': 'csv-table', - u'list-table (translation required)': 'list-table', + # u'frågor-och-svar': 'questions', + # u'vanliga-frågor': 'questions', + u'tabell': 'table', + u'csv-tabell': 'csv-table', + u'list-tabell': 'list-table', u'meta': 'meta', - 'math (translation required)': 'math', + u'matematik': 'math', # u'bildkarta': 'imagemap', # FIXME: Translation might be too literal. u'bild': 'image', u'figur': 'figure', - u'inkludera': 'include', - u'r\u00e5': 'raw', # FIXME: Translation might be too literal. - u'ers\u00e4tt': 'replace', + u'inkludera': 'include', + u'rå': 'raw', + u'ersätta': 'replace', u'unicode': 'unicode', u'datum': 'date', - u'class (translation required)': 'class', - u'role (translation required)': 'role', - u'default-role (translation required)': 'default-role', - u'title (translation required)': 'title', - u'inneh\u00e5ll': 'contents', + u'klass': 'class', + u'roll': 'role', + u'standardroll': 'default-role', + u'titel': 'title', + u'innehåll': 'contents', u'sektionsnumrering': 'sectnum', u'target-notes (translation required)': 'target-notes', - u'header (translation required)': 'header', - u'footer (translation required)': 'footer', + u'sidhuvud': 'header', + u'sidfot': 'footer', # u'fotnoter': 'footnotes', # u'citeringar': 'citations', } @@ -69,26 +70,26 @@ directives = { mapping.""" roles = { - u'abbreviation (translation required)': 'abbreviation', - u'acronym (translation required)': 'acronym', - u'code (translation required)': 'code', - u'index (translation required)': 'index', - u'subscript (translation required)': 'subscript', - u'superscript (translation required)': 'superscript', - u'title-reference (translation required)': 'title-reference', - u'pep-reference (translation required)': 'pep-reference', - u'rfc-reference (translation required)': 'rfc-reference', - u'emphasis (translation required)': 'emphasis', - u'strong (translation required)': 'strong', - u'literal (translation required)': 'literal', - 'math (translation required)': 'math', - u'named-reference (translation required)': 'named-reference', - u'anonymous-reference (translation required)': 'anonymous-reference', - u'footnote-reference (translation required)': 'footnote-reference', - u'citation-reference (translation required)': 'citation-reference', - u'substitution-reference (translation required)': 'substitution-reference', - u'target (translation required)': 'target', - u'uri-reference (translation required)': 'uri-reference', - u'r\u00e5': 'raw',} + u'förkortning': 'abbreviation', + u'akronym': 'acronym', + u'kod': 'code', + u'index': 'index', + u'nedsänkt': 'subscript', + u'upphöjd': 'superscript', + u'titel-referens': 'title-reference', + u'pep-referens': 'pep-reference', + u'rfc-referens': 'rfc-reference', + u'betoning': 'emphasis', + u'stark': 'strong', + u'bokstavlig': 'literal', # also 'ordagranna' + u'matematik': 'math', + u'namngiven-referens': 'named-reference', + u'anonym-referens': 'anonymous-reference', + u'fotnot-referens': 'footnote-reference', + u'citat-referens': 'citation-reference', + u'ersättnings-referens': 'substitution-reference', + u'mål': 'target', + u'uri-referens': 'uri-reference', + u'rå': 'raw',} """Mapping of Swedish role names to canonical role names for interpreted text. """ 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 35de9d2..fa4c507 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 7958 2016-07-28 21:52:14Z milde $ +# $Id: states.py 8060 2017-04-19 20:00:04Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -117,6 +117,7 @@ from docutils.parsers.rst import directives, languages, tableparser, roles from docutils.parsers.rst.languages import en as _fallback_language_module from docutils.utils import escape2null, unescape, column_width from docutils.utils import punctuation_chars, roman, urischemes +from docutils.utils import split_escaped_whitespace class MarkupError(DataError): pass class UnknownInterpretedRoleError(DataError): pass @@ -463,11 +464,13 @@ class Inliner: """ def __init__(self): - pass + self.implicit_dispatch = [] + """List of (pattern, bound method) tuples, used by + `self.implicit_inline`.""" def init_customizations(self, settings): # lookahead and look-behind expressions for inline markup rules - if settings.character_level_inline_markup: + if getattr(settings, 'character_level_inline_markup', False): start_string_prefix = u'(^|(?<!\x00))' end_string_suffix = u'' else: @@ -478,7 +481,6 @@ class Inliner: (punctuation_chars.closing_delimiters, punctuation_chars.delimiters, punctuation_chars.closers)) - args = locals().copy() args.update(vars(self.__class__)) @@ -508,6 +510,9 @@ class Inliner: ) ] ) + self.start_string_prefix = start_string_prefix + self.end_string_suffix = end_string_suffix + self.parts = parts self.patterns = Struct( initial=build_regexp(parts), @@ -540,12 +545,12 @@ class Inliner: $ # end of string """ % args, re.VERBOSE | re.UNICODE), literal=re.compile(self.non_whitespace_before + '(``)' - + end_string_suffix), + + end_string_suffix, re.UNICODE), target=re.compile(self.non_whitespace_escape_before - + r'(`)' + end_string_suffix), + + r'(`)' + end_string_suffix, re.UNICODE), substitution_ref=re.compile(self.non_whitespace_escape_before + r'(\|_{0,2})' - + end_string_suffix), + + end_string_suffix, re.UNICODE), email=re.compile(self.email_pattern % args + '$', re.VERBOSE | re.UNICODE), uri=re.compile( @@ -595,10 +600,8 @@ class Inliner: (RFC(-|\s+)?(?P<rfcnum>\d+)) %(end_string_suffix)s""" % args, re.VERBOSE | re.UNICODE)) - self.implicit_dispatch = [(self.patterns.uri, self.standalone_uri),] - """List of (pattern, bound method) tuples, used by - `self.implicit_inline`.""" - + self.implicit_dispatch.append((self.patterns.uri, + self.standalone_uri)) if settings.pep_references: self.implicit_dispatch.append((self.patterns.pep, self.pep_reference)) @@ -656,12 +659,11 @@ class Inliner: # Inline object recognition # ------------------------- - # print start_string_prefix.encode('utf8') - # TODO: support non-ASCII whitespace in the following 4 patterns? - non_whitespace_before = r'(?<![ \n])' - non_whitespace_escape_before = r'(?<![ \n\x00])' - non_unescaped_whitespace_escape_before = r'(?<!(?<!\x00)[ \n\x00])' - non_whitespace_after = r'(?![ \n])' + # See also init_customizations(). + non_whitespace_before = r'(?<!\s)' + non_whitespace_escape_before = r'(?<![\s\x00])' + non_unescaped_whitespace_escape_before = r'(?<!(?<!\x00)[\s\x00])' + non_whitespace_after = r'(?!\s)' # Alphanumerics with isolated internal [-._+:] chars (i.e. not 2 together): simplename = r'(?:(?!_)\w)+(?:[-._+:](?:(?!_)\w)+)*' # Valid URI characters (see RFC 2396 & RFC 2732); @@ -807,7 +809,9 @@ class Inliner: target.indirect_reference_name = aliastext[:-1] else: aliastype = 'uri' - alias = ''.join(aliastext.split()) + alias_parts = split_escaped_whitespace(match.group(2)) + alias = ' '.join(''.join(unescape(part).split()) + for part in alias_parts) alias = self.adjust_uri(alias) if alias.endswith(r'\_'): alias = alias[:-2] + '_' @@ -1767,8 +1771,10 @@ class Body(RSTState): def build_table(self, tabledata, tableline, stub_columns=0, widths=None): colwidths, headrows, bodyrows = tabledata table = nodes.table() - if widths: - table['classes'] += ['colwidths-%s' % widths] + if widths == 'auto': + table['classes'] += ['colwidths-auto'] + elif widths: # "grid" or list of integers + table['classes'] += ['colwidths-given'] tgroup = nodes.tgroup(cols=len(colwidths)) table += tgroup for colwidth in colwidths: @@ -1958,8 +1964,10 @@ class Body(RSTState): refname = self.is_reference(reference) if refname: return 'refname', refname - reference = ''.join([''.join(line.split()) for line in block]) - return 'refuri', unescape(reference) + ref_parts = split_escaped_whitespace(' '.join(block)) + reference = ' '.join(''.join(unescape(part).split()) + for part in ref_parts) + return 'refuri', reference def is_reference(self, reference): match = self.explicit.patterns.reference.match( diff --git a/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py b/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py index 8bfc64f..92287da 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 7897 2015-05-29 11:48:20Z milde $ +# $Id: frontmatter.py 8117 2017-06-18 23:38:18Z milde $ # Author: David Goodger, Ueli Schlaepfer <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -403,7 +403,7 @@ class DocInfo(Transform): for field in field_list: try: name = field[0][0].astext() - normedname = nodes.make_id(name) + normedname = nodes.fully_normalize_name(name) if not (len(field) == 2 and normedname in bibliofields and self.check_empty_biblio_field(field, name)): raise TransformError @@ -433,8 +433,10 @@ class DocInfo(Transform): and isinstance(field[-1][0], nodes.paragraph): utils.clean_rcs_keywords( field[-1][0], self.rcs_keyword_substitutions) - if normedname and normedname not in bibliofields: - field['classes'].append(normedname) + if normedname not in bibliofields: + classvalue = nodes.make_id(normedname) + if classvalue: + field['classes'].append(classvalue) docinfo.append(field) nodelist = [] if len(docinfo) != 0: diff --git a/docutils/src/main/resources/docutils/docutils/transforms/peps.py b/docutils/src/main/resources/docutils/docutils/transforms/peps.py index 519a707..94b47c1 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 7773 2014-10-08 15:08:29Z grubert $ +# $Id: peps.py 7995 2016-12-10 17:50:59Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -113,7 +113,7 @@ class Headers(Transform): elif name in ('replaces', 'replaced-by', 'requires'): newbody = [] space = nodes.Text(' ') - for refpep in re.split(',?\s+', body.astext()): + for refpep in re.split(r',?\s+', body.astext()): pepno = int(refpep) newbody.append(nodes.reference( refpep, refpep, diff --git a/docutils/src/main/resources/docutils/docutils/transforms/references.py b/docutils/src/main/resources/docutils/docutils/transforms/references.py index e88460d..f271067 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/references.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/references.py @@ -1,4 +1,4 @@ -# $Id: references.py 7624 2013-03-07 14:10:26Z milde $ +# $Id: references.py 8067 2017-05-04 20:10:03Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -710,6 +710,7 @@ class Substitutions(Transform): raise CircularSubstitutionDefinitionError else: nested[nested_name].append(key) + nested_ref['ref-origin'] = ref subreflist.append(nested_ref) except CircularSubstitutionDefinitionError: parent = ref.parent @@ -721,9 +722,13 @@ class Substitutions(Transform): 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) + '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) @@ -893,7 +898,10 @@ class DanglingReferencesVisitor(nodes.SparseNodeVisitor): msgid = self.document.set_id(msg) prb = nodes.problematic( node.rawsource, node.rawsource, refid=msgid) - prbid = self.document.set_id(prb) + try: + prbid = node['ids'][0] + except IndexError: + prbid = self.document.set_id(prb) msg.add_backref(prbid) node.replace_self(prb) else: diff --git a/docutils/src/main/resources/docutils/docutils/transforms/universal.py b/docutils/src/main/resources/docutils/docutils/transforms/universal.py index c485d72..40036c0 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 7957 2016-07-28 20:59:14Z milde $ +# $Id: universal.py 8144 2017-07-26 21:25:08Z milde $ # -*- coding: utf-8 -*- # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Günter Milde # Maintainer: docutils-develop@lists.sourceforge.net @@ -208,6 +208,7 @@ class StripClassesAndElements(Transform): if class_value in self.strip_elements: return 1 + class SmartQuotes(Transform): """ @@ -218,6 +219,20 @@ class SmartQuotes(Transform): default_priority = 850 + 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, + nodes.raw, nodes.problematic) + """Do not change quotes in instances of these inline nodes.""" + + smartquotes_action = 'qDe' + """Setting to select smartquote transformations. + + The default 'qDe' educates normal quote characters: (", '), + em- and en-dashes (---, --) and ellipses (...). + """ + def __init__(self, document, startnode): Transform.__init__(self, document, startnode=startnode) self.unsupported_languages = set() @@ -230,11 +245,7 @@ class SmartQuotes(Transform): False: 'plain'} for txtnode in txtnodes: nodetype = texttype[isinstance(txtnode.parent, - (nodes.literal, - nodes.math, - nodes.image, - nodes.raw, - nodes.problematic))] + self.literal_nodes)] yield (nodetype, txtnode.astext()) @@ -249,12 +260,15 @@ class SmartQuotes(Transform): # print repr(alternative) document_language = self.document.settings.language_code + lc_smartquotes = self.document.settings.smartquotes_locales + if lc_smartquotes: + smartquotes.smartchars.quotes.update(dict(lc_smartquotes)) # "Educate" quotes in normal text. Handle each block of text # (TextElement node) as a unit to keep context around inline nodes: for node in self.document.traverse(nodes.TextElement): # skip preformatted text blocks and special elements: - if isinstance(node, (nodes.FixedTextElement, nodes.Special)): + if isinstance(node, self.nodes_to_skip): continue # nested TextElements are not "block-level" elements: if isinstance(node.parent, nodes.TextElement): @@ -273,7 +287,7 @@ class SmartQuotes(Transform): lang = lang.replace('-x-altquot', '') else: lang += '-x-altquot' - # drop subtags missing in quotes: + # drop unsupported subtags: for tag in utils.normalize_language_tag(lang): if tag in smartquotes.smartchars.quotes: lang = tag @@ -286,11 +300,12 @@ class SmartQuotes(Transform): lang = '' # Iterator educating quotes in plain text: - # '2': set all, using old school en- and em- dash shortcuts + # (see "utils/smartquotes.py" for the attribute setting) teacher = smartquotes.educate_tokens(self.get_tokens(txtnodes), - attr='2', language=lang) + attr=self.smartquotes_action, language=lang) for txtnode, newtext in zip(txtnodes, teacher): - txtnode.parent.replace(txtnode, nodes.Text(newtext)) + txtnode.parent.replace(txtnode, nodes.Text(newtext, + rawsource=txtnode.rawsource)) - self.unsupported_languages = set() # reset + self.unsupported_languages = set() # reset diff --git a/docutils/src/main/resources/docutils/docutils/utils/__init__.py b/docutils/src/main/resources/docutils/docutils/utils/__init__.py index c319217..39976fb 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 7668 2013-06-04 12:46:30Z milde $ +# $Id: __init__.py 8141 2017-07-08 17:05:18Z goodger $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -13,9 +13,10 @@ import sys import os import os.path import re +import itertools import warnings import unicodedata -from docutils import ApplicationError, DataError +from docutils import ApplicationError, DataError, __version_info__ from docutils import nodes import docutils.io from docutils.utils.error_reporting import ErrorOutput, SafeString @@ -575,7 +576,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): +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. @@ -587,6 +588,16 @@ def unescape(text, restore_backslashes=False): text = ''.join(text.split(sep)) return text +def split_escaped_whitespace(text): + """ + Split `text` on escaped whitespace (null+space or null+newline). + Return a list of strings. + """ + strings = text.split('\x00 ') + strings = [string.split('\x00\n') for string in strings] + # flatten list of lists of strings to list of strings: + return list(itertools.chain(*strings)) + def strip_combining_chars(text): if isinstance(text, str) and sys.version_info < (3,0): return text @@ -595,8 +606,10 @@ def strip_combining_chars(text): def find_combining_chars(text): """Return indices of all combining chars in Unicode string `text`. + >>> from docutils.utils import find_combining_chars >>> find_combining_chars(u'A t̆ab̆lĕ') [3, 6, 9] + """ if isinstance(text, str) and sys.version_info < (3,0): return [] @@ -605,8 +618,10 @@ def find_combining_chars(text): def column_indices(text): """Indices of Unicode string `text` when skipping combining characters. + >>> from docutils.utils import column_indices >>> column_indices(u'A t̆ab̆lĕ') [0, 1, 2, 4, 5, 7, 8] + """ # TODO: account for asian wide chars here instead of using dummy # replacements in the tableparser? @@ -663,17 +678,21 @@ def normalize_language_tag(tag): Example: + >>> from docutils.utils import normalize_language_tag >>> normalize_language_tag('de_AT-1901') ['de-at-1901', 'de-at', 'de-1901', 'de'] + >>> normalize_language_tag('de-CH-x_altquot') + ['de-ch-x-altquot', 'de-ch', 'de-x-altquot', 'de'] + """ # normalize: - tag = tag.lower().replace('_','-') + 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) - taglist = [] - subtags = [subtag.replace('_', '-') for subtag in tag.split('-')] + tag = re.sub(r'_([a-zA-Z0-9])_', r'_\1-', tag) + subtags = [subtag for subtag in tag.split('_')] base_tag = [subtags.pop(0)] # find all combinations of subtags + taglist = [] for n in range(len(subtags), 0, -1): for tags in unique_combinations(subtags, n): taglist.append('-'.join(base_tag+tags)) @@ -747,3 +766,42 @@ class DependencyList(object): except AttributeError: output_file = None return '%s(%r, %s)' % (self.__class__.__name__, output_file, self.list) + + +release_level_abbreviations = { + 'alpha': 'a', + 'beta': 'b', + 'candidate': 'rc', + 'final': '',} + +def version_identifier(version_info=None): + # 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. + """ + if version_info is None: + version_info = __version_info__ + if version_info[2]: # version_info.micro + micro = '.%s' % version_info[2] + else: + micro = '' + releaselevel = release_level_abbreviations[ + version_info[3]] # version_info.releaselevel + if version_info[4]: # version_info.serial + serial = version_info[4] + else: + serial = '' + if version_info[5]: # 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 + micro, + releaselevel, + serial, + dev) + return version 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 c0f3109..314a506 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: 2015-04-20 16:05:27 +0200 (Mo, 20 Apr 2015) $ # :Copyright: This module has been placed in the public domain. from docutils import ApplicationError 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 a893d21..02e62eb 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 7947 2016-07-22 08:49:52Z milde $ +# :Id: $Id: error_reporting.py 8119 2017-06-22 20:59:19Z milde $ # :Copyright: © 2011 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -54,6 +54,8 @@ else: # and https://sourceforge.net/p/docutils/bugs/298/ if "unknown locale: UTF-8" in error.args: locale_encoding = "UTF-8" + else: + locale_encoding = None except: # any other problems determining the locale -> use None locale_encoding = None try: 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 9927e43..bcb4877 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/latex2mathml.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/latex2mathml.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# :Id: $Id: latex2mathml.py 7865 2015-04-12 10:06:43Z milde $ +# :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 @@ -151,8 +151,8 @@ mathscr = { } negatables = {'=': u'\u2260', - '\in': u'\u2209', - '\equiv': u'\u2262'} + r'\in': u'\u2209', + r'\equiv': u'\u2262'} # LaTeX to MathML translation stuff: class math: diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py b/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py index c65485b..1f61e23 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py @@ -4101,7 +4101,7 @@ class FormulaCommand(FormulaBit): def emptycommand(self, pos): """Check for an empty command: look for command disguised as ending. - Special case against '{ \{ \} }' situation.""" + Special case against '{ \\{ \\} }' situation.""" command = '' if not pos.isout(): ending = pos.nextending() @@ -4560,7 +4560,7 @@ class EquationEnvironment(MultiRowFormula): self.parserows(pos) class BeginCommand(CommandBit): - "A \\begin{}...\end command and what it entails (array, cases, aligned)" + "A \\begin{}...\\end command and what it entails (array, cases, aligned)" commandmap = {FormulaConfig.array['begin']:''} 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 d1a3997..041cf9c 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,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# :Copyright: © 2011 Günter Milde. +# :Id: $Id: punctuation_chars.py 8016 2017-01-17 15:06:17Z milde $ +# :Copyright: © 2011, 2017 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # # Copying and distribution of this file, with or without modification, @@ -9,29 +10,38 @@ # This file is offered as-is, without any warranty. # # .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause - -# :Id: $Id: punctuation_chars.py 7668 2013-06-04 12:46:30Z milde $ +# +# This file is generated by +# ``docutils/tools/dev/generate_punctuation_chars.py``. +# :: import sys, re import unicodedata -# punctuation characters around inline markup -# =========================================== -# -# This module provides the lists of characters for the implementation of -# the `inline markup recognition rules`_ in the reStructuredText parser -# (states.py) -# -# .. _inline markup recognition rules: -# ../../docs/ref/rst/restructuredtext.html#inline-markup +"""Docutils character category patterns. -# Docutils punctuation category sample strings -# -------------------------------------------- -# -# The sample strings are generated by punctuation_samples() and put here -# literal to avoid the time-consuming generation with every Docutils run. -# As the samples are used inside ``[ ]`` in regular expressions, hyphen and -# square brackets are escaped. :: + Patterns for the implementation of the `inline markup recognition rules`_ + in the reStructuredText parser `docutils.parsers.rst.states.py` based + on Unicode character categories. + The patterns are used inside ``[ ]`` in regular expressions. + + Rule (5) requires determination of matching open/close pairs. However, the + pairing of open/close quotes is ambiguous due to different typographic + conventions in different languages. The ``quote_pairs`` function tests + whether two characters form an open/close pair. + + The patterns are generated by + ``docutils/tools/dev/generate_punctuation_chars.py`` to prevent dependence + on the Python version and avoid the time-consuming generation with every + Docutils run. See there for motives and implementation details. + + The category of some characters changed with the development of the + Unicode standard. The current lists are generated with the help of the + "unicodedata" module of Python 2.7.13 (based on Unicode version 5.2.0). + + .. _inline markup recognition rules: + http://docutils.sf.net/docs/ref/rst/restructuredtext.html#inline-markup-reco... +""" openers = (u'"\'(<\\[{\u0f3a\u0f3c\u169b\u2045\u207d\u208d\u2329\u2768' u'\u276a\u276c\u276e\u2770\u2772\u2774\u27c5\u27e6\u27e8\u27ea' @@ -84,272 +94,29 @@ closing_delimiters = u'\\\\.,;!?' # Matching open/close quotes # -------------------------- -# Rule (5) requires determination of matching open/close pairs. However, -# the pairing of open/close quotes is ambigue due to different typographic -# conventions in different languages. - -quote_pairs = {u'\xbb': u'\xbb', # Swedish - u'\u2018': u'\u201a', # Greek - u'\u2019': u'\u2019', # Swedish - u'\u201a': u'\u2018\u2019', # German, Polish - u'\u201c': u'\u201e', # German - u'\u201e': u'\u201c\u201d', - u'\u201d': u'\u201d', # Swedish - u'\u203a': u'\u203a', # Swedish - } +quote_pairs = {# open char: matching closing characters # usage example + u'\xbb': u'\xbb', # » » Swedish + u'\u2018': u'\u201a', # ‘ ‚ Albanian/Greek/Turkish + u'\u2019': u'\u2019', # ’ ’ Swedish + u'\u201a': u'\u2018\u2019', # ‚ ‘ German ‚ ’ Polish + u'\u201c': u'\u201e', # “ „ Albanian/Greek/Turkish + u'\u201e': u'\u201c\u201d', # „ “ German „ ” Polish + u'\u201d': u'\u201d', # ” ” Swedish + u'\u203a': u'\u203a', # › › Swedish + } +"""Additional open/close quote pairs.""" def match_chars(c1, c2): + """Test whether `c1` and `c2` are a matching open/close character pair. + + Matching open/close pairs are at the same position in + `punctuation_chars.openers` and `punctuation_chars.closers`. + The pairing of open/close quotes is ambiguous due to different + typographic conventions in different languages, + so we test for additional matches stored in `quote_pairs`. + """ try: i = openers.index(c1) except ValueError: # c1 not in openers return False - return c2 == closers[i] or c2 in quote_pairs.get(c1, '') - - -# Running this file as a standalone module checks the definitions against a -# re-calculation:: - -if __name__ == '__main__': - - -# Unicode punctuation character categories -# ---------------------------------------- - - unicode_punctuation_categories = { - # 'Pc': 'Connector', # not used in Docutils inline markup recognition - 'Pd': 'Dash', - 'Ps': 'Open', - 'Pe': 'Close', - 'Pi': 'Initial quote', # may behave like Ps or Pe depending on usage - 'Pf': 'Final quote', # may behave like Ps or Pe depending on usage - 'Po': 'Other' - } - """Unicode character categories for punctuation""" - - -# generate character pattern strings -# ================================== - - def unicode_charlists(categories, cp_min=0, cp_max=None): - """Return dictionary of Unicode character lists. - - For each of the `catagories`, an item contains a list with all Unicode - characters with `cp_min` <= code-point <= `cp_max` that belong to - the category. - - The default values check every code-point supported by Python - (`sys.maxint` is 0x10FFFF in a "wide" build and 0xFFFF in a "narrow" - build, i.e. ucs4 and ucs2 respectively). - """ - # Determine highest code point with one of the given categories - # (may shorten the search time considerably if there are many - # categories with not too high characters): - if cp_max is None: - cp_max = max(x for x in xrange(sys.maxunicode+1) - if unicodedata.category(unichr(x)) in categories) - # print cp_max # => 74867 for unicode_punctuation_categories - charlists = {} - for cat in categories: - charlists[cat] = [unichr(x) for x in xrange(cp_min, cp_max+1) - if unicodedata.category(unichr(x)) == cat] - return charlists - - -# Character categories in Docutils -# -------------------------------- - - def punctuation_samples(): - - """Docutils punctuation category sample strings. - - Return list of sample strings for the categories "Open", "Close", - "Delimiters" and "Closing-Delimiters" used in the `inline markup - recognition rules`_. - """ - - # Lists with characters in Unicode punctuation character categories - cp_min = 160 # ASCII chars have special rules for backwards compatibility - ucharlists = unicode_charlists(unicode_punctuation_categories, cp_min) - - # match opening/closing characters - # -------------------------------- - # Rearange the lists to ensure matching characters at the same - # index position. - - # low quotation marks are also used as closers (e.g. in Greek) - # move them to category Pi: - ucharlists['Ps'].remove(u'‚') # 201A SINGLE LOW-9 QUOTATION MARK - ucharlists['Ps'].remove(u'„') # 201E DOUBLE LOW-9 QUOTATION MARK - ucharlists['Pi'] += [u'‚', u'„'] - - ucharlists['Pi'].remove(u'‛') # 201B SINGLE HIGH-REVERSED-9 QUOTATION MARK - ucharlists['Pi'].remove(u'‟') # 201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK - ucharlists['Pf'] += [u'‛', u'‟'] - - # 301F LOW DOUBLE PRIME QUOTATION MARK misses the opening pendant: - ucharlists['Ps'].insert(ucharlists['Pe'].index(u'\u301f'), u'\u301d') - - # print u''.join(ucharlists['Ps']).encode('utf8') - # print u''.join(ucharlists['Pe']).encode('utf8') - # print u''.join(ucharlists['Pi']).encode('utf8') - # print u''.join(ucharlists['Pf']).encode('utf8') - - # The Docutils character categories - # --------------------------------- - # - # The categorization of ASCII chars is non-standard to reduce - # both false positives and need for escaping. (see `inline markup - # recognition rules`_) - - # allowed before markup if there is a matching closer - openers = [u'"\'(<\\[{'] - for cat in ('Ps', 'Pi', 'Pf'): - openers.extend(ucharlists[cat]) - - # allowed after markup if there is a matching opener - closers = [u'"\')>\\]}'] - for cat in ('Pe', 'Pf', 'Pi'): - closers.extend(ucharlists[cat]) - - # non-matching, allowed on both sides - delimiters = [u'\\-/:'] - for cat in ('Pd', 'Po'): - delimiters.extend(ucharlists[cat]) - - # non-matching, after markup - closing_delimiters = [r'\\.,;!?'] - - # # Test open/close matching: - # for i in range(min(len(openers),len(closers))): - # print '%4d %s %s' % (i, openers[i].encode('utf8'), - # closers[i].encode('utf8')) - - return [u''.join(chars) for chars in (openers, closers, delimiters, - closing_delimiters)] - - def separate_wide_chars(s): - """Return (s1,s2) with characters above 0xFFFF in s2""" - maxunicode_narrow = 0xFFFF - l1 = [ch for ch in s if ord(ch) <= maxunicode_narrow] - l2 = [ch for ch in s if ord(ch) > maxunicode_narrow] - return ''.join(l1), ''.join(l2) - - def mark_intervals(s): - """Return s with shortcut notation for runs of consecutive characters - - Sort string and replace 'cdef' by 'c-f' and similar. - """ - l =[] - s = [ord(ch) for ch in s] - s.sort() - for n in s: - try: - if l[-1][-1]+1 == n: - l[-1].append(n) - else: - l.append([n]) - except IndexError: - l.append([n]) - - l2 = [] - for i in l: - i = [unichr(n) for n in i] - if len(i) > 2: - i = i[0], u'-', i[-1] - l2.extend(i) - - return ''.join(l2) - - def wrap_string(s, startstring= "(", - endstring = ")", wrap=65): - """Line-wrap a unicode string literal definition.""" - c = len(startstring) - contstring = "'\n" + ' ' * len(startstring) + "u'" - l = [startstring] - for ch in s: - c += 1 - if ch == '\\' and c > wrap: - c = len(startstring) - ch = contstring + ch - l.append(ch) - l.append(endstring) - return ''.join(l) - - -# print results -# ============= - -# (re) create and compare the samples: - - (o, c, d, cd) = punctuation_samples() - o, o_wide = separate_wide_chars(o) - c, c_wide = separate_wide_chars(c) - d, d_wide = separate_wide_chars(d) - d = d[:5] + mark_intervals(d[5:]) - d_wide = mark_intervals(d_wide) - if sys.maxunicode >= 0x10FFFF: # "wide" build - d += d_wide - if o != openers: - print '- openers = ur"""%s"""' % openers.encode('utf8') - print '+ openers = ur"""%s"""' % o.encode('utf8') - if o_wide: - print '+ openers-wide = ur"""%s"""' % o_wide.encode('utf8') - if c != closers: - print '- closers = ur"""%s"""' % closers.encode('utf8') - print '+ closers = ur"""%s"""' % c.encode('utf8') - if c_wide: - print '+ closers-wide = ur"""%s"""' % c_wide.encode('utf8') - if d != delimiters: - print '- delimiters = ur"%s"' % delimiters.encode('utf8') - print '+ delimiters = ur"%s"' % d.encode('utf8') - if cd != closing_delimiters: - print '- closing_delimiters = ur"%s"' % closing_delimiters.encode('utf8') - print '+ closing_delimiters = ur"%s"' % cd.encode('utf8') - # closing_delimiters are all ASCII characters - -# Print literal code to define the character sets: - - # `openers` and `closers` must be verbose and keep order because they are - # also used in `match_chars()`. - print wrap_string(repr(o), startstring='openers = (') - print wrap_string(repr(c), startstring='closers = (') - # delimiters: sort and use shortcut for intervals (saves ~150 characters): - print wrap_string(repr(d), startstring='delimiters = (') - # add characters in the upper plane only in a "wide" build: - print 'if sys.maxunicode >= 0x10FFFF: # "wide" build' - print wrap_string(repr(d_wide), startstring=' delimiters += (') - print 'closing_delimiters =', repr(cd) - -# test prints - - # print "wide" Unicode characters: - # ucharlists = unicode_charlists(unicode_punctuation_categories) - # for key in ucharlists: - # if key.endswith('wide'): - # print key, ucharlists[key] - - # print 'openers = ', repr(openers) - # print 'closers = ', repr(closers) - # print 'delimiters = ', repr(delimiters) - # print 'closing_delimiters = ', repr(closing_delimiters) - - # ucharlists = unicode_charlists(unicode_punctuation_categories) - # for cat, chars in ucharlists.items(): - # # print cat, chars - # # compact output (visible with a comprehensive font): - # print (u":%s: %s" % (cat, u''.join(chars))).encode('utf8') - -# verbose print - - # print 'openers:' - # for ch in openers: - # print ch.encode('utf8'), unicodedata.name(ch) - # print 'closers:' - # for ch in closers: - # print ch.encode('utf8'), unicodedata.name(ch) - # print 'delimiters:' - # for ch in delimiters: - # print ch.encode('utf8'), unicodedata.name(ch) - # print 'closing_delimiters:' - # for ch in closing_delimiters: - # print ch.encode('utf8'), unicodedata.name(ch) + return c2 == closers[i] or c2 in quote_pairs.get(c1, u'') diff --git a/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py b/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py index ae5ccc4..f7425ef 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 7933 2016-01-13 21:09:13Z milde $ +# :Id: $Id: smartquotes.py 8095 2017-05-30 21:04:18Z milde $ # :Copyright: © 2010 Günter Milde, # original `SmartyPants`_: © 2003 John Gruber # smartypants.py: © 2004, 2007 Chad Miller @@ -17,25 +17,25 @@ r""" -======================== -SmartyPants for Docutils -======================== +========================= +Smart Quotes for Docutils +========================= Synopsis ======== -Smart-quotes for Docutils. +"SmartyPants" is a free web publishing plug-in for Movable Type, Blosxom, and +BBEdit that easily translates plain ASCII punctuation characters into "smart" +typographic punctuation characters. -The original "SmartyPants" is a free web publishing plug-in for Movable Type, -Blosxom, and BBEdit that easily translates plain ASCII punctuation characters -into "smart" typographic punctuation characters. +``smartquotes.py`` is an adaption of "SmartyPants" to Docutils_. -`smartypants.py`, endeavours to be a functional port of -SmartyPants to Python, for use with Pyblosxom_. +* Using Unicode instead of HTML entities for typographic punctuation + characters, it works for any output format that supports Unicode. +* Supports `language specific quote characters`__. + +__ http://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks -`smartquotes.py` is an adaption of Smartypants to Docutils_. By using Unicode -characters instead of HTML entities for typographic quotes, it works for any -output format that supports Unicode. Authors ======= @@ -160,102 +160,25 @@ appropriate, such as source code or example markup. Backslash Escapes ================= -If you need to use literal straight quotes (or plain hyphens and -periods), SmartyPants accepts the following backslash escape sequences -to force non-smart punctuation. It does so by transforming the escape -sequence into a character: - -======== ===== ========= -Escape Value Character -======== ===== ========= -``\\\\`` \ \\ -\\" " " -\\' ' ' -\\. . . -\\- - \- -\\` ` \` -======== ===== ========= +If you need to use literal straight quotes (or plain hyphens and periods), +`smartquotes` accepts the following backslash escape sequences to force +ASCII-punctuation. Mind, that you need two backslashes as Docutils expands it, +too. + +======== ========= +Escape Character +======== ========= +``\\`` \\ +``\\"`` \\" +``\\'`` \\' +``\\.`` \\. +``\\-`` \\- +``\\``` \\` +======== ========= This is useful, for example, when you want to use straight quotes as foot and inch marks: 6\\'2\\" tall; a 17\\" iMac. -Options -======= - -For Pyblosxom users, the ``smartypants_attributes`` attribute is where you -specify configuration options. - -Numeric values are the easiest way to configure SmartyPants' behavior: - -"0" - Suppress all transformations. (Do nothing.) -"1" - Performs default SmartyPants transformations: quotes (including - \`\`backticks'' -style), em-dashes, and ellipses. "``--``" (dash dash) - is used to signify an em-dash; there is no support for en-dashes. - -"2" - Same as smarty_pants="1", except that it uses the old-school typewriter - shorthand for dashes: "``--``" (dash dash) for en-dashes, "``---``" - (dash dash dash) - for em-dashes. - -"3" - Same as smarty_pants="2", but inverts the shorthand for dashes: - "``--``" (dash dash) for em-dashes, and "``---``" (dash dash dash) for - en-dashes. - -"-1" - Stupefy mode. Reverses the SmartyPants transformation process, turning - the characters produced by SmartyPants into their ASCII equivalents. - E.g. "“" is turned into a simple double-quote (\"), "—" is - turned into two dashes, etc. - - -The following single-character attribute values can be combined to toggle -individual transformations from within the smarty_pants attribute. For -example, to educate normal quotes and em-dashes, but not ellipses or -\`\`backticks'' -style quotes: - -``py['smartypants_attributes'] = "1"`` - -"q" - Educates normal quote characters: (") and ('). - -"b" - Educates \`\`backticks'' -style double quotes. - -"B" - Educates \`\`backticks'' -style double quotes and \`single' quotes. - -"d" - Educates em-dashes. - -"D" - Educates em-dashes and en-dashes, using old-school typewriter shorthand: - (dash dash) for en-dashes, (dash dash dash) for em-dashes. - -"i" - Educates em-dashes and en-dashes, using inverted old-school typewriter - shorthand: (dash dash) for em-dashes, (dash dash dash) for en-dashes. - -"e" - Educates ellipses. - -"w" - Translates any instance of ``"`` into a normal double-quote character. - This should be of no interest to most people, but of particular interest - to anyone who writes their posts using Dreamweaver, as Dreamweaver - inexplicably uses this entity to represent a literal double-quote - character. SmartyPants only educates normal quotes, not entities (because - ordinarily, entities are used for the explicit purpose of representing the - specific character they represent). The "w" option must be used in - conjunction with one (or both) of the other quote options ("q" or "b"). - Thus, if you wish to apply all SmartyPants transformations (quotes, en- - and em-dashes, and ellipses) and also translate ``"`` entities into - regular quotes so SmartyPants can educate them, you should pass the - following to the smarty_pants attribute: - Caveats ======= @@ -274,7 +197,7 @@ If you're the sort of person who just doesn't care, you might well want to continue not caring. Using straight quotes -- and sticking to the 7-bit ASCII character set in general -- is certainly a simpler way to live. -Even if you I *do* care about accurate typography, you still might want to +Even if you *do* care about accurate typography, you still might want to think twice before educating the quote characters in your weblog. One side effect of publishing curly quote characters is that it makes your weblog a bit harder for others to quote from using copy-and-paste. What @@ -300,21 +223,52 @@ Algorithmic Shortcomings ------------------------ One situation in which quotes will get curled the wrong way is when -apostrophes are used at the start of leading contractions. For example: +apostrophes are used at the start of leading contractions. For example:: -``'Twas the night before Christmas.`` + '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 a closing one. 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. +single-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. + +In English, the same character is used for apostrophe and closing single +quote (both plain and "smart" ones). For other locales (French, Italean, +Swiss, ...) "smart" single 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:: + + .. class:: language-de-CH + + "Er sagt: 'Ich fass' es nicht.'" + +will get a single closing guillemet instead of an apostrophe. + +This can be prevented by use use of the curly apostrophe character (’) in +the source:: + + - "Er sagt: 'Ich fass' es nicht.'" + + "Er sagt: 'Ich fass’ es nicht.'" Version History =============== -1.7 2012-11-19 +1.8: 2017-04-24 + - Command line front-end. + +1.7.1: 2017-03-19 + - Update and extend language-dependent quotes. + - Differentiate apostrophe from single quote. + +1.7: 2012-11-19 - Internationalization: language-dependent quotes. 1.6.1: 2012-11-06 @@ -358,10 +312,72 @@ Version History - Initial release """ +options = r""" +Options +======= + +Numeric values are the easiest way to configure SmartyPants' behavior: + +:0: Suppress all transformations. (Do nothing.) + +:1: Performs default SmartyPants transformations: quotes (including + \`\`backticks'' -style), em-dashes, and ellipses. "``--``" (dash dash) + is used to signify an em-dash; there is no support for en-dashes + +:2: Same as smarty_pants="1", except that it uses the old-school typewriter + shorthand for dashes: "``--``" (dash dash) for en-dashes, "``---``" + (dash dash dash) + for em-dashes. + +:3: Same as smarty_pants="2", but inverts the shorthand for dashes: + "``--``" (dash dash) for em-dashes, and "``---``" (dash dash dash) for + en-dashes. + +:-1: Stupefy mode. Reverses the SmartyPants transformation process, turning + the characters produced by SmartyPants into their ASCII equivalents. + E.g. the LEFT DOUBLE QUOTATION MARK (“) is turned into a simple + double-quote (\"), "—" is turned into two dashes, etc. + + +The following single-character attribute values can be combined to toggle +individual transformations from within the smarty_pants attribute. For +example, ``"1"`` is equivalent to ``"qBde"``. + +:q: Educates normal quote characters: (") and ('). + +:b: Educates \`\`backticks'' -style double quotes. + +:B: Educates \`\`backticks'' -style double quotes and \`single' quotes. + +:d: Educates em-dashes. + +:D: Educates em-dashes and en-dashes, using old-school typewriter shorthand: + (dash dash) for en-dashes, (dash dash dash) for em-dashes. + +:i: Educates em-dashes and en-dashes, using inverted old-school typewriter + shorthand: (dash dash) for em-dashes, (dash dash dash) for en-dashes. + +:e: Educates ellipses. + +:w: Translates any instance of ``"`` into a normal double-quote character. + This should be of no interest to most people, but of particular interest + to anyone who writes their posts using Dreamweaver, as Dreamweaver + inexplicably uses this entity to represent a literal double-quote + character. SmartyPants only educates normal quotes, not entities (because + ordinarily, entities are used for the explicit purpose of representing the + specific character they represent). The "w" option must be used in + conjunction with one (or both) of the other quote options ("q" or "b"). + Thus, if you wish to apply all SmartyPants transformations (quotes, en- + and em-dashes, and ellipses) and also translate ``"`` entities into + regular quotes so SmartyPants can educate them, you should pass the + following to the smarty_pants attribute: +""" + + default_smartypants_attr = "1" -import re +import re, sys class smartchars(object): """Smart quotes and dashes @@ -370,75 +386,116 @@ class smartchars(object): endash = u'–' # "–" EN DASH emdash = u'—' # "—" EM DASH ellipsis = u'…' # "…" HORIZONTAL ELLIPSIS + apostrophe = u'’' # "’" RIGHT SINGLE QUOTATION MARK # quote characters (language-specific, set in __init__()) + # [1] http://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks + # [2] http://de.wikipedia.org/wiki/Anf%C3%BChrungszeichen#Andere_Sprachen + # [3] https://fr.wikipedia.org/wiki/Guillemet + # [4] http://typographisme.net/post/Les-espaces-typographiques-et-le-web + # [5] http://www.btb.termiumplus.gc.ca/tpv2guides/guides/redac/index-fra.html + # [6] https://en.wikipedia.org/wiki/Hebrew_punctuation#Quotation_marks + # [7] http://www.tustep.uni-tuebingen.de/bi/bi00/bi001t1-anfuehrung.pdf + # [8] http://www.korrekturavdelingen.no/anforselstegn.htm + # [9] Typografisk håndbok. Oslo: Spartacus. 2000. s. 67. ISBN 8243001530. + # [10] http://www.typografi.org/sitat/sitatart.html # - # English smart quotes (open primary, close primary, open secondary, close - # secondary) are: - # opquote = u'“' # "“" LEFT DOUBLE QUOTATION MARK - # cpquote = u'”' # "”" RIGHT DOUBLE QUOTATION MARK - # osquote = u'‘' # "‘" LEFT SINGLE QUOTATION MARK - # csquote = u'’' # "’" RIGHT SINGLE QUOTATION MARK - # For other languages see: - # http://en.wikipedia.org/wiki/Non-English_usage_of_quotation_marks - # http://de.wikipedia.org/wiki/Anf%C3%BChrungszeichen#Andere_Sprachen + # TODO: configuration option, e.g.:: + # + # smartquote-locales: nl: „“’’, # apostrophe for ``'s Gravenhage`` + # nr: se, # alias + # fr: « : »:‹ : ›, # :-separated list with NBSPs quotes = {'af': u'“”‘’', 'af-x-altquot': u'„”‚’', + 'bg': u'„“‚‘', # Bulgarian, https://bg.wikipedia.org/wiki/Кавички 'ca': u'«»“”', 'ca-x-altquot': u'“”‘’', 'cs': u'„“‚‘', 'cs-x-altquot': u'»«›‹', - 'da': u'»«‘’', + 'da': u'»«›‹', 'da-x-altquot': u'„“‚‘', + # 'da-x-altquot2': u'””’’', 'de': u'„“‚‘', 'de-x-altquot': u'»«›‹', - 'de-CH': u'«»‹›', + 'de-ch': u'«»‹›', 'el': u'«»“”', 'en': u'“”‘’', - 'en-UK': u'‘’“”', + 'en-uk-x-altquot': u'‘’“”', # Attention: " → ‘ and ' → “ ! 'eo': u'“”‘’', 'es': u'«»“”', + 'es-x-altquot': u'“”‘’', 'et': u'„“‚‘', # no secondary quote listed in - 'et-x-altquot': u'»«›‹', # the sources above (wikipedia.org) + 'et-x-altquot': u'«»‹›', # the sources above (wikipedia.org) 'eu': u'«»‹›', - 'es-x-altquot': u'“”‘’', 'fi': u'””’’', - 'fi-x-altquot': u'»»’’', - 'fr': (u'« ', u' »', u'‹ ', u' ›'), # with narrow no-break space - 'fr-x-altquot': u'«»‹›', # for use with manually set spaces - # 'fr-x-altquot': (u'“ ', u' ”', u'‘ ', u' ’'), # rarely used - 'fr-CH': u'«»‹›', + 'fi-x-altquot': u'»»››', + 'fr': (u'« ', u' »', u'“', u'”'), # full no-break space + 'fr-x-altquot': (u'« ', u' »', u'“', u'”'), # narrow no-break space + 'fr-ch': u'«»‹›', + 'fr-ch-x-altquot': (u'« ', u' »', u'‹ ', u' ›'), # narrow no-break space, http://typoguide.ch/ 'gl': u'«»“”', - 'he': u'”“»«', - 'he-x-altquot': u'„”‚’', + 'he': u'”“»«', # Hebrew is RTL, test position: + 'he-x-altquot': u'„”‚’', # low quotation marks are opening. + # 'he-x-altquot': u'“„‘‚', # RTL: low quotation marks opening + 'hr': u'„”‘’', # http://hrvatska-tipografija.com/polunavodnici/ + 'hr-x-altquot': u'»«›‹', + 'hsb': u'„“‚‘', + 'hsb-x-altquot':u'»«›‹', + 'hu': u'„”«»', + 'is': u'„“‚‘', 'it': u'«»“”', - 'it-CH': u'«»‹›', + 'it-ch': u'«»‹›', 'it-x-altquot': u'“”‘’', + # 'it-x-altquot2': u'“„‘‚', # [7] in headlines 'ja': u'「」『』', 'lt': u'„“‚‘', + 'lv': u'„“‚‘', + 'mk': u'„“‚‘', # Macedonian, https://mk.wikipedia.org/wiki/Правопис_и_правоговор_на_македонскиот_јазик 'nl': u'“”‘’', 'nl-x-altquot': u'„”‚’', + # 'nl-x-altquot2': u'””’’', + 'nb': u'«»’’', # Norsk bokmål (canonical form 'no') + 'nn': u'«»’’', # Nynorsk [10] + 'nn-x-altquot': u'«»‘’', # [8], [10] + # 'nn-x-altquot2': u'«»«»', # [9], [10 + # 'nn-x-altquot3': u'„“‚‘', # [10] + 'no': u'«»’’', # Norsk bokmål [10] + 'no-x-altquot': u'«»‘’', # [8], [10] + # 'no-x-altquot2': u'«»«»', # [9], [10 + # 'no-x-altquot3': u'„“‚‘', # [10] 'pl': u'„”«»', - 'pl-x-altquot': u'«»“”', + 'pl-x-altquot': u'«»‚’', + # 'pl-x-altquot2': u'„”‚’', # https://pl.wikipedia.org/wiki/Cudzys%C5%82%C3%B3w 'pt': u'«»“”', - 'pt-BR': u'“”‘’', + 'pt-br': u'“”‘’', 'ro': u'„”«»', - 'ro-x-altquot': u'«»„”', 'ru': u'«»„“', - 'sk': u'„“‚‘', + 'sh': u'„”‚’', # Serbo-Croatian + 'sh-x-altquot': u'»«›‹', + 'sk': u'„“‚‘', # Slovak 'sk-x-altquot': u'»«›‹', - 'sv': u'„“‚‘', - 'sv-x-altquot': u'»«›‹', - 'zh-CN': u'“”‘’', - 'it': u'«»“”', - 'zh-TW': u'「」『』', + 'sl': u'„“‚‘', # Slovenian + 'sl-x-altquot': u'»«›‹', + 'sq': u'«»‹›', # Albanian + 'sq-x-altquot': u'“„‘‚', + 'sr': u'„”’’', + 'sr-x-altquot': u'»«›‹', + 'sv': u'””’’', + 'sv-x-altquot': u'»»››', + 'tr': u'“”‘’', + 'tr-x-altquot': u'«»‹›', + # 'tr-x-altquot2': u'“„‘‚', # [7] antiquated? + 'uk': u'«»„“', + 'uk-x-altquot': u'„“‚‘', + 'zh-cn': u'“”‘’', + 'zh-tw': u'「」『』', } def __init__(self, language='en'): self.language = language try: (self.opquote, self.cpquote, - self.osquote, self.csquote) = self.quotes[language] + self.osquote, self.csquote) = self.quotes[language.lower()] except KeyError: self.opquote, self.cpquote, self.osquote, self.csquote = u'""\'\'' @@ -476,9 +533,8 @@ def educate_tokens(text_tokens, attr=default_smartypants_attr, language='en'): do_ellipses = False do_stupefy = False - if attr == "0": # Do nothing. - yield text - elif attr == "1": # Do everything, turn all options on. + # if attr == "0": # pass tokens unchanged (see below). + if attr == "1": # Do everything, turn all options on. do_quotes = True do_backticks = True do_dashes = 1 @@ -550,7 +606,10 @@ def educate_tokens(text_tokens, attr=default_smartypants_attr, language='en'): text = educateSingleBackticks(text, language) if do_quotes: - text = educateQuotes(prev_token_last_char+text, language)[1:] + # Replace plain quotes in context to prevent converstion to + # 2-character sequence in French. + context = prev_token_last_char.replace('"',';').replace("'",';') + text = educateQuotes(context+text, language)[1:] if do_stupefy: text = stupefyEntities(text, language) @@ -591,7 +650,8 @@ def educateQuotes(text, language='en'): text = re.sub(r"""'"(?=\w)""", smart.osquote+smart.opquote, text) # Special case for decade abbreviations (the '80s): - text = re.sub(r"""\b'(?=\d{2}s)""", smart.csquote, text) + 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"""–|—""" @@ -608,21 +668,31 @@ def educateQuotes(text, language='en'): ) ' # the quote (?=\w) # followed by a word character - """ % (dec_dashes,), re.VERBOSE) + """ % (dec_dashes,), re.VERBOSE | re.UNICODE) text = opening_single_quotes_regex.sub(r'\1'+smart.osquote, text) + # In many locales, single 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 | s\b | \d) - """ % (close_class,), re.VERBOSE) + (?!\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) + """ % (close_class,), re.VERBOSE | re.UNICODE) text = closing_single_quotes_regex.sub(r'\1%s\2' % smart.csquote, text) # Any remaining single quotes should be opening ones: @@ -855,52 +925,98 @@ def tokenize(text): if __name__ == "__main__": - import locale - + import itertools try: - locale.setlocale(locale.LC_ALL, '') + import locale # module missing in Jython + locale.setlocale(locale.LC_ALL, '') # set to user defaults + defaultlanguage = locale.getdefaultlocale()[0] except: - pass - - from docutils.core import publish_string - docstring_html = publish_string(__doc__, writer_name='html') - - print docstring_html - - - # Unit test output goes out stderr. - import unittest - sp = smartyPants - - class TestSmartypantsAllAttributes(unittest.TestCase): - # the default attribute is "1", which means "all". - - def test_dates(self): - self.assertEqual(sp("1440-80's"), u"1440-80’s") - self.assertEqual(sp("1440-'80s"), u"1440-‘80s") - self.assertEqual(sp("1440---'80s"), u"1440–‘80s") - self.assertEqual(sp("1960s"), "1960s") # no effect. - self.assertEqual(sp("1960's"), u"1960’s") - self.assertEqual(sp("one two '60s"), u"one two ‘60s") - self.assertEqual(sp("'60s"), u"‘60s") - - def test_ordinal_numbers(self): - self.assertEqual(sp("21st century"), "21st century") # no effect. - self.assertEqual(sp("3rd"), "3rd") # no effect. + defaultlanguage = 'en' + + # Normalize and drop unsupported subtags: + defaultlanguage = defaultlanguage.lower().replace('-','_') + # split (except singletons, which mark the following tag as non-standard): + defaultlanguage = re.sub(r'_([a-zA-Z0-9])_', r'_\1-', defaultlanguage) + _subtags = [subtag for subtag in defaultlanguage.split('_')] + _basetag = _subtags.pop(0) + # find all combinations of subtags + for n in range(len(_subtags), 0, -1): + for tags in itertools.combinations(_subtags, n): + _tag = '-'.join((_basetag,)+tags) + if _tag in smartchars.quotes: + defaultlanguage = _tag + break + else: + if _basetag in smartchars.quotes: + defaultlanguage = _basetag + else: + defaultlanguage = 'en' + + + import argparse + parser = argparse.ArgumentParser( + description='Filter stdin making ASCII punctuation "smart".') + # parser.add_argument("text", help="text to be acted on") + parser.add_argument("-a", "--action", default="1", + help="what to do with the input (see --actionhelp)") + 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) + parser.add_argument("-q", "--alternative-quotes", action="store_true", + help="use alternative quote style") + parser.add_argument("--doc", action="store_true", + help="print documentation") + parser.add_argument("--actionhelp", action="store_true", + help="list available actions") + parser.add_argument("--stylehelp", action="store_true", + help="list available quote styles") + parser.add_argument("--test", action="store_true", + help="perform short self-test") + args = parser.parse_args() + + if args.doc: + print (__doc__) + elif args.actionhelp: + print options + elif args.stylehelp: + print + print "Available styles (primary open/close, secondary open/close)" + print "language tag quotes" + print "============ ======" + for key in sorted(smartchars.quotes.keys()): + print "%-14s %s" % (key, smartchars.quotes[key]) + elif args.test: + # Unit test output goes to stderr. + import unittest + + class TestSmartypantsAllAttributes(unittest.TestCase): + # the default attribute is "1", which means "all". + def test_dates(self): + self.assertEqual(smartyPants("1440-80's"), u"1440-80’s") + self.assertEqual(smartyPants("1440-'80s"), u"1440-’80s") + self.assertEqual(smartyPants("1440---'80s"), u"1440–’80s") + self.assertEqual(smartyPants("1960's"), u"1960’s") + self.assertEqual(smartyPants("one two '60s"), u"one two ’60s") + self.assertEqual(smartyPants("'60s"), u"’60s") + + def test_educated_quotes(self): + self.assertEqual(smartyPants('"Isn\'t this fun?"'), u'“Isn’t this fun?”') + + def test_html_tags(self): + text = '<a src="foo">more</a>' + self.assertEqual(smartyPants(text), text) + + suite = unittest.TestLoader().loadTestsFromTestCase( + TestSmartypantsAllAttributes) + unittest.TextTestRunner().run(suite) - def test_educated_quotes(self): - self.assertEqual(sp('''"Isn't this fun?"'''), u'“Isn’t this fun?”') - - def test_html_tags(self): - text = '<a src="foo">more</a>' - self.assertEqual(sp(text), text) - - unittest.main() - - - - -__author__ = "Chad Miller <smartypantspy@chad.org>" -__version__ = "1.5_1.6: Fri, 27 Jul 2007 07:06:40 -0400" -__url__ = "http://wiki.chad.org/SmartyPantsPy" -__description__ = "Smart-quotes, smart-ellipses, and smart-dashes for weblog entries in pyblosxom" + else: + if args.alternative_quotes: + if '-x-altquot' in args.language: + args.language = args.language.replace('-x-altquot', '') + else: + args.language += '-x-altquot' + text = sys.stdin.read().decode(args.encoding) + print smartyPants(text, attr=args.action, + language=args.language).encode(args.encoding) 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 a03231c..f92ddc1 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/_html_base.py +++ b/docutils/src/main/resources/docutils/docutils/writers/_html_base.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -# -*- coding: utf8 -*- +# -*- coding: utf-8 -*- # :Author: David Goodger, Günter Milde # Based on the html4css1 writer by David Goodger. # :Maintainer: docutils-develop@lists.sourceforge.net -# :Revision: $Revision: 7977 $ +# :Revision: $Revision: 8118 $ # :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: @@ -15,14 +15,13 @@ # # .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause - -# _html_base.py: common definitions for Docutils HTML writers -# ============================================================ +"""common definitions for Docutils HTML writers""" import sys import os.path import re import urllib + try: # check for the Python Imaging Library import PIL.Image except ImportError: @@ -32,6 +31,7 @@ except ImportError: PIL.Image = Image except ImportError: PIL = None + import docutils from docutils import nodes, utils, writers, languages, io from docutils.utils.error_reporting import SafeString @@ -54,7 +54,7 @@ class Writer(writers.Writer): settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'} # config_section = ... # set in subclass! - config_section_dependencies = ('writers',) + config_section_dependencies = ['writers', 'html writers'] visitor_attributes = ( 'head_prefix', 'head', 'stylesheet', 'body_prefix', @@ -95,13 +95,63 @@ class Writer(writers.Writer): self.parts[part] = ''.join(getattr(self, part)) - class HTMLTranslator(nodes.NodeVisitor): - """Generic Docutils to HTML translator. + """ + Generic Docutils to HTML translator. + + See the `html4css1` and `html5_polyglot` writers for full featured + HTML writers. + + .. IMPORTANT:: + The `visit_*` and `depart_*` methods use a + heterogeneous stack, `self.context`. + When subclassing, make sure to be consistent in its use! + + Examples for robust coding: + + a) Override both `visit_*` and `depart_*` methods, don't call the + parent functions. + + b) Extend both and unconditionally call the parent functions:: + + def visit_example(self, node): + if foo: + self.body.append('<div class="foo">') + html4css1.HTMLTranslator.visit_example(self, node) + + def depart_example(self, node): + html4css1.HTMLTranslator.depart_example(self, node) + if foo: + self.body.append('</div>') + + c) Extend both, calling the parent functions under the same + conditions:: + + def visit_example(self, node): + if foo: + self.body.append('<div class="foo">\n') + else: # call the parent method + _html_base.HTMLTranslator.visit_example(self, node) - See the html4css1 and html5_polyglott for writers for full featured HTML - writers. """ + def depart_example(self, node): + if foo: + self.body.append('</div>\n') + else: # call the parent method + _html_base.HTMLTranslator.depart_example(self, node) + + d) Extend one method (call the parent), but don't otherwise use the + `self.context` stack:: + + def depart_example(self, node): + _html_base.HTMLTranslator.depart_example(self, node) + if foo: + # implementation-specific code + # that does not use `self.context` + self.body.append('</div>\n') + + This way, changes in stack use will not bite you. + """ xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n' doctype = '<!DOCTYPE html>\n' @@ -115,17 +165,27 @@ class HTMLTranslator(nodes.NodeVisitor): # Template for the MathJax script in the header: mathjax_script = '<script type="text/javascript" src="%s"></script>\n' - # The latest version of MathJax from the distributed server: - # avaliable to the public under the `MathJax CDN Terms of Service`__ - # __http://www.mathjax.org/download/mathjax-cdn-terms-of-service/ - # may be overwritten by custom URL appended to "mathjax" - mathjax_url = ('https://cdn.mathjax.org/mathjax/latest/MathJax.js?' - 'config=TeX-AMS_CHTML') + + mathjax_url = 'file:/usr/share/javascript/mathjax/MathJax.js' + """ + URL of the MathJax javascript library. + + The MathJax library ought to be installed on the same + server as the rest of the deployed site files and specified + in the `math-output` setting appended to "mathjax". + See `Docutils Configuration`__. + + __ http://docutils.sourceforge.net/docs/user/config.html#math-output + + The fallback tries a local MathJax installation at + ``/usr/share/javascript/mathjax/MathJax.js``. + """ stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n' embedded_stylesheet = '<style type="text/css">\n\n%s\n</style>\n' words_and_spaces = re.compile(r'\S+| +|\n') - sollbruchstelle = re.compile(r'.+\W\W.+|[-?].+', re.U) # wrap point inside word + # 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 special_characters = {ord('&'): u'&', @@ -136,6 +196,7 @@ class HTMLTranslator(nodes.NodeVisitor): } """Character references for characters with a special meaning in HTML.""" + def __init__(self, document): nodes.NodeVisitor.__init__(self, document) self.settings = settings = document.settings @@ -168,9 +229,11 @@ class HTMLTranslator(nodes.NodeVisitor): self.math_output_options = self.math_output[1:] self.math_output = self.math_output[0].lower() - # A heterogenous stack used in conjunction with the tree traversal. - # Make sure that the pops correspond to the pushes: self.context = [] + """Heterogeneous stack. + + Used by visit_* and depart_* functions in conjunction with the tree + traversal. Make sure that the pops correspond to the pushes.""" self.topic_classes = [] # TODO: replace with self_in_contents self.colspecs = [] @@ -289,9 +352,10 @@ class HTMLTranslator(nodes.NodeVisitor): # elements aren't allowed in XHTML (even if they do # not all have a "href" attribute). if empty or isinstance(node, - (nodes.bullet_list, nodes.enumerated_list, - nodes.definition_list, nodes.field_list, - nodes.option_list, nodes.docinfo)): + (nodes.bullet_list, nodes.docinfo, + nodes.definition_list, nodes.enumerated_list, + nodes.field_list, nodes.option_list, + nodes.table)): # Insert target right in front of element. prefix.append('<span id="%s"></span>' % id) else: @@ -360,7 +424,7 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_address(self, node): self.visit_docinfo_item(node, 'address', meta=False) - self.body.append(self.starttag(node, 'pre', + self.body.append(self.starttag(node, 'pre', suffix= '', CLASS='address')) def depart_address(self, node): @@ -645,8 +709,9 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('\n</pre>\n') def visit_document(self, node): - self.head.append('<title>%s</title>\n' - % self.encode(node.get('title', ''))) + title = (node.get('title', '') or os.path.basename(node['source']) + or 'docutils document without title') + self.head.append('<title>%s</title>\n' % self.encode(title)) def depart_document(self, node): self.head_prefix.extend([self.doctype, @@ -983,7 +1048,7 @@ class HTMLTranslator(nodes.NodeVisitor): # 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.sollbruchstelle.search(token): + if token.strip() and self.in_word_wrap_point.search(token): self.body.append('<span class="pre">%s</span>' % self.encode(token)) else: @@ -1032,7 +1097,7 @@ class HTMLTranslator(nodes.NodeVisitor): wrappers = {# math_mode: (inline, block) 'mathml': ('$%s$', u'\\begin{%s}\n%s\n\\end{%s}'), 'html': ('$%s$', u'\\begin{%s}\n%s\n\\end{%s}'), - 'mathjax': ('\(%s\)', u'\\begin{%s}\n%s\n\\end{%s}'), + 'mathjax': (r'\(%s\)', u'\\begin{%s}\n%s\n\\end{%s}'), 'latex': (None, None), } wrapper = wrappers[self.math_output][math_env != ''] @@ -1050,8 +1115,15 @@ class HTMLTranslator(nodes.NodeVisitor): if self.math_output in ('latex', 'mathjax'): math_code = self.encode(math_code) if self.math_output == 'mathjax' and not self.math_header: - if self.math_output_options: + try: self.mathjax_url = self.math_output_options[0] + except IndexError: + self.document.reporter.warning('No MathJax URL specified, ' + 'using local fallback (see config.html)') + # append configuration, if not already present in the URL: + # input LaTeX with AMS, output common HTML + if '?' not in self.mathjax_url: + self.mathjax_url += '?config=TeX-AMS_CHTML' self.math_header = [self.mathjax_script % self.mathjax_url] elif self.math_output == 'html': if self.math_output_options and not self.math_header: 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 7853496..567eec8 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 7977 2016-11-29 12:00:39Z milde $ +# $Id: __init__.py 8035 2017-02-13 22:01:47Z milde $ # Author: David Goodger # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -247,6 +247,29 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): def depart_authors(self, node): self.depart_docinfo_item() + # use "width" argument insted of "style: 'width'": + def visit_colspec(self, node): + self.colspecs.append(node) + # "stubs" list is an attribute of the tgroup element: + node.parent.stubs.append(node.attributes.get('stub')) + # + def depart_colspec(self, node): + # write out <colgroup> when all colspecs are processed + if isinstance(node.next_node(descend=False, siblings=True), + nodes.colspec): + return + if 'colwidths-auto' in node.parent.parent['classes'] or ( + 'colwidths-auto' in self.settings.table_style and + ('colwidths-given' not in node.parent.parent['classes'])): + return + total_width = sum(node['colwidth'] for node in self.colspecs) + self.body.append(self.starttag(node, 'colgroup')) + for node in self.colspecs: + colwidth = int(node['colwidth'] * 100.0 / total_width + 0.5) + self.body.append(self.emptytag(node, 'col', + width='%i%%' % colwidth)) + self.body.append('</colgroup>\n') + # Compact lists: # exclude definition lists and field lists (non-compact by default) @@ -278,20 +301,6 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): self.body.append(' <span class="classifier-delimiter">:</span> ') self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) - # rewritten in _html_base (support for "auto" width) - def depart_colspec(self, node): - pass - - def write_colspecs(self): - width = 0 - for node in self.colspecs: - width += node['colwidth'] - for node in self.colspecs: - colwidth = int(node['colwidth'] * 100.0 / width + 0.5) - self.body.append(self.emptytag(node, 'col', - width='%i%%' % colwidth)) - self.colspecs = [] - # ersatz for first/last pseudo-classes def visit_definition(self, node): self.body.append('</dt>\n') @@ -556,7 +565,7 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): if token.strip(): # Protect text like "--an-option" and the regular expression # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping - if self.sollbruchstelle.search(token): + if self.in_word_wrap_point.search(token): self.body.append('<span class="pre">%s</span>' % self.encode(token)) else: @@ -761,24 +770,17 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): # hard-coded vertical alignment def visit_tbody(self, node): - self.write_colspecs() - self.body.append(self.context.pop()) # '</colgroup>\n' or '' self.body.append(self.starttag(node, 'tbody', valign='top')) + # + def depart_tbody(self, node): + self.body.append('</tbody>\n') - # rewritten in _html_base - def visit_tgroup(self, node): - self.body.append(self.starttag(node, 'colgroup')) - # Appended by thead or tbody: - self.context.append('</colgroup>\n') - node.stubs = [] - - # rewritten in _html_base + # hard-coded vertical alignment def visit_thead(self, node): - self.write_colspecs() - self.body.append(self.context.pop()) # '</colgroup>\n' - # There may or may not be a <thead>; this is for <tbody> to use: - self.context.append('') self.body.append(self.starttag(node, 'thead', valign='bottom')) + # + def depart_thead(self, node): + self.body.append('</thead>\n') class SimpleListChecker(writers._html_base.SimpleListChecker): 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 c14c5ee..9835447 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,9 +1,8 @@ # .. coding: utf8 +# $Id: __init__.py 8041 2017-03-01 11:02:33Z milde $ # :Author: Günter Milde <milde@users.sf.net> # Based on the html4css1 writer by David Goodger. # :Maintainer: docutils-develop@lists.sourceforge.net -# :Revision: $Revision: 7977 $ -# :Date: $Date: 2005-06-28$ # :Copyright: © 2005, 2009, 2015 Günter Milde, # portions from html4css1 © David Goodger. # :License: Released under the terms of the `2-Clause BSD license`_, in short: @@ -21,7 +20,7 @@ """ Plain HyperText Markup Language document tree Writer. -The output conforms to the `HTML 5` specification. +The output conforms to the `HTML5` specification. The cascading style sheet "minimal.css" is required for proper viewing, the style sheet "plain.css" improves reading experience. @@ -139,7 +138,7 @@ class Writer(writers._html_base.Writer): ['--cloak-email-addresses'], {'action': 'store_true', 'validator': frontend.validate_boolean}),)) - config_section = 'html-plain writer' + config_section = 'html5 writer' def __init__(self): self.parts = {} @@ -148,37 +147,46 @@ class Writer(writers._html_base.Writer): class HTMLTranslator(writers._html_base.HTMLTranslator): """ - This writer generates `polyglot markup`: HTML 5 that is also valid XML. - """ - # def __init__(self, document): - # writers._html_base.HTMLTranslator.__init__(self, document) + This writer generates `polyglot markup`: HTML5 that is also valid XML. + Safe subclassing: when overriding, treat ``visit_*`` and ``depart_*`` + methods as a unit to prevent breaks due to internal changes. See the + docstring of docutils.writers._html_base.HTMLTranslator for details + and examples. + """ # <acronym> tag not supported 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 HTML 5 + # no meta tag in HTML5 def visit_authors(self, node): self.visit_docinfo_item(node, 'authors', meta=False) + def depart_authors(self, node): + self.depart_docinfo_item() + # no meta tag in HTML5 def visit_copyright(self, node): self.visit_docinfo_item(node, 'copyright', meta=False) + def depart_copyright(self, node): + self.depart_docinfo_item() - # no meta tag in HTML 5 + # no meta tag in HTML5 def visit_date(self, node): self.visit_docinfo_item(node, 'date', meta=False) + def depart_date(self, node): + self.depart_docinfo_item() - # TODO: use HTML 5 <footer> element? + # TODO: use HTML5 <footer> element? # def visit_footer(self, node): # def depart_footer(self, node): # TODO: use the new HTML5 element <aside>? (Also for footnote text) # def visit_footnote(self, node): + # def depart_footnote(self, node): # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 # HTML5/polyglot recommends using both @@ -188,13 +196,19 @@ class HTMLTranslator(writers._html_base.HTMLTranslator): # 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 HTML 5 + # no meta tag 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 HTML 5 element <section>? + # TODO: use the new HTML5 element <section>? # def visit_section(self, node): + # def depart_section(self, node): # TODO: use the new HTML5 element <aside>? # def visit_topic(self, node): + # def depart_topic(self, node): 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 5eb5b3d..180213b 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,7 +1,7 @@ /* Minimal style sheet for the HTML output of Docutils. */ /* */ /* :Author: Günter Milde, based on html4css1.css by David Goodger */ -/* :Id: $Id: minimal.css 7952 2016-07-26 18:15:59Z milde $ */ +/* :Id: $Id: minimal.css 8036 2017-02-14 13:05:46Z milde $ */ /* :Copyright: © 2015 Günter Milde. */ /* :License: Released under the terms of the `2-Clause BSD license`_, */ /* in short: */ @@ -204,7 +204,7 @@ table.align-right { margin-left: auto; } /* reset inner alignment in figures and tables */ -div.align-left, div.align-center, div.align-right, +/* div.align-left, div.align-center, div.align-right, */ table.align-left, table.align-center, table.align-right { text-align: inherit } 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 aeccf7a..a2d27ba 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 @@ -2,7 +2,7 @@ /* Rules for easy reading and pre-defined style variants. */ /* */ /* :Author: Günter Milde, based on html4css1.css by David Goodger */ -/* :Id: $Id: plain.css 7952 2016-07-26 18:15:59Z milde $ */ +/* :Id: $Id: plain.css 8120 2017-06-22 21:02:40Z milde $ */ /* :Copyright: © 2015 Günter Milde. */ /* :License: Released under the terms of the `2-Clause BSD license`_, */ /* in short: */ @@ -66,9 +66,9 @@ dl > dd { /* Definition Lists */ -dl > dd p:first-child { margin-top: 0; } +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; } */ +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) */ 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 112bc76..b9720d2 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py @@ -1,5 +1,5 @@ # .. coding: utf-8 -# $Id: __init__.py 7971 2016-09-13 19:11:48Z milde $ +# $Id: __init__.py 8058 2017-04-19 16:45:32Z milde $ # Author: Engelbert Gruber, Günter Milde # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -476,7 +476,7 @@ class PreambleCmds(object): PreambleCmds.abstract = r""" % abstract title -\providecommand*{\DUtitleabstract}[1]{\centering\textbf{#1}}""" +\providecommand*{\DUtitleabstract}[1]{\centerline{\textbf{#1}}}""" PreambleCmds.admonition = r""" % admonition (specially marked topic) @@ -491,12 +491,6 @@ PreambleCmds.admonition = r""" \fi }""" -PreambleCmds.align_center = r""" -\makeatletter -\@namedef{DUrolealign-center}{\centering} -\makeatother -""" - ## PreambleCmds.caption = r"""% configure caption layout ## \usepackage{caption} ## \captionsetup{singlelinecheck=false}% no exceptions for one-liners""" @@ -510,12 +504,24 @@ PreambleCmds.docinfo = r""" PreambleCmds.dedication = r""" % dedication topic -\providecommand{\DUtopicdedication}[1]{\begin{center}#1\end{center}}""" +\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.errortitle._depends = 'color' PreambleCmds.fieldlist = r""" % fieldlist environment @@ -559,14 +565,11 @@ PreambleCmds.inline = r""" % inline markup (custom roles) % \DUrole{#1}{#2} tries \DUrole#1{#2} \providecommand*{\DUrole}[2]{% - \ifcsname DUrole#1\endcsname% + % backwards compatibility: try \docutilsrole#1{#2} + \ifcsname docutilsrole#1\endcsname% + \csname docutilsrole#1\endcsname{#2}% + \else \csname DUrole#1\endcsname{#2}% - \else% backwards compatibility: try \docutilsrole#1{#2} - \ifcsname docutilsrole#1\endcsname% - \csname docutilsrole#1\endcsname{#2}% - \else% - #2% - \fi% \fi% }""" @@ -629,20 +632,20 @@ PreambleCmds.providelength = r""" PreambleCmds.rubric = r""" % rubric (informal heading) -\providecommand*{\DUrubric}[2][class-arg]{% - \subsubsection*{\centering\textit{\textmd{#2}}}}""" +\providecommand*{\DUrubric}[1]{% + \subsubsection*{\centering\textit{\textmd{#1}}}}""" PreambleCmds.sidebar = r""" % sidebar (text outside the main text flow) -\providecommand{\DUsidebar}[2][class-arg]{% +\providecommand{\DUsidebar}[1]{% \begin{center} - \colorbox[gray]{0.80}{\parbox{0.9\linewidth}{#2}} + \colorbox[gray]{0.80}{\parbox{0.9\linewidth}{#1}} \end{center} }""" PreambleCmds.subtitle = r""" % subtitle (for topic/sidebar) -\providecommand*{\DUsubtitle}[2][class-arg]{\par\emph{#2}\smallskip}""" +\providecommand*{\DUsubtitle}[1]{\par\emph{#1}\smallskip}""" PreambleCmds.documentsubtitle = r""" % subtitle (in document title) @@ -657,6 +660,12 @@ PreambleCmds.table = r"""\usepackage{longtable,ltcaption,array} PreambleCmds.textcomp = """\ \\usepackage{textcomp} % text symbol macros""" +PreambleCmds.textsubscript = r""" +% text mode subscript +\ifx\textsubscript\undefined + \usepackage{fixltx2e} % since 2015 loaded by default +\fi""" + PreambleCmds.titlereference = r""" % titlereference role \providecommand*{\DUroletitlereference}[1]{\textsl{#1}}""" @@ -672,19 +681,9 @@ PreambleCmds.title = r""" \fi }""" -PreambleCmds.topic = r""" -% topic (quote with heading) -\providecommand{\DUtopic}[2][class-arg]{% - \ifcsname DUtopic#1\endcsname% - \csname DUtopic#1\endcsname{#2}% - \else - \begin{quote}#2\end{quote} - \fi -}""" - PreambleCmds.transition = r""" % transition (break, fancybreak, anonymous section) -\providecommand*{\DUtransition}[1][class-arg]{% +\providecommand*{\DUtransition}{% \hspace*{\fill}\hrulefill\hspace*{\fill} \vskip 0.5\baselineskip }""" @@ -728,19 +727,31 @@ class CharMaps(object): } # Unicode chars that are not recognized by LaTeX's utf8 encoding unsupported_unicode = { - 0x00A0: ur'~', # NO-BREAK SPACE # 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 - 0x2011: ur'\hbox{-}', # NON-BREAKING HYPHEN + 0x200b: ur'\hspace{0pt}', # ZERO WIDTH SPACE 0x202F: ur'\,', # NARROW NO-BREAK SPACE - 0x21d4: ur'$\Leftrightarrow$', + # 0x02d8: ur'\\u{ }', # BREVE + 0x2011: ur'\hbox{-}', # NON-BREAKING HYPHEN + 0x212b: ur'\AA', # ANGSTROM SIGN + 0x21d4: ur'\ensuremath{\Leftrightarrow}', # Docutils footnote symbols: - 0x2660: ur'$\spadesuit$', - 0x2663: ur'$\clubsuit$', + 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 } # 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 @@ -1123,13 +1134,19 @@ class Table(object): class LaTeXTranslator(nodes.NodeVisitor): + """ + Generate code for 8-bit LaTeX from a Docutils document tree. + + See the docstring of docutils.writers._html_base.HTMLTranslator for + notes on and examples of safe subclassing. + """ # When options are given to the documentclass, latex will pass them # to other packages, as done with babel. # Dummy settings might be taken from document settings - # Write code for typesetting with 8-bit tex/pdftex (vs. xetex/luatex) engine - # overwritten by the XeTeX writer + # Generate code for typesetting with 8-bit latex/pdflatex vs. + # xelatex/lualatex engine. Overwritten by the XeTeX writer is_xetex = False # Config setting defaults @@ -1194,7 +1211,7 @@ class LaTeXTranslator(nodes.NodeVisitor): (none, self.literal_block_env, self.literal_block_options, - none ) = re.split('(\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' # @@ -1252,9 +1269,11 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body = [] ## self.body_suffix = ['\\end{document}\n'] - # A heterogenous stack used in conjunction with the tree traversal. - # Make sure that the pops correspond to the pushes: self.context = [] + """Heterogeneous stack. + + Used by visit_* and depart_* functions in conjunction with the tree + traversal. Make sure that the pops correspond to the pushes.""" # Title metadata: self.title_labels = [] @@ -1462,7 +1481,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def encode(self, text): """Return text with 'problematic' characters escaped. - * Escape the special printing characters ``# $ % & ~ _ ^ \ { }``, + * Escape the special printing characters ``# $ % & ~ _ ^ \\ { }``, square brackets ``[ ]``, double quotes and (in OT1) ``< | >``. * Translate non-supported Unicode characters. * Separate ``-`` (and more in literal text) to prevent input ligatures. @@ -1496,17 +1515,24 @@ class LaTeXTranslator(nodes.NodeVisitor): table[ord(' ')] = ur'~' # Unicode replacements for 8-bit tex engines (not required with XeTeX/LuaTeX): if not self.is_xetex: - table.update(CharMaps.unsupported_unicode) if not self.latex_encoding.startswith('utf8'): + table.update(CharMaps.unsupported_unicode) table.update(CharMaps.utf8_supported_unicode) table.update(CharMaps.textcomp) table.update(CharMaps.pifont) # Characters that require a feature/package to render - if [True for ch in text if ord(ch) in CharMaps.textcomp]: - self.requirements['textcomp'] = PreambleCmds.textcomp - if [True for ch in text if ord(ch) in CharMaps.pifont]: + for ch in text: + cp = ord(ch) + if cp in CharMaps.textcomp: + self.requirements['textcomp'] = PreambleCmds.textcomp + elif cp in CharMaps.pifont: self.requirements['pifont'] = '\\usepackage{pifont}' - + # preamble-definitions for unsupported Unicode characters + elif (self.latex_encoding == 'utf8' + and cp in CharMaps.unsupported_unicode): + self.requirements['_inputenc'+str(cp)] = ( + '\\DeclareUnicodeCharacter{%04X}{%s}' + % (cp, CharMaps.unsupported_unicode[cp])) text = text.translate(table) # Break up input ligatures e.g. '--' to '-{}-'. @@ -1561,13 +1587,57 @@ class LaTeXTranslator(nodes.NodeVisitor): def ids_to_labels(self, node, set_anchor=True): """Return list of label definitions for all ids of `node` - If `set_anchor` is True, an anchor is set with \phantomsection. + If `set_anchor` is True, an anchor is set with \\phantomsection. """ labels = ['\\label{%s}' % id for id in node.get('ids', [])] if set_anchor and labels: labels.insert(0, '\\phantomsection') return labels + def set_align_from_classes(self, node): + """Convert ``align-*`` class arguments into alignment args.""" + # separate: + align = [cls for cls in node['classes'] if cls.startswith('align-')] + if align: + node['align'] = align[-1].replace('align-', '') + node['classes'] = [cls for cls in node['classes'] + if not cls.startswith('align-')] + + def insert_align_declaration(self, node, default=None): + align = node.get('align', default) + if align == 'left': + self.out.append('\\raggedright\n') + elif align == 'center': + self.out.append('\\centering\n') + elif align == 'right': + self.out.append('\\raggedleft\n') + + def duclass_open(self, node): + """Open a group and insert declarations for class values.""" + if not isinstance(node.parent, nodes.compound): + self.out.append('\n') + for cls in node['classes']: + if cls.startswith('language-'): + language = self.babel.language_name(cls[9:]) + if language: + self.babel.otherlanguages[language] = True + self.out.append('\\begin{selectlanguage}{%s}\n' % language) + else: + self.fallbacks['DUclass'] = PreambleCmds.duclass + self.out.append('\\begin{DUclass}{%s}\n' % cls) + + def duclass_close(self, node): + """Close a group of class declarations.""" + for cls in reversed(node['classes']): + if cls.startswith('language-'): + language = self.babel.language_name(cls[9:]) + if language: + self.babel.otherlanguages[language] = True + self.out.append('\\end{selectlanguage}\n') + else: + self.fallbacks['DUclass'] = PreambleCmds.duclass + self.out.append('\\end{DUclass}\n') + def push_output_collector(self, new_out): self.out_stack.append(self.out) self.out = new_out @@ -1611,7 +1681,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # 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]{\n' % ','.join(node['classes'])) + self.out.append('\n\\DUadmonition[%s]{' % ','.join(node['classes'])) def depart_admonition(self, node=None): self.out.append('}\n') @@ -1630,30 +1700,26 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def visit_block_quote(self, node): - self.out.append( '%\n\\begin{quote}\n') - if node['classes']: - self.visit_inline(node) + self.duclass_open(node) + self.out.append( '\\begin{quote}') def depart_block_quote(self, node): - if node['classes']: - self.depart_inline(node) - self.out.append( '\n\\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( '%\n\\begin{list}{}{}\n' ) + self.out.append( '\\begin{list}{}{}' ) else: - self.out.append( '%\n\\begin{itemize}\n' ) - # if node['classes']: - # self.visit_inline(node) + self.out.append( '\\begin{itemize}' ) def depart_bullet_list(self, node): - # if node['classes']: - # self.depart_inline(node) if self.is_toc_list: - self.out.append( '\n\\end{list}\n' ) + self.out.append( '\\end{list}\n' ) else: - self.out.append( '\n\\end{itemize}\n' ) + self.out.append( '\\end{itemize}\n' ) + self.duclass_close(node) def visit_superscript(self, node): self.out.append(r'\textsuperscript{') @@ -1666,7 +1732,8 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('}') def visit_subscript(self, node): - self.out.append(r'\textsubscript{') # requires `fixltx2e` + self.fallbacks['textsubscript'] = PreambleCmds.textsubscript + self.out.append(r'\textsubscript{') if node['classes']: self.visit_inline(node) @@ -1752,7 +1819,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append( '(\\textbf{' ) def depart_classifier(self, node): - self.out.append( '})\n' ) + self.out.append( '})' ) def visit_colspec(self, node): self.active_table.visit_colspec(node) @@ -1761,18 +1828,23 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def visit_comment(self, node): + if not isinstance(node.parent, nodes.compound): + self.out.append('\n') # Precede every line with a comment sign, wrap in newlines - self.out.append('\n%% %s\n' % node.astext().replace('\n', '\n% ')) + self.out.append('%% %s\n' % node.astext().replace('\n', '\n% ')) raise nodes.SkipNode def depart_comment(self, node): pass def visit_compound(self, node): - pass + if isinstance(node.parent, nodes.compound): + self.out.append('\n') + node['classes'].insert(0, 'compound') + self.duclass_open(node) def depart_compound(self, node): - pass + self.duclass_close(node) def visit_contact(self, node): self.visit_docinfo_item(node, 'contact') @@ -1781,10 +1853,10 @@ class LaTeXTranslator(nodes.NodeVisitor): self.depart_docinfo_item(node) def visit_container(self, node): - pass + self.duclass_open(node) def depart_container(self, node): - pass + self.duclass_close(node) def visit_copyright(self, node): self.visit_docinfo_item(node, 'copyright') @@ -1809,13 +1881,15 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def depart_definition(self, node): - self.out.append('\n') + self.out.append('\n') # TODO: just pass? def visit_definition_list(self, node): - self.out.append( '%\n\\begin{description}\n' ) + self.duclass_open(node) + self.out.append( '\\begin{description}\n' ) def depart_definition_list(self, node): self.out.append( '\\end{description}\n' ) + self.duclass_close(node) def visit_definition_list_item(self, node): pass @@ -1919,7 +1993,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.titledata.append('%%% Title Data') # \title (empty \title prevents error with \maketitle) if self.title: - self.title.insert(0, '\phantomsection%\n ') + self.title.insert(0, '\\phantomsection%\n ') title = [''.join(self.title)] + self.title_labels if self.subtitle: title += [r'\\ % subtitle', @@ -2069,7 +2143,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if self._enumeration_counters: prefix += self._enumeration_counters[-1] # TODO: use LaTeX default for unspecified label-type? - # (needs change of parser) + # (needs change of parser) prefix += node.get('prefix', '') enumtype = types[node.get('enumtype' '')] suffix = node.get('suffix', '') @@ -2079,23 +2153,21 @@ class LaTeXTranslator(nodes.NodeVisitor): label = r'%s\%s{%s}%s' % (prefix, enumtype, counter_name, suffix) self._enumeration_counters.append(label) + self.duclass_open(node) if enumeration_level <= 4: - self.out.append('\\begin{enumerate}\n') + self.out.append('\\begin{enumerate}') if (prefix, enumtype, suffix ) != labels[enumeration_level-1]: - self.out.append('\\renewcommand{\\label%s}{%s}\n' % + self.out.append('\n\\renewcommand{\\label%s}{%s}' % (counter_name, label)) else: self.fallbacks[counter_name] = '\\newcounter{%s}' % counter_name self.out.append('\\begin{list}') self.out.append('{%s}' % label) - self.out.append('{\\usecounter{%s}}\n' % counter_name) + self.out.append('{\\usecounter{%s}}' % counter_name) if 'start' in node: - self.out.append('\\setcounter{%s}{%d}\n' % + self.out.append('\n\\setcounter{%s}{%d}' % (counter_name,node['start']-1)) - # ## set rightmargin equal to leftmargin - # self.out.append('\\setlength{\\rightmargin}{\\leftmargin}\n') - def depart_enumerated_list(self, node): @@ -2103,6 +2175,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\\end{enumerate}\n') else: self.out.append('\\end{list}\n') + self.duclass_close(node) self._enumeration_counters.pop() def visit_field(self, node): @@ -2110,7 +2183,7 @@ class LaTeXTranslator(nodes.NodeVisitor): pass def depart_field(self, node): - self.out.append('\n') + pass ##self.out.append('%[depart_field]\n') def visit_field_argument(self, node): @@ -2124,16 +2197,18 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_field_body(self, node): if self.out is self.docinfo: - self.out.append(r'\\') + self.out.append(r'\\'+'\n') def visit_field_list(self, node): + self.duclass_open(node) if self.out is not self.docinfo: self.fallbacks['fieldlist'] = PreambleCmds.fieldlist - self.out.append('%\n\\begin{DUfieldlist}\n') + self.out.append('\\begin{DUfieldlist}') def depart_field_list(self, node): if self.out is not self.docinfo: self.out.append('\\end{DUfieldlist}\n') + self.duclass_close(node) def visit_field_name(self, node): if self.out is self.docinfo: @@ -2141,7 +2216,7 @@ class LaTeXTranslator(nodes.NodeVisitor): else: # Commands with optional args inside an optional arg must be put # in a group, e.g. ``\item[{\hyperref[label]{text}}]``. - self.out.append('\\item[{') + self.out.append('\n\\item[{') def depart_field_name(self, node): if self.out is self.docinfo: @@ -2151,6 +2226,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_figure(self, node): self.requirements['float_settings'] = PreambleCmds.float_settings + self.duclass_open(node) # The 'align' attribute sets the "outer alignment", # for "inner alignment" use LaTeX default alignment (similar to HTML) alignment = node.attributes.get('align', 'center') @@ -2158,14 +2234,15 @@ class LaTeXTranslator(nodes.NodeVisitor): # The LaTeX "figure" environment always uses the full linewidth, # so "outer alignment" is ignored. Just write a comment. # TODO: use the wrapfigure environment? - self.out.append('\n\\begin{figure} %% align = "%s"\n' % alignment) + self.out.append('\\begin{figure} %% align = "%s"\n' % alignment) else: - self.out.append('\n\\begin{figure}\n') + self.out.append('\\begin{figure}\n') if node.get('ids'): self.out += self.ids_to_labels(node) + ['\n'] def depart_figure(self, node): self.out.append('\\end{figure}\n') + self.duclass_close(node) def visit_footer(self, node): self.push_output_collector([]) @@ -2266,7 +2343,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if pxunit is not None: sys.stderr.write('deprecation warning: LaTeXTranslator.to_latex_length()' ' option `pxunit` will be removed.') - match = re.match('(\d*\.?\d*)\s*(\S*)', length_str) + match = re.match(r'(\d*\.?\d*)\s*(\S*)', length_str) if not match: return length_str value, unit = match.groups()[:2] @@ -2295,10 +2372,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # Set default align of image in a figure to 'center' if isinstance(node.parent, nodes.figure): attrs['align'] = 'center' - # query 'align-*' class argument - for cls in node['classes']: - if cls.startswith('align-'): - attrs['align'] = cls.split('-')[1] + self.set_align_from_classes(node) # pre- and postfix (prefix inserted in reverse order) pre = [] post = [] @@ -2330,8 +2404,10 @@ class LaTeXTranslator(nodes.NodeVisitor): include_graphics_options.append('width=%s' % self.to_latex_length(attrs['width'])) if not (self.is_inline(node) or - isinstance(node.parent, nodes.figure)): + isinstance(node.parent, (nodes.figure, nodes.compound))): pre.append('\n') + if not (self.is_inline(node) or + isinstance(node.parent, nodes.figure)): post.append('\n') pre.reverse() self.out.extend(pre) @@ -2346,10 +2422,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out += self.ids_to_labels(node) + ['\n'] def visit_inline(self, node): # <span>, i.e. custom roles - self.context.append('}' * len(node['classes'])) for cls in node['classes']: - if cls == 'align-center': - self.fallbacks['align-center'] = PreambleCmds.align_center if cls.startswith('language-'): language = self.babel.language_name(cls[9:]) if language: @@ -2360,7 +2433,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append(r'\DUrole{%s}{' % cls) def depart_inline(self, node): - self.out.append(self.context.pop()) + self.out.append('}' * len(node['classes'])) def visit_interpreted(self, node): # @@@ Incomplete, pending a proper implementation on the @@ -2378,7 +2451,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.append('\\end{DUlegend}\n') def visit_line(self, node): - self.out.append('\item[] ') + self.out.append(r'\item[] ') def depart_line(self, node): self.out.append('\n') @@ -2386,20 +2459,19 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_line_block(self, node): self.fallbacks['_providelength'] = PreambleCmds.providelength self.fallbacks['lineblock'] = PreambleCmds.lineblock + self.set_align_from_classes(node) if isinstance(node.parent, nodes.line_block): self.out.append('\\item[]\n' '\\begin{DUlineblock}{\\DUlineblockindent}\n') + # nested line-blocks cannot be given class arguments else: - self.out.append('\n\\begin{DUlineblock}{0em}\n') - if node['classes']: - self.visit_inline(node) - self.out.append('\n') + self.duclass_open(node) + self.out.append('\\begin{DUlineblock}{0em}\n') + self.insert_align_declaration(node) def depart_line_block(self, node): - if node['classes']: - self.depart_inline(node) - self.out.append('\n') self.out.append('\\end{DUlineblock}\n') + self.duclass_close(node) def visit_list_item(self, node): self.out.append('\n\\item ') @@ -2455,15 +2527,15 @@ class LaTeXTranslator(nodes.NodeVisitor): if node.get('ids'): self.out += ['\n'] + self.ids_to_labels(node) + 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.append('%\n\\begin{quote}\n') + self.out.append('\\begin{quote}\n') self.context.append('\n\\end{quote}\n') else: - self.out.append('\n') self.context.append('\n') if self.is_plaintext(node): @@ -2495,6 +2567,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.alltt = False self.out.append(self.context.pop()) self.out.append(self.context.pop()) + self.duclass_close(node) ## def visit_meta(self, node): ## self.out.append('[visit_meta]\n') @@ -2522,7 +2595,7 @@ class LaTeXTranslator(nodes.NodeVisitor): math_code = '\n'.join([math_code] + self.ids_to_labels(node)) if math_env == '$': if self.alltt: - wrapper = u'\(%s\)' + wrapper = ur'\(%s\)' else: wrapper = u'$%s$' else: @@ -2575,10 +2648,12 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_option_list(self, node): self.fallbacks['_providelength'] = PreambleCmds.providelength self.fallbacks['optionlist'] = PreambleCmds.optionlist - self.out.append('%\n\\begin{DUoptionlist}\n') + self.duclass_open(node) + self.out.append('\\begin{DUoptionlist}') def depart_option_list(self, node): - self.out.append('\n\\end{DUoptionlist}\n') + self.out.append('\\end{DUoptionlist}\n') + self.duclass_close(node) def visit_option_list_item(self, node): pass @@ -2602,12 +2677,12 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_paragraph(self, node): # insert blank line, unless - # * the paragraph is first in a list item, + # * the paragraph is first in a list item or compound, # * 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) or - isinstance(node.parent, nodes.description))): + if index == 0 and isinstance(node.parent, + (nodes.list_item, nodes.description, nodes.compound)): pass elif (index > 0 and isinstance(node.parent, nodes.compound) and not isinstance(node.parent[index - 1], nodes.paragraph) and @@ -2715,6 +2790,15 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_revision(self, node): self.depart_docinfo_item(node) + def visit_rubric(self, node): + self.fallbacks['rubric'] = PreambleCmds.rubric + self.duclass_open(node) + self.out.append('\\DUrubric{') + + def depart_rubric(self, node): + self.out.append('}\n') + self.duclass_close(node) + def visit_section(self, node): self.section_level += 1 # Initialize counter for potential subsections: @@ -2728,12 +2812,14 @@ class LaTeXTranslator(nodes.NodeVisitor): self.section_level -= 1 def visit_sidebar(self, node): + self.duclass_open(node) self.requirements['color'] = PreambleCmds.color self.fallbacks['sidebar'] = PreambleCmds.sidebar - self.out.append('\n\\DUsidebar{\n') + self.out.append('\\DUsidebar{') def depart_sidebar(self, node): self.out.append('}\n') + self.duclass_close(node) attribution_formats = {'dash': (u'—', ''), # EM DASH 'parentheses': ('(', ')'), @@ -2795,13 +2881,13 @@ class LaTeXTranslator(nodes.NodeVisitor): self.fallbacks['title'] = PreambleCmds.title node['classes'] = ['system-message'] self.visit_admonition(node) - self.out.append('\\DUtitle[system-message]{system-message}\n') + self.out.append('\n\\DUtitle[system-message]{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: @@ -2934,7 +3020,7 @@ class LaTeXTranslator(nodes.NodeVisitor): classes = ','.join(node.parent['classes']) if not classes: classes = node.tagname - self.out.append('\\DUtitle[%s]{' % classes) + self.out.append('\n\\DUtitle[%s]{' % classes) self.context.append('}\n') # Table caption elif isinstance(node.parent, nodes.table): @@ -3020,7 +3106,7 @@ class LaTeXTranslator(nodes.NodeVisitor): 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}\n' % + self.out.append('\n\\pdfbookmark[%d]{%s}{%s}' % (self.section_level+1, node.next_node().astext(), node.get('ids', ['contents'])[0] @@ -3032,59 +3118,50 @@ class LaTeXTranslator(nodes.NodeVisitor): depth = node.get('depth', 0) if 'local' in node['classes']: self.minitoc(node, title, depth) - self.context.append('') return if depth: self.out.append('\\setcounter{tocdepth}{%d}\n' % depth) if title != 'Contents': - self.out.append('\\renewcommand{\\contentsname}{%s}\n' % + self.out.append('\n\\renewcommand{\\contentsname}{%s}' % title) - self.out.append('\\tableofcontents\n\n') + 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.context.append('') elif ('abstract' in node['classes'] and self.settings.use_latex_abstract): self.push_output_collector(self.abstract) self.out.append('\\begin{abstract}') - self.context.append('\\end{abstract}\n') if isinstance(node.next_node(), nodes.title): node.pop(0) # LaTeX provides its own title else: - self.fallbacks['topic'] = PreambleCmds.topic # special topics: if 'abstract' in node['classes']: self.fallbacks['abstract'] = PreambleCmds.abstract self.push_output_collector(self.abstract) - if 'dedication' in node['classes']: + elif 'dedication' in node['classes']: self.fallbacks['dedication'] = PreambleCmds.dedication self.push_output_collector(self.dedication) - self.out.append('\n\\DUtopic[%s]{\n' % ','.join(node['classes'])) - self.context.append('}\n') + else: + node['classes'].insert(0, 'topic') + self.visit_block_quote(node) def depart_topic(self, node): - self.out.append(self.context.pop()) 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']: + self.depart_block_quote(node) if ('abstract' in node['classes'] or 'dedication' in node['classes']): self.pop_output_collector() - def visit_rubric(self, node): - self.fallbacks['rubric'] = PreambleCmds.rubric - self.out.append('\n\\DUrubric{') - self.context.append('}\n') - - def depart_rubric(self, node): - self.out.append(self.context.pop()) - def visit_transition(self, node): self.fallbacks['transition'] = PreambleCmds.transition - self.out.append('\n\n') - self.out.append('%' + '_' * 75 + '\n') - self.out.append(r'\DUtransition') - self.out.append('\n\n') + self.out.append('\n%' + '_' * 75 + '\n') + self.out.append('\\DUtransition\n') def depart_transition(self, node): pass 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 69e99fd..98f6396 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/default.tex @@ -1,5 +1,4 @@ $head_prefix% generated by Docutils <http://docutils.sourceforge.net/> -\usepackage{fixltx2e} % LaTeX patches, \textsubscript \usepackage{cmap} % fix search and cut-and-paste in Acrobat $requirements %%% Custom LaTeX preamble diff --git a/docutils/src/main/resources/docutils/docutils/writers/latex2e/xelatex.tex b/docutils/src/main/resources/docutils/docutils/writers/latex2e/xelatex.tex index 39df6f9..22c1311 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/xelatex.tex +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/xelatex.tex @@ -1,8 +1,12 @@ $head_prefix% generated by Docutils <http://docutils.sourceforge.net/> % rubber: set program xelatex -\usepackage{fixltx2e} \usepackage{fontspec} % \defaultfontfeatures{Scale=MatchLowercase} +% straight double quotes (defined T1 but missing in TU): +\ifdefined \UnicodeEncodingName + \DeclareTextCommand{\textquotedbl}{\UnicodeEncodingName}{% + {\addfontfeatures{RawFeature=-tlig,Mapping=}\char34}}% +\fi $requirements %%% Custom LaTeX preamble $latex_preamble diff --git a/docutils/src/main/resources/docutils/docutils/writers/manpage.py b/docutils/src/main/resources/docutils/docutils/writers/manpage.py index 0207285..287c6f2 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 7628 2013-03-09 10:19:35Z grubert $ +# $Id: manpage.py 8116 2017-06-18 19:09:40Z milde $ # Author: Engelbert Gruber <grubert@users.sourceforge.net> # Copyright: This module is put into the public domain. @@ -368,7 +368,7 @@ class Translator(nodes.NodeVisitor): tmpl = (".TH %(title_upper)s %(manual_section)s" " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" ".SH NAME\n" - "%(title)s \- %(subtitle)s\n") + "%(title)s \\- %(subtitle)s\n") return tmpl % self._docinfo def append_header(self): @@ -403,7 +403,7 @@ class Translator(nodes.NodeVisitor): self.defs['strong'][0], self.language.labels.get(name, name).upper(), self.defs['strong'][1], - ) + ) self.body.append(name) self.visit_block_quote(node) @@ -755,6 +755,12 @@ class Translator(nodes.NodeVisitor): depart_important = depart_admonition + def visit_inline(self, node): + pass + + def depart_inline(self, node): + pass + def visit_label(self, node): # footnote and citation if (isinstance(node.parent, nodes.footnote) @@ -818,7 +824,7 @@ class Translator(nodes.NodeVisitor): # BUG/HACK: indent alway uses the _last_ indention, # thus we need two of them. self.indent(LITERAL_BLOCK_INDENT) - self.indent(0) + self.indent(0) self.body.append(self.defs['literal_block'][0]) self._in_literal = True 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 3b29e3b..efe7967 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,4 +1,4 @@ -# $Id: __init__.py 7899 2015-06-03 22:02:46Z timehorse $ +# $Id: __init__.py 8131 2017-07-03 22:06:53Z dkuhlman $ # Author: Dave Kuhlman <dkuhlman@rexx.com> # Copyright: This module has been placed in the public domain. @@ -23,12 +23,18 @@ import re import StringIO import copy import urllib2 +import itertools import docutils +try: + import locale # module missing in Jython +except ImportError: + pass from docutils import frontend, nodes, utils, writers, languages from docutils.readers import standalone from docutils.transforms import references +IMAGE_NAME_COUNTER = itertools.count() WhichElementTree = '' try: # 1. Try to use lxml. @@ -569,6 +575,48 @@ class Writer(writers.Writer): s1 = self.create_meta() self.write_zip_str(zfile, 'meta.xml', s1) s1 = self.get_stylesheet() + # Set default language in document to be generated. + # Language is specified by the -l/--language command line option. + # The format is described in BCP 47. If region is omitted, we use + # local.normalize(ll) to obtain a region. + language_code = None + region_code = None + if self.visitor.language_code: + language_ids = self.visitor.language_code.replace('_', '-') + language_ids = language_ids.split('-') + # first tag is primary language tag + language_code = language_ids[0].lower() + # 2-letter region subtag may follow in 2nd or 3rd position + for subtag in language_ids[1:]: + if len(subtag) == 2 and subtag.isalpha(): + region_code = subtag.upper() + break + elif len(subtag) == 1: + break # 1-letter tag is never before valid region tag + if region_code is None: + try: + rcode = locale.normalize(language_code) + except NameError: + rcode = language_code + rcode = rcode.split('_') + if len(rcode) > 1: + rcode = rcode[1].split('.') + region_code = rcode[0] + if region_code is None: + self.document.reporter.warning( + 'invalid language-region.\n' + ' Could not find region with locale.normalize().\n' + ' Please specify both language and region (ll-RR).\n' + ' Examples: es-MX (Spanish, Mexico),\n' + ' en-AU (English, Australia).') + # Update the style ElementTree with the language and region. + # Note that we keep a reference to the modified node because + # it is possible that ElementTree will throw away the Python + # representation of the updated node if we do not. + updated, new_dom_styles, updated_node = self.update_stylesheet( + self.visitor.get_dom_stylesheet(), language_code, region_code) + if updated: + s1 = etree.tostring(new_dom_styles) self.write_zip_str(zfile, 'styles.xml', s1) self.store_embedded_files(zfile) self.copy_from_stylesheet(zfile) @@ -580,7 +628,58 @@ class Writer(writers.Writer): self.parts['encoding'] = self.document.settings.output_encoding self.parts['version'] = docutils.__version__ - def write_zip_str(self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED): + def update_stylesheet(self, stylesheet_root, language_code, region_code): + """Update xml style sheet element with language and region/country.""" + updated = False + modified_nodes = set() + if language_code is not None or region_code is not None: + n1 = stylesheet_root.find( + '{urn:oasis:names:tc:opendocument:xmlns:office:1.0}' + 'styles') + if n1 is None: + raise RuntimeError( + "Cannot find 'styles' element in styles.odt/styles.xml") + n2_nodes = n1.findall( + '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}' + 'default-style') + if not n2_nodes: + raise RuntimeError( + "Cannot find 'default-style' " + "element in styles.xml") + for node in n2_nodes: + family = node.attrib.get( + '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}' + 'family') + if family == 'paragraph' or family == 'graphic': + n3 = node.find( + '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}' + 'text-properties') + if n3 is None: + raise RuntimeError( + "Cannot find 'text-properties' " + "element in styles.xml") + if language_code is not None: + n3.attrib[ + '{urn:oasis:names:tc:opendocument:xmlns:' + 'xsl-fo-compatible:1.0}language'] = language_code + n3.attrib[ + '{urn:oasis:names:tc:opendocument:xmlns:' + 'style:1.0}language-complex'] = language_code + updated = True + modified_nodes.add(n3) + if region_code is not None: + n3.attrib[ + '{urn:oasis:names:tc:opendocument:xmlns:' + 'xsl-fo-compatible:1.0}country'] = region_code + n3.attrib[ + '{urn:oasis:names:tc:opendocument:xmlns:' + 'style:1.0}country-complex'] = region_code + updated = True + modified_nodes.add(n3) + return updated, stylesheet_root, modified_nodes + + def write_zip_str( + self, zfile, name, bytes, compress_type=zipfile.ZIP_DEFLATED): localtime = time.localtime(time.time()) zinfo = zipfile.ZipInfo(name, localtime) # Add some standard UNIX file access permissions (-rw-r--r--). @@ -725,8 +824,8 @@ class Writer(writers.Writer): #s1 = doc.toprettyxml(' ') return s1 -# class ODFTranslator(nodes.SparseNodeVisitor): +# class ODFTranslator(nodes.SparseNodeVisitor): class ODFTranslator(nodes.GenericNodeVisitor): used_styles = ( @@ -784,15 +883,17 @@ class ODFTranslator(nodes.GenericNodeVisitor): 'lineblock5', 'lineblock6', 'image', 'figureframe', - ) + ) def __init__(self, document): #nodes.SparseNodeVisitor.__init__(self, document) nodes.GenericNodeVisitor.__init__(self, document) self.settings = document.settings - lcode = self.settings.language_code - self.language = languages.get_language(lcode, document.reporter) - self.format_map = { } + self.language_code = self.settings.language_code + self.language = languages.get_language( + self.language_code, + document.reporter) + self.format_map = {} if self.settings.odf_config_file: from ConfigParser import ConfigParser @@ -802,8 +903,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): if rststyle not in self.used_styles: self.document.reporter.warning( 'Style "%s" is not a style used by odtwriter.' % ( - rststyle, )) - self.format_map[rststyle] = format.decode('utf-8') + rststyle, )) + if sys.version_info.major == 2: + self.format_map[rststyle] = format.decode('utf-8') self.section_level = 0 self.section_count = 0 # Create ElementTree content and styles documents. @@ -982,18 +1084,18 @@ class ODFTranslator(nodes.GenericNodeVisitor): 'style:name': style_name, 'style:master-page-name': "rststyle-pagedefault", 'style:family': "paragraph", - }, nsdict=SNSD) + }, nsdict=SNSD) if current_style: el1.set('style:parent-style-name', current_style) el.set('text:style-name', style_name) - - def rststyle(self, name, parameters=( )): + def rststyle(self, name, parameters=()): """ Returns the style name to use for the given style. - If `parameters` is given `name` must contain a matching number of ``%`` and - is used as a format expression with `parameters` as the value. + If `parameters` is given `name` must contain a matching number of + ``%`` and is used as a format expression with `parameters` as + the value. """ name1 = name % parameters stylename = self.format_map.get(name1, 'rststyle-%s' % name1) @@ -1010,6 +1112,9 @@ class ODFTranslator(nodes.GenericNodeVisitor): new_content = etree.tostring(self.dom_stylesheet) return new_content + def get_dom_stylesheet(self): + return self.dom_stylesheet + def setup_paper(self, root_el): try: fin = os.popen("paperconf -s 2> /dev/null") @@ -2058,7 +2163,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): # Capture the image file. if 'uri' in node.attributes: source = node.attributes['uri'] - if not source.startswith('http:'): + if not (source.startswith('http:') or source.startswith('https:')): if not source.startswith(os.sep): docsource, line = utils.get_source_line(node) if docsource: @@ -2077,7 +2182,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.image_count += 1 filename = os.path.split(source)[1] destination = 'Pictures/1%08x%s' % (self.image_count, filename, ) - if source.startswith('http:'): + if source.startswith('http:') or source.startswith('https:'): try: imgfile = urllib2.urlopen(source) content = imgfile.read() @@ -2118,70 +2223,151 @@ class ODFTranslator(nodes.GenericNodeVisitor): def get_image_width_height(self, node, attr): size = None + unit = None if attr in node.attributes: size = node.attributes[attr] - unit = size[-2:] - if unit.isalpha(): - size = size[:-2] - else: - unit = 'px' + size = size.strip() + # For conversion factors, see: + # http://www.unitconversion.org/unit_converter/typography-ex.html try: - size = float(size) - except ValueError, e: + if size.endswith('%'): + if attr == 'height': + # Percentage allowed for width but not height. + raise ValueError('percentage not allowed for height') + size = size.rstrip(' %') + size = float(size) / 100.0 + unit = '%' + else: + size, unit = convert_to_cm(size) + except ValueError, exp: self.document.reporter.warning( - 'Invalid %s for image: "%s"' % ( - attr, node.attributes[attr])) - size = [size, unit] - return size + 'Invalid %s for image: "%s". ' + 'Error: "%s".' % ( + attr, node.attributes[attr], exp)) + return size, unit + + def convert_to_cm(self, size): + """Convert various units to centimeters. + + Note that a call to this method should be wrapped in: + try: except ValueError: + """ + size = size.strip() + if size.endswith('px'): + size = float(size[:-2]) * 0.026 # convert px to cm + elif size.endswith('in'): + size = float(size[:-2]) * 2.54 # convert in to cm + elif size.endswith('pt'): + size = float(size[:-2]) * 0.035 # convert pt to cm + elif size.endswith('pc'): + size = float(size[:-2]) * 2.371 # convert pc to cm + elif size.endswith('mm'): + size = float(size[:-2]) * 10.0 # convert mm to cm + elif size.endswith('cm'): + size = float(size[:-2]) + else: + raise ValueError('unknown unit type') + unit = 'cm' + return size, unit def get_image_scale(self, node): if 'scale' in node.attributes: + scale = node.attributes['scale'] try: - scale = int(node.attributes['scale']) - if scale < 1: # or scale > 100: - self.document.reporter.warning( - 'scale out of range (%s), using 1.' % (scale, )) - scale = 1 - scale = scale * 0.01 - except ValueError, e: + scale = int(scale) + except ValueError: self.document.reporter.warning( 'Invalid scale for image: "%s"' % ( node.attributes['scale'], )) + if scale < 1: # or scale > 100: + self.document.reporter.warning( + 'scale out of range (%s), using 1.' % (scale, )) + scale = 1 + scale = scale * 0.01 else: scale = 1.0 return scale def get_image_scaled_width_height(self, node, source): + """Return the image size in centimeters adjusted by image attrs.""" scale = self.get_image_scale(node) - width = self.get_image_width_height(node, 'width') - height = self.get_image_width_height(node, 'height') - + width, width_unit = self.get_image_width_height(node, 'width') + height, _ = self.get_image_width_height(node, 'height') dpi = (72, 72) if PIL is not None and source in self.image_dict: filename, destination = self.image_dict[source] imageobj = PIL.Image.open(filename, 'r') dpi = imageobj.info.get('dpi', dpi) # dpi information can be (xdpi, ydpi) or xydpi - try: iter(dpi) - except: dpi = (dpi, dpi) + try: + iter(dpi) + except: + dpi = (dpi, dpi) else: imageobj = None - if width is None or height is None: if imageobj is None: raise RuntimeError( 'image size not fully specified and PIL not installed') - if width is None: width = [imageobj.size[0], 'px'] - if height is None: height = [imageobj.size[1], 'px'] - - width[0] *= scale - height[0] *= scale - if width[1] == 'px': width = [width[0] / dpi[0], 'in'] - if height[1] == 'px': height = [height[0] / dpi[1], 'in'] - - width[0] = str(width[0]) - height[0] = str(height[0]) - return ''.join(width), ''.join(height) + if width is None: + width = imageobj.size[0] + width = float(width) * 0.026 # convert px to cm + if height is None: + height = imageobj.size[1] + height = float(height) * 0.026 # convert px to cm + if width_unit == '%': + factor = width + image_width = imageobj.size[0] + image_width = float(image_width) * 0.026 # convert px to cm + image_height = imageobj.size[1] + image_height = float(image_height) * 0.026 # convert px to cm + line_width = self.get_page_width() + width = factor * line_width + factor = (factor * line_width) / image_width + height = factor * image_height + width *= scale + height *= scale + width = '%.2fcm' % width + height = '%.2fcm' % height + return width, height + + def get_page_width(self): + """Return the document's page width in centimeters.""" + root = self.get_dom_stylesheet() + nodes = root.iterfind( + './/{urn:oasis:names:tc:opendocument:xmlns:style:1.0}' + 'page-layout/' + '{urn:oasis:names:tc:opendocument:xmlns:style:1.0}' + 'page-layout-properties') + width = None + for node in nodes: + page_width = node.get( + '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}' + 'page-width') + margin_left = node.get( + '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}' + 'margin-left') + margin_right = node.get( + '{urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0}' + 'margin-right') + if (page_width is None or + margin_left is None or + margin_right is None): + continue + try: + page_width, _ = self.convert_to_cm(page_width) + margin_left, _ = self.convert_to_cm(margin_left) + margin_right, _ = self.convert_to_cm(margin_right) + except ValueError, exp: + self.document.reporter.warning( + 'Stylesheet file contains invalid page width ' + 'or margin size.') + width = page_width - margin_left - margin_right + if width is None: + # We can't find the width in styles, so we make a guess. + # Use a width of 6 in = 15.24 cm. + width = 15.24 + return width def generate_figure(self, node, source, destination, current_element): caption = None @@ -2222,6 +2408,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): el2 = 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() # Add the styles attrib = { 'style:name': style_name, @@ -2252,7 +2439,7 @@ class ODFTranslator(nodes.GenericNodeVisitor): 'style:graphic-properties', attrib=attrib, nsdict=SNSD) attrib = { 'draw:style-name': style_name, - 'draw:name': 'Frame1', + 'draw:name': draw_name, 'text:anchor-type': 'paragraph', 'draw:z-index': '0', } @@ -2326,12 +2513,13 @@ class ODFTranslator(nodes.GenericNodeVisitor): attrib['style:wrap'] = 'none' el2 = SubElement(el1, 'style:graphic-properties', attrib=attrib, nsdict=SNSD) + draw_name = 'graphics%d' % IMAGE_NAME_COUNTER.next() # Add the content. #el = SubElement(current_element, 'text:p', # attrib={'text:style-name': self.rststyle('textbody')}) attrib={ 'draw:style-name': style_name, - 'draw:name': 'graphics2', + 'draw:name': draw_name, 'draw:z-index': '1', } if isinstance(node.parent, nodes.TextElement): @@ -3263,14 +3451,19 @@ class ODFTranslator(nodes.GenericNodeVisitor): depart_admonition = depart_warning def generate_admonition(self, node, label, title=None): - el1 = SubElement(self.current_element, 'text:p', attrib = { - 'text:style-name': self.rststyle('admon-%s-hdr', ( label, )), - }) + if hasattr(self.language, 'labels'): + translated_label = self.language.labels[label] + else: + translated_label = label + el1 = SubElement(self.current_element, 'text:p', attrib={ + 'text:style-name': self.rststyle( + 'admon-%s-hdr', (label, )), + }) if title: el1.text = title else: - el1.text = '%s!' % (label.capitalize(), ) - s1 = self.rststyle('admon-%s-body', ( label, )) + el1.text = '%s!' % (translated_label.capitalize(), ) + s1 = self.rststyle('admon-%s-body', (label, )) self.paragraph_style_stack.append(s1) # 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 7d6fcaf..0e4aad1 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/xetex/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py index e5ecf1d..1c82059 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/xetex/__init__.py @@ -2,8 +2,8 @@ # -*- coding: utf-8 -*- # :Author: Günter Milde <milde@users.sourceforge.net> -# :Revision: $Revision: 7852 $ -# :Date: $Date: 2015-03-21 17:07:49 +0100 (Sa, 21. Mär 2015) $ +# :Revision: $Revision: 8046 $ +# :Date: $Date: 2017-03-11 13:09:36 +0100 (Sa, 11 Mär 2017) $ # :Copyright: © 2010 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -99,6 +99,11 @@ class Babel(latex2e.Babel): for key in ('af', # 'afrikaans', 'de-AT', # 'naustrian', 'de-AT-1901', # 'austrian', + # TODO: use variant=... for English variants + 'en-CA', # 'canadian', + 'en-GB', # 'british', + 'en-NZ', # 'newzealand', + 'en-US', # 'american', 'fr-CA', # 'canadien', 'grc-ibycus', # 'ibycus', (Greek Ibycus encoding) 'sr-Latn', # 'serbian script=latin' @@ -110,12 +115,12 @@ class Babel(latex2e.Babel): self.reporter = reporter self.language = self.language_name(language_code) self.otherlanguages = {} - self.warn_msg = 'Language "%s" not supported by XeTeX (polyglossia).' + self.warn_msg = 'Language "%s" not supported by Polyglossia.' self.quote_index = 0 self.quotes = ('"', '"') # language dependent configuration: # double quotes are "active" in some languages (e.g. German). - self.literal_double_quote = u'"' # TODO: use \textquotedbl + self.literal_double_quote = u'"' # TODO: use \textquotedbl ? def __call__(self): setup = [r'\usepackage{polyglossia}', @@ -127,6 +132,12 @@ class Babel(latex2e.Babel): class XeLaTeXTranslator(latex2e.LaTeXTranslator): + """ + Generate code for LaTeX using Unicode fonts (XeLaTex or LuaLaTeX). + + See the docstring of docutils.writers._html_base.HTMLTranslator for + notes on and examples of safe subclassing. + """ def __init__(self, document): self.is_xetex = True # typeset with XeTeX or LuaTeX engine -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.