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 6076986ca0c874f5d598df9f3ac11461dc43d0aa Author: Eric Chatellier <eric.chatellier@gmail.com> Date: Sun Feb 12 16:03:13 2017 +0100 fixes #4148: Update docutils codebase to 0.13.1 --- .../main/resources/docutils/docutils/__init__.py | 4 +- .../src/main/resources/docutils/docutils/io.py | 6 +- .../resources/docutils/docutils/languages/fa.py | 61 ++ .../resources/docutils/docutils/languages/lt.py | 67 +- .../resources/docutils/docutils/languages/lv.py | 60 + .../src/main/resources/docutils/docutils/nodes.py | 4 +- .../docutils/docutils/parsers/rst/__init__.py | 25 +- .../docutils/parsers/rst/directives/__init__.py | 14 +- .../docutils/parsers/rst/directives/misc.py | 22 +- .../docutils/parsers/rst/directives/tables.py | 81 +- .../docutils/docutils/parsers/rst/languages/fa.py | 102 ++ .../docutils/docutils/parsers/rst/languages/lv.py | 108 ++ .../docutils/docutils/parsers/rst/roles.py | 7 +- .../docutils/docutils/parsers/rst/states.py | 273 ++--- .../docutils/docutils/parsers/rst/tableparser.py | 4 +- .../docutils/docutils/transforms/frontmatter.py | 6 +- .../resources/docutils/docutils/transforms/peps.py | 6 +- .../docutils/docutils/transforms/universal.py | 6 +- .../docutils/docutils/transforms/writer_aux.py | 2 +- .../docutils/docutils/utils/code_analyzer.py | 4 +- .../docutils/docutils/utils/error_reporting.py | 26 +- .../docutils/docutils/utils/math/__init__.py | 7 +- .../docutils/docutils/utils/math/latex2mathml.py | 13 +- .../docutils/docutils/utils/math/math2html.py | 402 ++++--- .../docutils/utils/math/tex2mathml_extern.py | 147 +++ .../docutils/docutils/utils/smartquotes.py | 4 +- .../docutils/docutils/utils/urischemes.py | 4 +- .../docutils/docutils/writers/__init__.py | 13 +- .../{html4css1/__init__.py => _html_base.py} | 982 +++++++---------- .../docutils/docutils/writers/docutils_xml.py | 22 +- .../docutils/writers/html4css1/__init__.py | 1157 +++----------------- .../docutils/writers/html4css1/html4css1.css | 28 +- .../docutils/writers/html5_polyglot/__init__.py | 200 ++++ .../writers/{html4css1 => html5_polyglot}/math.css | 0 .../docutils/writers/html5_polyglot/minimal.css | 260 +++++ .../docutils/writers/html5_polyglot/plain.css | 288 +++++ .../docutils/writers/html5_polyglot/template.txt | 8 + .../docutils/docutils/writers/latex2e/__init__.py | 460 ++++---- .../docutils/docutils/writers/latex2e/xelatex.tex | 3 +- .../docutils/docutils/writers/odf_odt/__init__.py | 32 +- .../docutils/docutils/writers/pep_html/pep.css | 2 +- .../docutils/docutils/writers/xetex/__init__.py | 15 +- 42 files changed, 2693 insertions(+), 2242 deletions(-) diff --git a/docutils/src/main/resources/docutils/docutils/__init__.py b/docutils/src/main/resources/docutils/docutils/__init__.py index 270b883..79ed8fb 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 7756 2014-07-06 11:48:05Z grubert $ +# $Id: __init__.py 7984 2016-12-09 09:48:27Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -52,7 +52,7 @@ Subpackages: __docformat__ = 'reStructuredText' -__version__ = '0.12' +__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 diff --git a/docutils/src/main/resources/docutils/docutils/io.py b/docutils/src/main/resources/docutils/docutils/io.py index 1a14ac0..234e14c 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 7596 2013-01-25 13:42:17Z milde $ +# $Id: io.py 7864 2015-04-12 09:57:05Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -369,9 +369,9 @@ class FileOutput(Output): if ('b' not in self.mode and sys.version_info < (3,0) or check_encoding(self.destination, self.encoding) is False ): - if sys.version_info >= (3,0) and os.linesep != '\n': - data = data.replace('\n', os.linesep) # fix endings data = self.encode(data) + if sys.version_info >= (3,0) and os.linesep != '\n': + data = data.replace(b('\n'), b(os.linesep)) # fix endings try: # In Python < 2.5, try...except has to be nested in try...finally. try: diff --git a/docutils/src/main/resources/docutils/docutils/languages/fa.py b/docutils/src/main/resources/docutils/docutils/languages/fa.py new file mode 100644 index 0000000..48aa588 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/languages/fa.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# $Id: fa.py 4564 2016-08-10 11:48:42Z +# Author: Shahin <me@5hah.in> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be +# translated for each language: one in docutils/languages, the other in +# docutils/parsers/rst/languages. + +""" +Persian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + u'author': u'نویسنده', + u'authors': u'نویسندگان', + u'organization': u'سازمان', + u'address': u'آدرس', + u'contact': u'تماس', + u'version': u'نسخه', + u'revision': u'بازبینی', + u'status': u'وضعیت', + u'date': u'تاریخ', + u'copyright': u'کپیرایت', + u'dedication': u'تخصیص', + u'abstract': u'چکیده', + u'attention': u'توجه!', + u'caution': u'احتیاط!', + u'danger': u'خطر!', + u'error': u'خطا', + u'hint': u'راهنما', + u'important': u'مهم', + u'note': u'یادداشت', + u'tip': u'نکته', + u'warning': u'اخطار', + u'contents': u'محتوا'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + u'نویسنده': u'author', + u'نویسندگان': u'authors', + u'سازمان': u'organization', + u'آدرس': u'address', + u'تماس': u'contact', + u'نسخه': u'version', + u'بازبینی': u'revision', + u'وضعیت': u'status', + u'تاریخ': u'date', + u'کپیرایت': u'copyright', + u'تخصیص': u'dedication', + u'چکیده': u'abstract'} +"""Persian (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [u'؛', u'،'] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/docutils/src/main/resources/docutils/docutils/languages/lt.py b/docutils/src/main/resources/docutils/docutils/languages/lt.py index 7e1569c..c5c3fdc 100644 --- a/docutils/src/main/resources/docutils/docutils/languages/lt.py +++ b/docutils/src/main/resources/docutils/docutils/languages/lt.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: lt.py 7668 2013-06-04 12:46:30Z milde $ +# $Id: lt.py 7911 2015-08-31 08:23:06Z milde $ # Author: Dalius Dobravolskas <dalius.do...@gmail.com> # Copyright: This module has been placed in the public domain. @@ -9,7 +9,7 @@ # docutils/parsers/rst/languages. """ -English-language mappings for language-dependent features of Docutils. +Lithuanian language mappings for language-dependent features of Docutils. """ __docformat__ = 'reStructuredText' @@ -54,68 +54,7 @@ bibliographic_fields = { 'autoriaus teisės': 'copyright', 'dedikacija': 'dedication', 'santrauka': 'abstract'} -"""English (lowcased) to canonical name mapping for bibliographic fields.""" - -author_separators = [';', ','] -"""List of separator strings for the 'Authors' bibliographic field. Tried in -order.""" -# -*- coding: utf-8 -*- -# $Id: lt.py 7668 2013-06-04 12:46:30Z milde $ -# Author: David Goodger <goodger@python.org> -# Copyright: This module has been placed in the public domain. - -# New language mappings are welcome. Before doing a new translation, please -# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be -# translated for each language: one in docutils/languages, the other in -# docutils/parsers/rst/languages. - -""" -English-language mappings for language-dependent features of Docutils. -""" - -__docformat__ = 'reStructuredText' - -labels = { - # fixed: language-dependent - 'author': 'Autorius', - 'authors': 'Autoriai', - 'organization': 'Organizacija', - 'address': 'Adresas', - 'contact': 'Kontaktas', - 'version': 'Versija', - 'revision': 'Revizija', - 'status': u'Būsena', - 'date': 'Data', - 'copyright': u'Autoriaus teisės', - 'dedication': 'Dedikacija', - 'abstract': 'Santrauka', - 'attention': u'Dėmesio!', - 'caution': 'Atsargiai!', - 'danger': '!PAVOJINGA!', - 'error': 'Klaida', - 'hint': u'Užuomina', - 'important': 'Svarbu', - 'note': 'Pastaba', - 'tip': 'Patarimas', - 'warning': u'Įspėjimas', - 'contents': 'Turinys'} -"""Mapping of node class name to label text.""" - -bibliographic_fields = { - # language-dependent: fixed - 'autorius': 'author', - 'autoriai': 'authors', - 'organizacija': 'organization', - 'adresas': 'address', - 'kontaktas': 'contact', - 'versija': 'version', - 'revizija': 'revision', - 'būsena': 'status', - 'data': 'date', - 'autoriaus teisės': 'copyright', - 'dedikacija': 'dedication', - 'santrauka': 'abstract'} -"""English (lowcased) to canonical name mapping for bibliographic fields.""" +"""Lithuanian (lowcased) to canonical name mapping for bibliographic fields.""" author_separators = [';', ','] """List of separator strings for the 'Authors' bibliographic field. Tried in diff --git a/docutils/src/main/resources/docutils/docutils/languages/lv.py b/docutils/src/main/resources/docutils/docutils/languages/lv.py new file mode 100644 index 0000000..f8125d4 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/languages/lv.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# $Id: lv.py 7975 2016-10-20 20:00:19Z milde $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be +# translated for each language: one in docutils/languages, the other in +# docutils/parsers/rst/languages. + +""" +Latvian-language mappings for language-dependent features of Docutils. +""" + +__docformat__ = 'reStructuredText' + +labels = { + # fixed: language-dependent + 'author': 'Autors', + 'authors': 'Autori', + 'organization': 'Organizācija', + 'address': 'Adrese', + 'contact': 'Kontakti', + 'version': 'Versija', + 'revision': 'Revīzija', + 'status': 'Statuss', + 'date': 'Datums', + 'copyright': 'Copyright', + 'dedication': 'Veltījums', + 'abstract': 'Atreferējums', + 'attention': 'Uzmanību!', + 'caution': 'Piesardzību!', + 'danger': '!BĪSTAMI!', + 'error': 'Kļūda', + 'hint': 'Ieteikums', + 'important': 'Svarīgi', + 'note': 'Piezīme', + 'tip': 'Padoms', + 'warning': 'Brīdinājums', + 'contents': 'Saturs'} +"""Mapping of node class name to label text.""" + +bibliographic_fields = { + # language-dependent: fixed + 'autors': 'author', + 'autori': 'authors', + 'organizācija': 'organization', + 'adrese': 'address', + 'kontakti': 'contact', + 'versija': 'version', + 'revīzija': 'revision', + 'statuss': 'status', + 'datums': 'date', + 'copyright': 'copyright', + 'veltījums': 'dedication', + 'atreferējums': 'abstract'} +"""English (lowcased) to canonical name mapping for bibliographic fields.""" + +author_separators = [';', ','] +"""List of separator strings for the 'Authors' bibliographic field. Tried in +order.""" diff --git a/docutils/src/main/resources/docutils/docutils/nodes.py b/docutils/src/main/resources/docutils/docutils/nodes.py index 7c172ee..8628e9c 100644 --- a/docutils/src/main/resources/docutils/docutils/nodes.py +++ b/docutils/src/main/resources/docutils/docutils/nodes.py @@ -1,4 +1,4 @@ -# $Id: nodes.py 7595 2013-01-21 17:33:56Z milde $ +# $Id: nodes.py 7788 2015-02-16 22:10:52Z milde $ # Author: David Goodger <goodger@python.org> # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -533,7 +533,7 @@ class Element(Node): parts = [self.tagname] for name, value in self.attlist(): if value is None: # boolean attribute - parts.append(name) + parts.append('%s="True"' % name) continue if isinstance(value, list): values = [serial_escape('%s' % (v,)) for v in value] 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 ed70e0e..6bf1e52 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 7598 2013-01-30 12:39:24Z milde $ +# $Id: __init__.py 7961 2016-07-28 22:02:47Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -101,9 +101,9 @@ class Parser(docutils.parsers.Parser): ('Recognize and link to standalone RFC references (like "RFC 822").', ['--rfc-references'], {'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Base URL for RFC references (default "http://www.faqs.org/rfcs/").', + ('Base URL for RFC references (default "http://tools.ietf.org/html/").', ['--rfc-base-url'], - {'metavar': '<URL>', 'default': 'http://www.faqs.org/rfcs/', + {'metavar': '<URL>', 'default': 'http://tools.ietf.org/html/', 'validator': frontend.validate_url_trailing_slash}), ('Set number of spaces for tab expansion (default 8).', ['--tab-width'], @@ -142,6 +142,19 @@ class Parser(docutils.parsers.Parser): 'one of "yes", "no", "alt[ernative]" (default "no").', ['--smart-quotes'], {'default': False, 'validator': frontend.validate_ternary}), + ('Inline markup recognized at word boundaries only ' + '(adjacent to punctuation or whitespace). ' + 'Force character-level inline markup recognition with ' + '"\ " (backslash + space). Default.', + ['--word-level-inline-markup'], + {'action': 'store_false', 'dest': 'character_level_inline_markup'}), + ('Inline markup recognized anywhere, regardless of surrounding ' + 'characters. Backslash-escapes must be used to avoid unwanted ' + 'markup recognition. Useful for East Asian languages. ' + 'Experimental.', + ['--character-level-inline-markup'], + {'action': 'store_true', 'default': False, + 'dest': 'character_level_inline_markup'}), )) config_section = 'restructuredtext parser' @@ -249,12 +262,6 @@ class Directive(object): - ``lineno`` is the absolute line number of the first line of the directive. - - ``src`` is the name (or path) of the rst source of the directive. - - - ``srcline`` is the line number of the first line of the directive - in its source. It may differ from ``lineno``, if the main source - includes other sources with the ``.. include::`` directive. - - ``content_offset`` is the line offset of the first line of the content from the beginning of the current input. Used when initiating a nested parse. 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 6e843b8..b1ede9c 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 7621 2013-03-04 13:20:49Z milde $ +# $Id: __init__.py 7785 2015-02-08 23:51:45Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -402,3 +402,15 @@ def choice(argument, values): def format_values(values): return '%s, or "%s"' % (', '.join(['"%s"' % s for s in values[:-1]]), values[-1]) + +def value_or(values, other): + """ + The argument can be any of `values` or `argument_type`. + """ + def auto_or_other(argument): + if argument in values: + return argument + else: + return other(argument) + return auto_or_other + diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py index 643bc70..f843bdc 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/directives/misc.py @@ -1,4 +1,4 @@ -# $Id: misc.py 7487 2012-07-22 21:20:28Z milde $ +# $Id: misc.py 7961 2016-07-28 22:02:47Z milde $ # Authors: David Goodger <goodger@python.org>; Dethe Elza # Copyright: This module has been placed in the public domain. @@ -231,7 +231,7 @@ class Raw(Directive): raise self.severe(u'Problems with "%s" directive URL "%s":\n%s.' % (self.name, self.options['url'], ErrorString(error))) raw_file = io.StringInput(source=raw_text, source_path=source, - encoding=encoding, + encoding=encoding, error_handler=e_handler) try: text = raw_file.read() @@ -477,6 +477,24 @@ class Date(Directive): except UnicodeEncodeError: raise self.warning(u'Cannot encode date format string ' u'with locale encoding "%s".' % locale_encoding) + # @@@ + # Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable? + # Pro: Docutils-generated documentation + # can easily be part of `reproducible software builds`__ + # + # __ https://reproducible-builds.org/ + # + # Con: Changes the specs, hard to predict behaviour, + # no actual use case! + # + # See also the discussion about \date \time \year in TeX + # http://tug.org/pipermail/tex-k/2016-May/002704.html + # source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') + # if (source_date_epoch + # and self.state.document.settings.use_source_date_epoch): + # text = time.strftime(format_str, + # time.gmtime(int(source_date_epoch))) + # else: text = time.strftime(format_str) if sys.version_info< (3, 0): # `text` is a byte string that may contain non-ASCII characters: 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 67fda63..0e890b6 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 7747 2014-03-20 10:51:10Z milde $ +# $Id: tables.py 7958 2016-07-28 21:52:14Z milde $ # Authors: David Goodger <goodger@python.org>; David Priest # Copyright: This module has been placed in the public domain. @@ -20,6 +20,10 @@ from docutils.parsers.rst import Directive from docutils.parsers.rst import directives +def align(argument): + return directives.choice(argument, ('left', 'center', 'right')) + + class Table(Directive): """ @@ -29,7 +33,10 @@ class Table(Directive): optional_arguments = 1 final_argument_whitespace = True option_spec = {'class': directives.class_option, - 'name': directives.unchanged} + 'name': directives.unchanged, + 'align': align, + 'widths': directives.value_or(('auto', 'grid'), + directives.positive_int_list)} has_content = True def make_title(self): @@ -85,15 +92,19 @@ class Table(Directive): self.block_text, self.block_text), line=self.lineno) raise SystemMessagePropagation(error) + @property + def widths(self): + return self.options.get('widths', '') + def get_column_widths(self, max_cols): - if 'widths' in self.options: - col_widths = self.options['widths'] - if len(col_widths) != max_cols: + if type(self.widths) == list: + if len(self.widths) != max_cols: error = self.state_machine.reporter.error( '"%s" widths do not match the number of columns in table ' '(%s).' % (self.name, max_cols), nodes.literal_block( self.block_text, self.block_text), line=self.lineno) raise SystemMessagePropagation(error) + col_widths = self.widths elif max_cols: col_widths = [100 // max_cols] * max_cols else: @@ -101,7 +112,13 @@ 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) - return col_widths + if self.widths == 'auto': + widths = 'auto' + elif self.widths: # "grid" or list of integers + widths = 'given' + else: + widths = self.widths + return widths, col_widths def extend_short_rows_with_empty_cells(self, columns, parts): for part in parts: @@ -130,6 +147,21 @@ class RSTTable(Table): return [error] table_node = node[0] table_node['classes'] += self.options.get('class', []) + if 'align' in self.options: + table_node['align'] = self.options.get('align') + tgroup = table_node[0] + if type(self.widths) == list: + colspecs = [child for child in tgroup.children + if child.tagname == 'colspec'] + for colspec, col_width in zip(colspecs, self.widths): + colspec['colwidth'] = col_width + # @@@ the colwidths argument for <tgroup> is not part of the + # XML Exchange Table spec (https://www.oasis-open.org/specs/tm9901.htm) + # and hence violates the docutils.dtd. + if self.widths == 'auto': + table_node['classes'] += ['colwidths-auto'] + elif self.widths: # "grid" or list of integers + table_node['classes'] += ['colwidths-given'] self.add_name(table_node) if title: table_node.insert(0, title) @@ -141,12 +173,14 @@ class CSVTable(Table): option_spec = {'header-rows': directives.nonnegative_int, 'stub-columns': directives.nonnegative_int, 'header': directives.unchanged, - 'widths': directives.positive_int_list, + 'widths': directives.value_or(('auto', ), + directives.positive_int_list), 'file': directives.path, 'url': directives.uri, 'encoding': directives.encoding, 'class': directives.class_option, 'name': directives.unchanged, + 'align': align, # field delimiter char 'delim': directives.single_char_or_whitespace_or_unicode, # treat whitespace after delimiter as significant @@ -219,7 +253,7 @@ class CSVTable(Table): self.check_table_dimensions(rows, header_rows, stub_columns) table_head.extend(rows[:header_rows]) table_body = rows[header_rows:] - col_widths = self.get_column_widths(max_cols) + widths, col_widths = self.get_column_widths(max_cols) self.extend_short_rows_with_empty_cells(max_cols, (table_head, table_body)) except SystemMessagePropagation, detail: @@ -235,8 +269,10 @@ class CSVTable(Table): return [error] table = (col_widths, table_head, table_body) table_node = self.state.build_table(table, self.content_offset, - stub_columns) + stub_columns, widths=widths) table_node['classes'] += self.options.get('class', []) + if 'align' in self.options: + table_node['align'] = self.options.get('align') self.add_name(table_node) if title: table_node.insert(0, title) @@ -356,13 +392,15 @@ class ListTable(Table): Implement tables whose data is encoded as a uniform two-level bullet list. For further ideas, see http://docutils.sf.net/docs/dev/rst/alternatives.html#list-driven-tables - """ + """ option_spec = {'header-rows': directives.nonnegative_int, 'stub-columns': directives.nonnegative_int, - 'widths': directives.positive_int_list, + 'widths': directives.value_or(('auto', ), + directives.positive_int_list), 'class': directives.class_option, - 'name': directives.unchanged} + 'name': directives.unchanged, + 'align': align} def run(self): if not self.content: @@ -375,7 +413,7 @@ class ListTable(Table): node = nodes.Element() # anonymous container for parsing self.state.nested_parse(self.content, self.content_offset, node) try: - num_cols, col_widths = self.check_list_content(node) + num_cols, widths, 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) @@ -383,8 +421,10 @@ 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, col_widths, + table_node = self.build_table_from_list(table_data, widths, col_widths, header_rows, stub_columns) + if 'align' in self.options: + table_node['align'] = self.options.get('align') table_node['classes'] += self.options.get('class', []) self.add_name(table_node) if title: @@ -427,15 +467,20 @@ class ListTable(Table): raise SystemMessagePropagation(error) else: num_cols = len(item[0]) - col_widths = self.get_column_widths(num_cols) - return num_cols, col_widths + widths, col_widths = self.get_column_widths(num_cols) + return num_cols, widths, col_widths - def build_table_from_list(self, table_data, col_widths, header_rows, stub_columns): + def build_table_from_list(self, table_data, widths, col_widths, header_rows, + stub_columns): table = nodes.table() + if widths: + table['classes'] += ['colwidths-%s' % widths] tgroup = nodes.tgroup(cols=len(col_widths)) table += tgroup for col_width in col_widths: - colspec = nodes.colspec(colwidth=col_width) + colspec = nodes.colspec() + if col_width is not None: + colspec.attributes['colwidth'] = col_width if stub_columns: colspec.attributes['stub'] = 1 stub_columns -= 1 diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/fa.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/fa.py new file mode 100644 index 0000000..5547759 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/fa.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# $Id: fa.py 4564 2016-08-10 11:48:42Z +# Author: Shahin <me@5hah.in> +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be +# translated for each language: one in docutils/languages, the other in +# docutils/parsers/rst/languages. + +""" +Persian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + u'توجه': u'attention', + u'احتیاط': u'caution', + u'کد': u'code', + u'بلوک-کد': u'code', + u'کد-منبع': u'code', + u'خطر': u'danger', + u'خطا': u'error', + u'راهنما': u'hint', + u'مهم': u'important', + u'یادداشت': u'note', + u'نکته': u'tip', + u'اخطار': u'warning', + u'تذکر': u'admonition', + u'نوار-کناری': u'sidebar', + u'موضوع': u'topic', + u'بلوک-خط': u'line-block', + u'تلفظ-پردازش-شده': u'parsed-literal', + u'سر-فصل': u'rubric', + u'کتیبه': u'epigraph', + u'نکات-برجسته': u'highlights', + u'نقل-قول': u'pull-quote', + u'ترکیب': u'compound', + u'ظرف': u'container', + #'questions': u'questions', + u'جدول': u'table', + u'جدول-csv': u'csv-table', + u'جدول-لیست': u'list-table', + #'qa': u'questions', + #'faq': u'questions', + u'متا': u'meta', + u'ریاضی': u'math', + #'imagemap': u'imagemap', + u'تصویر': u'image', + u'شکل': u'figure', + u'شامل': u'include', + u'خام': u'raw', + u'جایگزین': u'replace', + u'یونیکد': u'unicode', + u'تاریخ': u'date', + u'کلاس': u'class', + u'قانون': u'role', + u'قانون-پیشفرض': u'default-role', + u'عنوان': u'title', + u'محتوا': u'contents', + u'شماره-فصل': u'sectnum', + u'شمارهگذاری-فصل': u'sectnum', + u'سرآیند': u'header', + u'پاصفحه': u'footer', + #'footnotes': u'footnotes', + #'citations': u'citations', + u'یادداشت-هدف': u'target-notes', + } +"""Persian name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + u'مخفف': u'abbreviation', + u'سرنام': u'acronym', + u'کد': u'code', + u'شاخص': u'index', + u'زیرنویس': u'subscript', + u'بالانویس': u'superscript', + u'عنوان': u'title-reference', + u'نیرو': u'pep-reference', + u'rfc-reference (translation required)': u'rfc-reference', + u'تاکید': u'emphasis', + u'قوی': u'strong', + u'لفظی': u'literal', + u'ریاضی': u'math', + u'منبع-نامگذاری': u'named-reference', + u'منبع-ناشناس': u'anonymous-reference', + u'منبع-پانویس': u'footnote-reference', + u'منبع-نقلفول': u'citation-reference', + u'منبع-جایگزینی': u'substitution-reference', + u'هدف': u'target', + u'منبع-uri': u'uri-reference', + u'uri': u'uri-reference', + u'url': u'uri-reference', + u'خام': u'raw',} +"""Mapping of Persian role names to canonical role names for interpreted text. +""" diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/lv.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/lv.py new file mode 100644 index 0000000..3f8313c --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/languages/lv.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# $Id: lv.py 7975 2016-10-20 20:00:19Z milde $ +# Copyright: This module has been placed in the public domain. + +# New language mappings are welcome. Before doing a new translation, please +# read <http://docutils.sf.net/docs/howto/i18n.html>. Two files must be +# translated for each language: one in docutils/languages, the other in +# docutils/parsers/rst/languages. + +""" +Latvian-language mappings for language-dependent features of +reStructuredText. +""" + +__docformat__ = 'reStructuredText' + + +directives = { + # language-dependent: fixed + 'uzmanību': 'attention', + 'piesardzību': 'caution', + 'kods': 'code', + 'koda-bloks': 'code', + 'pirmkods': 'code', + 'bīstami': 'danger', + 'kļūda': 'error', + 'ieteikums': 'hint', + 'svarīgi': 'important', + 'piezīme': 'note', + 'padoms': 'tip', + 'brīdinājums': 'warning', + 'aizrādījums': 'admonition', + 'sānjosla': 'sidebar', + 'tēma': 'topic', + 'rindu-bloks': 'line-block', + 'parsēts-literālis': 'parsed-literal', + 'rubrika': 'rubric', + 'epigrāfs': 'epigraph', + 'apskats': 'highlights', + 'izvilkuma-citāts': 'pull-quote', + 'savienojums': 'compound', + 'konteiners': 'container', + #'questions': 'questions', + 'tabula': 'table', + 'csv-tabula': 'csv-table', + 'sarakstveida-tabula': 'list-table', + #'qa': 'questions', + #'faq': 'questions', + 'meta': 'meta', + 'matemātika': 'math', + #'imagemap': 'imagemap', + 'attēls': 'image', + 'figūra': 'figure', + 'ietvert': 'include', + 'burtiski': 'raw', + 'aizvieto': 'replace', + 'unicode': 'unicode', + 'datums': 'date', + 'klase': 'class', + 'role': 'role', + 'noklusējuma-role': 'default-role', + 'virsraksts': 'title', + 'saturs': 'contents', + 'numurēt-sekcijas': 'sectnum', + 'galvene': 'header', + 'kājene': 'footer', + #'footnotes': 'footnotes', + #'citations': 'citations', + 'atsauces-apakšā': 'target-notes', + 'restructuredtext-testa-direktīva': 'restructuredtext-test-directive'} +"""English name to registered (in directives/__init__.py) directive name +mapping.""" + +roles = { + # language-dependent: fixed + 'saīsinājums': 'abbreviation', + 'īsi': 'abbreviation', + 'akronīms': 'acronym', + 'kods': 'code', + 'indekss': 'index', + 'i': 'index', + 'apakšraksts': 'subscript', + 'apakšā': 'subscript', + 'augšraksts': 'superscript', + 'augšā': 'superscript', + 'virsraksta-atsauce': 'title-reference', + 'virsraksts': 'title-reference', + 'v': 'title-reference', + 'atsauce-uz-pep': 'pep-reference', + 'pep': 'pep-reference', + 'atsauce-uz-rfc': 'rfc-reference', + 'rfc': 'rfc-reference', + 'izcēlums': 'emphasis', + 'blīvs': 'strong', + 'literālis': 'literal', + 'matemātika': 'math', + 'nosaukta-atsauce': 'named-reference', + 'nenosaukta-atsauce': 'anonymous-reference', + 'kājenes-atsauce': 'footnote-reference', + 'citātā-atsauce': 'citation-reference', + 'aizvietojuma-atsauce': 'substitution-reference', + 'mēr''kis': 'target', + 'atsauce-uz-uri': 'uri-reference', + 'uri': 'uri-reference', + 'url': 'uri-reference', + 'burtiski': 'raw',} +"""Mapping of English role names to canonical role names for interpreted text. +""" diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py index b6dca44..7fa8c1f 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/roles.py @@ -1,4 +1,4 @@ -# $Id: roles.py 7514 2012-09-14 14:27:12Z milde $ +# $Id: roles.py 7937 2016-05-24 10:48:48Z milde $ # Author: Edward Loper <edloper@gradient.cis.upenn.edu> # Copyright: This module has been placed in the public domain. @@ -334,7 +334,7 @@ def code_role(role, rawtext, text, lineno, inliner, options={}, content=[]): node = nodes.literal(rawtext, '', classes=classes) - # analyze content and add nodes for every token + # analyse content and add nodes for every token for classes, value in tokens: # print (classes, value) if classes: @@ -351,9 +351,10 @@ code_role.options = {'class': directives.class_option, register_canonical_role('code', code_role) def math_role(role, rawtext, text, lineno, inliner, options={}, content=[]): + set_classes(options) i = rawtext.find('`') text = rawtext.split('`')[1] - node = nodes.math(rawtext, text) + node = nodes.math(rawtext, text, **options) return [node], [] register_canonical_role('math', math_role) 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 30f48df..35de9d2 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 7640 2013-03-25 20:57:52Z milde $ +# $Id: states.py 7958 2016-07-28 21:52:14Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -225,7 +225,6 @@ class RSTState(StateWS): # enable the reporter to determine source and source-line if not hasattr(self.reporter, 'get_source_and_line'): self.reporter.get_source_and_line = self.state_machine.get_source_and_line - # print "adding get_source_and_line to reporter", self.state_machine.input_offset def goto_line(self, abs_line_offset): @@ -464,12 +463,142 @@ class Inliner: """ def __init__(self): + pass + + def init_customizations(self, settings): + # lookahead and look-behind expressions for inline markup rules + if settings.character_level_inline_markup: + start_string_prefix = u'(^|(?<!\x00))' + end_string_suffix = u'' + else: + start_string_prefix = (u'(^|(?<=\\s|[%s%s]))' % + (punctuation_chars.openers, + punctuation_chars.delimiters)) + end_string_suffix = (u'($|(?=\\s|[\x00%s%s%s]))' % + (punctuation_chars.closing_delimiters, + punctuation_chars.delimiters, + punctuation_chars.closers)) + + args = locals().copy() + args.update(vars(self.__class__)) + + parts = ('initial_inline', start_string_prefix, '', + [('start', '', self.non_whitespace_after, # simple start-strings + [r'\*\*', # strong + r'\*(?!\*)', # emphasis but not strong + r'``', # literal + r'_`', # inline internal target + r'\|(?!\|)'] # substitution reference + ), + ('whole', '', end_string_suffix, # whole constructs + [# reference name & end-string + r'(?P<refname>%s)(?P<refend>__?)' % self.simplename, + ('footnotelabel', r'\[', r'(?P<fnend>\]_)', + [r'[0-9]+', # manually numbered + r'\#(%s)?' % self.simplename, # auto-numbered (w/ label?) + r'\*', # auto-symbol + r'(?P<citationlabel>%s)' % self.simplename] # citation reference + ) + ] + ), + ('backquote', # interpreted text or phrase reference + '(?P<role>(:%s:)?)' % self.simplename, # optional role + self.non_whitespace_after, + ['`(?!`)'] # but not literal + ) + ] + ) + + self.patterns = Struct( + initial=build_regexp(parts), + emphasis=re.compile(self.non_whitespace_escape_before + + r'(\*)' + end_string_suffix, re.UNICODE), + strong=re.compile(self.non_whitespace_escape_before + + r'(\*\*)' + end_string_suffix, re.UNICODE), + interpreted_or_phrase_ref=re.compile( + r""" + %(non_unescaped_whitespace_escape_before)s + ( + ` + (?P<suffix> + (?P<role>:%(simplename)s:)? + (?P<refend>__?)? + ) + ) + %(end_string_suffix)s + """ % args, re.VERBOSE | re.UNICODE), + embedded_link=re.compile( + r""" + ( + (?:[ \n]+|^) # spaces or beginning of line/string + < # open bracket + %(non_whitespace_after)s + (([^<>]|\x00[<>])+) # anything but unescaped angle brackets + %(non_whitespace_escape_before)s + > # close bracket + ) + $ # end of string + """ % args, re.VERBOSE | re.UNICODE), + literal=re.compile(self.non_whitespace_before + '(``)' + + end_string_suffix), + target=re.compile(self.non_whitespace_escape_before + + r'(`)' + end_string_suffix), + substitution_ref=re.compile(self.non_whitespace_escape_before + + r'(\|_{0,2})' + + end_string_suffix), + email=re.compile(self.email_pattern % args + '$', + re.VERBOSE | re.UNICODE), + uri=re.compile( + (r""" + %(start_string_prefix)s + (?P<whole> + (?P<absolute> # absolute URI + (?P<scheme> # scheme (http, ftp, mailto) + [a-zA-Z][a-zA-Z0-9.+-]* + ) + : + ( + ( # either: + (//?)? # hierarchical URI + %(uric)s* # URI characters + %(uri_end)s # final URI char + ) + ( # optional query + \?%(uric)s* + %(uri_end)s + )? + ( # optional fragment + \#%(uric)s* + %(uri_end)s + )? + ) + ) + | # *OR* + (?P<email> # email address + """ + self.email_pattern + r""" + ) + ) + %(end_string_suffix)s + """) % args, re.VERBOSE | re.UNICODE), + pep=re.compile( + r""" + %(start_string_prefix)s + ( + (pep-(?P<pepnum1>\d+)(.txt)?) # reference to source file + | + (PEP\s+(?P<pepnum2>\d+)) # reference by name + ) + %(end_string_suffix)s""" % args, re.VERBOSE | re.UNICODE), + rfc=re.compile( + r""" + %(start_string_prefix)s + (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`.""" - def init_customizations(self, settings): - """Setting-based customizations; run when parsing begins.""" if settings.pep_references: self.implicit_dispatch.append((self.patterns.pep, self.pep_reference)) @@ -527,14 +656,6 @@ class Inliner: # Inline object recognition # ------------------------- - # lookahead and look-behind expressions for inline markup rules - start_string_prefix = (u'(^|(?<=\\s|[%s%s]))' % - (punctuation_chars.openers, - punctuation_chars.delimiters)) - end_string_suffix = (u'($|(?=\\s|[\x00%s%s%s]))' % - (punctuation_chars.closing_delimiters, - punctuation_chars.delimiters, - punctuation_chars.closers)) # print start_string_prefix.encode('utf8') # TODO: support non-ASCII whitespace in the following 4 patterns? non_whitespace_before = r'(?<![ \n])' @@ -560,118 +681,6 @@ class Inliner: %(emailc)s+(?:\.%(emailc)s*)* # host %(uri_end)s # final URI char """ - parts = ('initial_inline', start_string_prefix, '', - [('start', '', non_whitespace_after, # simple start-strings - [r'\*\*', # strong - r'\*(?!\*)', # emphasis but not strong - r'``', # literal - r'_`', # inline internal target - r'\|(?!\|)'] # substitution reference - ), - ('whole', '', end_string_suffix, # whole constructs - [# reference name & end-string - r'(?P<refname>%s)(?P<refend>__?)' % simplename, - ('footnotelabel', r'\[', r'(?P<fnend>\]_)', - [r'[0-9]+', # manually numbered - r'\#(%s)?' % simplename, # auto-numbered (w/ label?) - r'\*', # auto-symbol - r'(?P<citationlabel>%s)' % simplename] # citation reference - ) - ] - ), - ('backquote', # interpreted text or phrase reference - '(?P<role>(:%s:)?)' % simplename, # optional role - non_whitespace_after, - ['`(?!`)'] # but not literal - ) - ] - ) - patterns = Struct( - initial=build_regexp(parts), - emphasis=re.compile(non_whitespace_escape_before - + r'(\*)' + end_string_suffix, re.UNICODE), - strong=re.compile(non_whitespace_escape_before - + r'(\*\*)' + end_string_suffix, re.UNICODE), - interpreted_or_phrase_ref=re.compile( - r""" - %(non_unescaped_whitespace_escape_before)s - ( - ` - (?P<suffix> - (?P<role>:%(simplename)s:)? - (?P<refend>__?)? - ) - ) - %(end_string_suffix)s - """ % locals(), re.VERBOSE | re.UNICODE), - embedded_link=re.compile( - r""" - ( - (?:[ \n]+|^) # spaces or beginning of line/string - < # open bracket - %(non_whitespace_after)s - ([^<>\x00]+(\x00_)?) # anything but angle brackets & nulls - # except escaped trailing low line - %(non_whitespace_before)s - > # close bracket w/o whitespace before - ) - $ # end of string - """ % locals(), re.VERBOSE | re.UNICODE), - literal=re.compile(non_whitespace_before + '(``)' - + end_string_suffix), - target=re.compile(non_whitespace_escape_before - + r'(`)' + end_string_suffix), - substitution_ref=re.compile(non_whitespace_escape_before - + r'(\|_{0,2})' - + end_string_suffix), - email=re.compile(email_pattern % locals() + '$', - re.VERBOSE | re.UNICODE), - uri=re.compile( - (r""" - %(start_string_prefix)s - (?P<whole> - (?P<absolute> # absolute URI - (?P<scheme> # scheme (http, ftp, mailto) - [a-zA-Z][a-zA-Z0-9.+-]* - ) - : - ( - ( # either: - (//?)? # hierarchical URI - %(uric)s* # URI characters - %(uri_end)s # final URI char - ) - ( # optional query - \?%(uric)s* - %(uri_end)s - )? - ( # optional fragment - \#%(uric)s* - %(uri_end)s - )? - ) - ) - | # *OR* - (?P<email> # email address - """ + email_pattern + r""" - ) - ) - %(end_string_suffix)s - """) % locals(), re.VERBOSE | re.UNICODE), - pep=re.compile( - r""" - %(start_string_prefix)s - ( - (pep-(?P<pepnum1>\d+)(.txt)?) # reference to source file - | - (PEP\s+(?P<pepnum2>\d+)) # reference by name - ) - %(end_string_suffix)s""" % locals(), re.VERBOSE | re.UNICODE), - rfc=re.compile( - r""" - %(start_string_prefix)s - (RFC(-|\s+)?(?P<rfcnum>\d+)) - %(end_string_suffix)s""" % locals(), re.VERBOSE | re.UNICODE)) def quoted_start(self, match): """Test if inline markup start-string is 'quoted'. @@ -787,8 +796,10 @@ class Inliner: match = self.patterns.embedded_link.search(escaped) if match: # embedded <URI> or <alias_> text = unescape(escaped[:match.start(0)]) - aliastext = unescape(match.group(2), restore_backslashes=True) - if aliastext.endswith('_') and not (aliastext.endswith(r'\_') + aliastext = match.group(2) + underscore_escaped = aliastext.endswith('\x00_') + aliastext = unescape(aliastext) + if aliastext.endswith('_') and not (underscore_escaped or self.patterns.uri.match(aliastext)): aliastype = 'name' alias = normalize_name(aliastext[:-1]) @@ -1230,6 +1241,8 @@ class Body(RSTState): def bullet(self, match, context, next_state): """Bullet list item.""" bulletlist = nodes.bullet_list() + (bulletlist.source, + bulletlist.line) = self.state_machine.get_source_and_line() self.parent += bulletlist bulletlist['bullet'] = match.string[0] i, blank_finish = self.list_item(match.end()) @@ -1460,6 +1473,7 @@ class Body(RSTState): def option_marker(self, match, context, next_state): """Option list item.""" optionlist = nodes.option_list() + (optionlist.source, optionlist.line) = self.state_machine.get_source_and_line() try: listitem, blank_finish = self.option_list_item(match) except MarkupError, error: @@ -1547,6 +1561,9 @@ class Body(RSTState): def doctest(self, match, context, next_state): data = '\n'.join(self.state_machine.get_text_block()) + # TODO: prepend class value ['pycon'] (Python Console) + # parse with `directives.body.CodeBlock` (returns literal-block + # with class "code" and syntax highlight markup). self.parent += nodes.doctest_block(data, data) return [], next_state, [] @@ -1747,9 +1764,11 @@ class Body(RSTState): line=startline+offset) return [error] - def build_table(self, tabledata, tableline, stub_columns=0): + 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] tgroup = nodes.tgroup(cols=len(colwidths)) table += tgroup for colwidth in colwidths: diff --git a/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py b/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py index 2cd3879..e19388b 100644 --- a/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py +++ b/docutils/src/main/resources/docutils/docutils/parsers/rst/tableparser.py @@ -1,4 +1,4 @@ -# $Id: tableparser.py 7320 2012-01-19 22:33:02Z milde $ +# $Id: tableparser.py 7898 2015-05-29 20:49:28Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -511,8 +511,8 @@ class SimpleTableParser(TableParser): if i == lastcol and line[end:].strip(): text = line[start:].rstrip() new_end = start + len(text) - columns[i] = (start, new_end) main_start, main_end = self.columns[-1] + columns[i] = (start, max(main_end, new_end)) if new_end > main_end: self.columns[-1] = (main_start, new_end) elif line[end:nextstart].strip(): diff --git a/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py b/docutils/src/main/resources/docutils/docutils/transforms/frontmatter.py index b39ce11..8bfc64f 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 7595 2013-01-21 17:33:56Z milde $ +# $Id: frontmatter.py 7897 2015-05-29 11:48:20Z 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.fully_normalize_name(name) + normedname = nodes.make_id(name) if not (len(field) == 2 and normedname in bibliofields and self.check_empty_biblio_field(field, name)): raise TransformError @@ -433,6 +433,8 @@ 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) 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 821cbcc..519a707 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 6433 2010-09-28 08:21:25Z milde $ +# $Id: peps.py 7773 2014-10-08 15:08:29Z grubert $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -32,8 +32,8 @@ class Headers(Transform): default_priority = 360 pep_url = 'pep-%04d' - pep_cvs_url = ('http://svn.python.org/view/*checkout*' - '/peps/trunk/pep-%04d.txt') + pep_cvs_url = ('http://hg.python.org' + '/peps/file/default/pep-%04d.txt') rcs_keyword_substitutions = ( (re.compile(r'\$' r'RCSfile: (.+),v \$$', re.IGNORECASE), r'\1'), (re.compile(r'\$[a-zA-Z]+: (.+) \$$'), r'\1'),) diff --git a/docutils/src/main/resources/docutils/docutils/transforms/universal.py b/docutils/src/main/resources/docutils/docutils/transforms/universal.py index 3f3b55f..c485d72 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 7668 2013-06-04 12:46:30Z milde $ +# $Id: universal.py 7957 2016-07-28 20:59:14Z milde $ # -*- coding: utf-8 -*- # Authors: David Goodger <goodger@python.org>; Ueli Schlaepfer; Günter Milde # Maintainer: docutils-develop@lists.sourceforge.net @@ -49,6 +49,10 @@ class Decorations(Transform): def generate_footer(self): # @@@ Text is hard-coded for now. # Should be made dynamic (language-dependent). + # @@@ Use timestamp from the `SOURCE_DATE_EPOCH`_ environment variable + # for the datestamp? + # See https://sourceforge.net/p/docutils/patches/132/ + # and https://reproducible-builds.org/specs/source-date-epoch/ settings = self.document.settings if settings.generator or settings.datestamp or settings.source_link \ or settings.source_url: diff --git a/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py b/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py index ab26b0b..c5818d9 100644 --- a/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py +++ b/docutils/src/main/resources/docutils/docutils/transforms/writer_aux.py @@ -1,4 +1,4 @@ -# $Id: writer_aux.py 7320 2012-01-19 22:33:02Z milde $ +# $Id: writer_aux.py 7808 2015-02-27 17:03:32Z milde $ # Author: Lea Wiemann <LeWiemann@gmail.com> # Copyright: This module has been placed in the public domain. 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 be23a19..c0f3109 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: 2011-12-20 15:14:21 +0100 (Die, 20. Dez 2011) $ +# :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 @@ -13,7 +13,7 @@ try: from pygments.lexers import get_lexer_by_name from pygments.formatters.html import _get_ttype_class with_pygments = True -except ImportError: +except (ImportError, SyntaxError): # pygments 2.0.1 fails with Py 3.1 and 3.2 with_pygments = False # Filter the following token types from the list of class arguments: 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 19348bf..a893d21 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 7668 2013-06-04 12:46:30Z milde $ +# :Id: $Id: error_reporting.py 7947 2016-07-22 08:49:52Z milde $ # :Copyright: © 2011 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -44,10 +44,18 @@ try: except ImportError: locale_encoding = None else: - locale_encoding = locale.getlocale()[1] or locale.getdefaultlocale()[1] - # locale.getpreferredencoding([do_setlocale=True|False]) - # has side-effects | might return a wrong guess. - # (cf. Update 1 in http://stackoverflow.com/questions/4082645/using-python-2-xs-locale-module-t...) + try: + locale_encoding = locale.getlocale()[1] or locale.getdefaultlocale()[1] + # locale.getpreferredencoding([do_setlocale=True|False]) + # has side-effects | might return a wrong guess. + # (cf. Update 1 in http://stackoverflow.com/questions/4082645/using-python-2-xs-locale-module-t...) + except ValueError, error: # OS X may set UTF-8 without language code + # see http://bugs.python.org/issue18378 + # and https://sourceforge.net/p/docutils/bugs/298/ + if "unknown locale: UTF-8" in error.args: + locale_encoding = "UTF-8" + except: # any other problems determining the locale -> use None + locale_encoding = None try: codecs.lookup(locale_encoding or '') # None -> '' except LookupError: @@ -72,7 +80,7 @@ class SafeString(object): def __str__(self): try: return str(self.data) - except UnicodeEncodeError, err: + except UnicodeEncodeError: if isinstance(self.data, Exception): args = [str(SafeString(arg, self.encoding, self.encoding_errors)) @@ -189,7 +197,11 @@ class ErrorOutput(object): self.stream.write(data) except UnicodeEncodeError: self.stream.write(data.encode(self.encoding, self.encoding_errors)) - except TypeError: # in Python 3, stderr expects unicode + except TypeError: + if isinstance(data, unicode): # passed stream may expect bytes + self.stream.write(data.encode(self.encoding, + self.encoding_errors)) + return if self.stream in (sys.stderr, sys.stdout): self.stream.buffer.write(data) # write bytes to raw stream else: diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py b/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py index 0508e4f..673f93e 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/__init__.py @@ -1,4 +1,4 @@ -# :Id: $Id: __init__.py 7218 2011-11-08 17:42:40Z milde $ +# :Id: $Id: __init__.py 7865 2015-04-12 10:06:43Z milde $ # :Author: Guenter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -17,8 +17,9 @@ It contains various modules for conversion between different math formats :math2html: LaTeX math -> HTML conversion from eLyXer :latex2mathml: LaTeX math -> presentational MathML -:unichar2tex: Unicode character to LaTeX math translation table -:tex2unichar: LaTeX math to Unicode character translation dictionaries +:unichar2tex: Unicode character to LaTeX math translation table +:tex2unichar: LaTeX math to Unicode character translation dictionaries +:tex2mathml_extern: Wrapper for TeX -> MathML command line converters """ # helpers for Docutils math support 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 7bbdbdd..9927e43 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 7668 2013-06-04 12:46:30Z milde $ +# :Id: $Id: latex2mathml.py 7865 2015-04-12 10:06:43Z milde $ # :Copyright: © 2010 Günter Milde. # Based on rst2mathml.py from the latex_math sandbox project # © 2005 Jens Jørgen Mortensen @@ -558,3 +558,14 @@ def handle_keyword(name, node, string): raise SyntaxError(u'Unknown LaTeX command: ' + name) return node, skip + +def tex2mathml(tex_math, inline=True): + """Return string with MathML code corresponding to `tex_math`. + + `inline`=True is for inline math and `inline`=False for displayed math. + """ + + mathml_tree = parse_latex_math(tex_math, inline=inline) + return ''.join(mathml_tree.xml()) + + 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 2d3149e..c65485b 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py +++ b/docutils/src/main/resources/docutils/docutils/utils/math/math2html.py @@ -14,7 +14,7 @@ # .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause # Based on eLyXer: convert LyX source files to HTML output. -# http://elyxer.nongnu.org/ +# http://alexfernandez.github.io/elyxer/ # --end-- # Alex 20101110 @@ -112,7 +112,7 @@ class BibStylesConfig(object): u'@conference':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', u'@inbook':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', u'@incollection':u'$authors: <i>$title</i>{ in <i>$booktitle</i>{ ($editor, ed.)}}.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', - u'@inproceedings':u'$authors: “$title”, <i>$journal</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', + u'@inproceedings':u'$authors: “$title”, <i>$booktitle</i>,{ pp. $pages,} $year.{ URL <a href="$url">$url</a>.}{ $note.}', u'@manual':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', u'@mastersthesis':u'$authors: <i>$title</i>.{{ $publisher,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', u'@misc':u'$authors: <i>$title</i>.{{ $publisher,}{ $howpublished,} $year.}{ URL <a href="$url">$url</a>.}{ $note.}', @@ -245,7 +245,8 @@ class ContainerConfig(object): u'\\begin_inset Quotes':u'QuoteContainer', u'\\begin_inset Tabular':u'Table', u'\\begin_inset Text':u'InsetText', u'\\begin_inset VSpace':u'VerticalSpace', u'\\begin_inset Wrap':u'Wrap', - u'\\begin_inset listings':u'Listing', u'\\begin_inset space':u'Space', + u'\\begin_inset listings':u'Listing', + u'\\begin_inset script':u'ScriptInset', u'\\begin_inset space':u'Space', u'\\begin_layout':u'Layout', u'\\begin_layout Abstract':u'Abstract', u'\\begin_layout Author':u'Author', u'\\begin_layout Bibliography':u'Bibliography', @@ -291,7 +292,7 @@ class EscapeConfig(object): "Configuration class from elyxer.config file" chars = { - u'\n':u'', u' -- ':u' — ', u'\'':u'’', u'---':u'—', u'`':u'‘', + u'\n':u'', u' -- ':u' — ', u' --- ':u' — ', u'\'':u'’', u'`':u'‘', } commands = { @@ -324,21 +325,24 @@ class FormulaConfig(object): alphacommands = { u'\\AA':u'Å', u'\\AE':u'Æ', - u'\\AmS':u'<span class="versalitas">AmS</span>', u'\\DH':u'Ð', - u'\\L':u'Ł', u'\\O':u'Ø', u'\\OE':u'Œ', u'\\TH':u'Þ', u'\\aa':u'å', - u'\\ae':u'æ', u'\\alpha':u'α', u'\\beta':u'β', u'\\delta':u'δ', - u'\\dh':u'ð', u'\\epsilon':u'ϵ', u'\\eta':u'η', u'\\gamma':u'γ', - u'\\i':u'ı', u'\\imath':u'ı', u'\\iota':u'ι', u'\\j':u'ȷ', - u'\\jmath':u'ȷ', u'\\kappa':u'κ', u'\\l':u'ł', u'\\lambda':u'λ', + u'\\AmS':u'<span class="versalitas">AmS</span>', u'\\Angstroem':u'Å', + u'\\DH':u'Ð', u'\\Koppa':u'Ϟ', u'\\L':u'Ł', u'\\Micro':u'µ', u'\\O':u'Ø', + u'\\OE':u'Œ', u'\\Sampi':u'Ϡ', u'\\Stigma':u'Ϛ', u'\\TH':u'Þ', + u'\\aa':u'å', u'\\ae':u'æ', u'\\alpha':u'α', u'\\beta':u'β', + u'\\delta':u'δ', u'\\dh':u'ð', u'\\digamma':u'ϝ', u'\\epsilon':u'ϵ', + u'\\eta':u'η', u'\\eth':u'ð', u'\\gamma':u'γ', u'\\i':u'ı', + u'\\imath':u'ı', u'\\iota':u'ι', u'\\j':u'ȷ', u'\\jmath':u'ȷ', + u'\\kappa':u'κ', u'\\koppa':u'ϟ', u'\\l':u'ł', u'\\lambda':u'λ', u'\\mu':u'μ', u'\\nu':u'ν', u'\\o':u'ø', u'\\oe':u'œ', u'\\omega':u'ω', u'\\phi':u'φ', u'\\pi':u'π', u'\\psi':u'ψ', u'\\rho':u'ρ', - u'\\sigma':u'σ', u'\\ss':u'ß', u'\\tau':u'τ', u'\\textcrh':u'ħ', - u'\\th':u'þ', u'\\theta':u'θ', u'\\upsilon':u'υ', u'\\varDelta':u'∆', + u'\\sampi':u'ϡ', u'\\sigma':u'σ', u'\\ss':u'ß', u'\\stigma':u'ϛ', + u'\\tau':u'τ', u'\\tcohm':u'Ω', u'\\textcrh':u'ħ', u'\\th':u'þ', + u'\\theta':u'θ', u'\\upsilon':u'υ', u'\\varDelta':u'∆', u'\\varGamma':u'Γ', u'\\varLambda':u'Λ', u'\\varOmega':u'Ω', u'\\varPhi':u'Φ', u'\\varPi':u'Π', u'\\varPsi':u'Ψ', u'\\varSigma':u'Σ', u'\\varTheta':u'Θ', u'\\varUpsilon':u'Υ', u'\\varXi':u'Ξ', - u'\\varepsilon':u'ε', u'\\varkappa':u'ϰ', u'\\varphi':u'φ', - u'\\varpi':u'ϖ', u'\\varrho':u'ϱ', u'\\varsigma':u'ς', + u'\\varbeta':u'ϐ', u'\\varepsilon':u'ε', u'\\varkappa':u'ϰ', + u'\\varphi':u'φ', u'\\varpi':u'ϖ', u'\\varrho':u'ϱ', u'\\varsigma':u'ς', u'\\vartheta':u'ϑ', u'\\xi':u'ξ', u'\\zeta':u'ζ', } @@ -376,59 +380,75 @@ class FormulaConfig(object): commands = { u'\\ ':u' ', u'\\!':u'', u'\\#':u'#', u'\\$':u'$', u'\\%':u'%', - u'\\&':u'&', u'\\,':u' ', u'\\:':u' ', u'\\;':u' ', - u'\\APLdownarrowbox':u'⍗', u'\\APLleftarrowbox':u'⍇', + u'\\&':u'&', u'\\,':u' ', u'\\:':u' ', u'\\;':u' ', u'\\AC':u'∿', + u'\\APLcomment':u'⍝', u'\\APLdownarrowbox':u'⍗', u'\\APLinput':u'⍞', + u'\\APLinv':u'⌹', u'\\APLleftarrowbox':u'⍇', u'\\APLlog':u'⍟', u'\\APLrightarrowbox':u'⍈', u'\\APLuparrowbox':u'⍐', u'\\Box':u'□', - u'\\Bumpeq':u'≎', u'\\CIRCLE':u'●', u'\\Cap':u'⋒', u'\\CheckedBox':u'☑', - u'\\Circle':u'○', u'\\Coloneqq':u'⩴', u'\\Corresponds':u'≙', - u'\\Cup':u'⋓', u'\\Delta':u'Δ', u'\\Diamond':u'◇', u'\\Downarrow':u'⇓', - u'\\EUR':u'€', u'\\Game':u'⅁', u'\\Gamma':u'Γ', u'\\Im':u'ℑ', - u'\\Join':u'⨝', u'\\LEFTCIRCLE':u'◖', u'\\LEFTcircle':u'◐', - u'\\Lambda':u'Λ', u'\\Leftarrow':u'⇐', u'\\Lleftarrow':u'⇚', - u'\\Longleftarrow':u'⟸', u'\\Longleftrightarrow':u'⟺', - u'\\Longrightarrow':u'⟹', u'\\Lsh':u'↰', u'\\Mapsfrom':u'⇐|', - u'\\Mapsto':u'|⇒', u'\\Omega':u'Ω', u'\\P':u'¶', u'\\Phi':u'Φ', - u'\\Pi':u'Π', u'\\Pr':u'Pr', u'\\Psi':u'Ψ', u'\\RIGHTCIRCLE':u'◗', - u'\\RIGHTcircle':u'◑', u'\\Re':u'ℜ', u'\\Rrightarrow':u'⇛', - u'\\Rsh':u'↱', u'\\S':u'§', u'\\Sigma':u'Σ', u'\\Square':u'☐', - u'\\Subset':u'⋐', u'\\Supset':u'⋑', u'\\Theta':u'Θ', u'\\Uparrow':u'⇑', - u'\\Updownarrow':u'⇕', u'\\Upsilon':u'Υ', u'\\Vdash':u'⊩', - u'\\Vert':u'∥', u'\\Vvdash':u'⊪', u'\\XBox':u'☒', u'\\Xi':u'Ξ', - u'\\Yup':u'⅄', u'\\\\':u'<br/>', u'\\_':u'_', u'\\aleph':u'ℵ', - u'\\amalg':u'∐', u'\\angle':u'∠', u'\\aquarius':u'♒', - u'\\arccos':u'arccos', u'\\arcsin':u'arcsin', u'\\arctan':u'arctan', - u'\\arg':u'arg', u'\\aries':u'♈', u'\\ast':u'∗', u'\\asymp':u'≍', + u'\\Bumpeq':u'≎', u'\\CIRCLE':u'●', u'\\Cap':u'⋒', + u'\\CapitalDifferentialD':u'ⅅ', u'\\CheckedBox':u'☑', u'\\Circle':u'○', + u'\\Coloneqq':u'⩴', u'\\ComplexI':u'ⅈ', u'\\ComplexJ':u'ⅉ', + u'\\Corresponds':u'≙', u'\\Cup':u'⋓', u'\\Delta':u'Δ', u'\\Diamond':u'◇', + u'\\Diamondblack':u'◆', u'\\Diamonddot':u'⟐', u'\\DifferentialD':u'ⅆ', + u'\\Downarrow':u'⇓', u'\\EUR':u'€', u'\\Euler':u'ℇ', + u'\\ExponetialE':u'ⅇ', u'\\Finv':u'Ⅎ', u'\\Game':u'⅁', u'\\Gamma':u'Γ', + u'\\Im':u'ℑ', u'\\Join':u'⨝', u'\\LEFTCIRCLE':u'◖', u'\\LEFTcircle':u'◐', + u'\\LHD':u'◀', u'\\Lambda':u'Λ', u'\\Lbag':u'⟅', u'\\Leftarrow':u'⇐', + u'\\Lleftarrow':u'⇚', u'\\Longleftarrow':u'⟸', + u'\\Longleftrightarrow':u'⟺', u'\\Longrightarrow':u'⟹', u'\\Lparen':u'⦅', + u'\\Lsh':u'↰', u'\\Mapsfrom':u'⇐|', u'\\Mapsto':u'|⇒', u'\\Omega':u'Ω', + u'\\P':u'¶', u'\\Phi':u'Φ', u'\\Pi':u'Π', u'\\Pr':u'Pr', u'\\Psi':u'Ψ', + u'\\Qoppa':u'Ϙ', u'\\RHD':u'▶', u'\\RIGHTCIRCLE':u'◗', + u'\\RIGHTcircle':u'◑', u'\\Rbag':u'⟆', u'\\Re':u'ℜ', u'\\Rparen':u'⦆', + u'\\Rrightarrow':u'⇛', u'\\Rsh':u'↱', u'\\S':u'§', u'\\Sigma':u'Σ', + u'\\Square':u'☐', u'\\Subset':u'⋐', u'\\Sun':u'☉', u'\\Supset':u'⋑', + u'\\Theta':u'Θ', u'\\Uparrow':u'⇑', u'\\Updownarrow':u'⇕', + u'\\Upsilon':u'Υ', u'\\Vdash':u'⊩', u'\\Vert':u'∥', u'\\Vvdash':u'⊪', + u'\\XBox':u'☒', u'\\Xi':u'Ξ', u'\\Yup':u'⅄', u'\\\\':u'<br/>', + u'\\_':u'_', u'\\aleph':u'ℵ', u'\\amalg':u'∐', u'\\anchor':u'⚓', + u'\\angle':u'∠', u'\\aquarius':u'♒', u'\\arccos':u'arccos', + u'\\arcsin':u'arcsin', u'\\arctan':u'arctan', u'\\arg':u'arg', + u'\\aries':u'♈', u'\\arrowbullet':u'➢', u'\\ast':u'∗', u'\\asymp':u'≍', u'\\backepsilon':u'∍', u'\\backprime':u'‵', u'\\backsimeq':u'⋍', - u'\\backslash':u'\\', u'\\barwedge':u'⊼', u'\\because':u'∵', - u'\\beth':u'ℶ', u'\\between':u'≬', u'\\bigcap':u'∩', u'\\bigcirc':u'○', - u'\\bigcup':u'∪', u'\\bigodot':u'⊙', u'\\bigoplus':u'⊕', - u'\\bigotimes':u'⊗', u'\\bigsqcup':u'⊔', u'\\bigstar':u'★', - u'\\bigtriangledown':u'▽', u'\\bigtriangleup':u'△', u'\\biguplus':u'⊎', - u'\\bigvee':u'∨', u'\\bigwedge':u'∧', u'\\blacklozenge':u'⧫', - u'\\blacksmiley':u'☻', u'\\blacksquare':u'■', u'\\blacktriangle':u'▲', - u'\\blacktriangledown':u'▼', u'\\blacktriangleright':u'▶', u'\\bot':u'⊥', - u'\\bowtie':u'⋈', u'\\box':u'▫', u'\\boxdot':u'⊡', u'\\bullet':u'•', + u'\\backslash':u'\\', u'\\ballotx':u'✗', u'\\barwedge':u'⊼', + u'\\because':u'∵', u'\\beth':u'ℶ', u'\\between':u'≬', u'\\bigcap':u'∩', + u'\\bigcirc':u'○', u'\\bigcup':u'∪', u'\\bigodot':u'⊙', + u'\\bigoplus':u'⊕', u'\\bigotimes':u'⊗', u'\\bigsqcup':u'⊔', + u'\\bigstar':u'★', u'\\bigtriangledown':u'▽', u'\\bigtriangleup':u'△', + u'\\biguplus':u'⊎', u'\\bigvee':u'∨', u'\\bigwedge':u'∧', + u'\\biohazard':u'☣', u'\\blacklozenge':u'⧫', u'\\blacksmiley':u'☻', + u'\\blacksquare':u'■', u'\\blacktriangle':u'▲', + u'\\blacktriangledown':u'▼', u'\\blacktriangleleft':u'◂', + u'\\blacktriangleright':u'▶', u'\\blacktriangleup':u'▴', u'\\bot':u'⊥', + u'\\bowtie':u'⋈', u'\\box':u'▫', u'\\boxast':u'⧆', u'\\boxbar':u'◫', + u'\\boxbox':u'⧈', u'\\boxbslash':u'⧅', u'\\boxcircle':u'⧇', + u'\\boxdot':u'⊡', u'\\boxminus':u'⊟', u'\\boxplus':u'⊞', + u'\\boxslash':u'⧄', u'\\boxtimes':u'⊠', u'\\bullet':u'•', u'\\bumpeq':u'≏', u'\\cancer':u'♋', u'\\cap':u'∩', u'\\capricornus':u'♑', - u'\\cdot':u'⋅', u'\\cdots':u'⋯', u'\\centerdot':u'∙', - u'\\checkmark':u'✓', u'\\chi':u'χ', u'\\circ':u'○', u'\\circeq':u'≗', - u'\\circledR':u'®', u'\\circledast':u'⊛', u'\\circledcirc':u'⊚', - u'\\circleddash':u'⊝', u'\\clubsuit':u'♣', u'\\coloneqq':u'≔', + u'\\cat':u'⁀', u'\\cdot':u'⋅', u'\\cdots':u'⋯', u'\\cent':u'¢', + u'\\centerdot':u'∙', u'\\checkmark':u'✓', u'\\chi':u'χ', u'\\circ':u'∘', + u'\\circeq':u'≗', u'\\circlearrowleft':u'↺', u'\\circlearrowright':u'↻', + u'\\circledR':u'®', u'\\circledast':u'⊛', u'\\circledbslash':u'⦸', + u'\\circledcirc':u'⊚', u'\\circleddash':u'⊝', u'\\circledgtr':u'⧁', + u'\\circledless':u'⧀', u'\\clubsuit':u'♣', u'\\colon':u': ', u'\\coloneqq':u'≔', u'\\complement':u'∁', u'\\cong':u'≅', u'\\coprod':u'∐', u'\\copyright':u'©', u'\\cos':u'cos', u'\\cosh':u'cosh', u'\\cot':u'cot', - u'\\coth':u'coth', u'\\csc':u'csc', u'\\cup':u'∪', - u'\\curvearrowleft':u'↶', u'\\curvearrowright':u'↷', u'\\dag':u'†', - u'\\dagger':u'†', u'\\daleth':u'ℸ', u'\\dashleftarrow':u'⇠', - u'\\dashv':u'⊣', u'\\ddag':u'‡', u'\\ddagger':u'‡', u'\\ddots':u'⋱', - u'\\deg':u'deg', u'\\det':u'det', u'\\diagdown':u'╲', u'\\diagup':u'╱', - u'\\diamond':u'◇', u'\\diamondsuit':u'♦', u'\\dim':u'dim', u'\\div':u'÷', - u'\\divideontimes':u'⋇', u'\\dotdiv':u'∸', u'\\doteq':u'≐', - u'\\doteqdot':u'≑', u'\\dotplus':u'∔', u'\\dots':u'…', - u'\\doublebarwedge':u'⌆', u'\\downarrow':u'↓', u'\\downdownarrows':u'⇊', - u'\\downharpoonleft':u'⇃', u'\\downharpoonright':u'⇂', u'\\earth':u'♁', - u'\\ell':u'ℓ', u'\\emptyset':u'∅', u'\\eqcirc':u'≖', u'\\eqcolon':u'≕', - u'\\eqsim':u'≂', u'\\euro':u'€', u'\\exists':u'∃', u'\\exp':u'exp', - u'\\fallingdotseq':u'≒', u'\\female':u'♀', u'\\flat':u'♭', - u'\\forall':u'∀', u'\\frown':u'⌢', u'\\frownie':u'☹', u'\\gcd':u'gcd', + u'\\coth':u'coth', u'\\csc':u'csc', u'\\cup':u'∪', u'\\curlyvee':u'⋎', + u'\\curlywedge':u'⋏', u'\\curvearrowleft':u'↶', + u'\\curvearrowright':u'↷', u'\\dag':u'†', u'\\dagger':u'†', + u'\\daleth':u'ℸ', u'\\dashleftarrow':u'⇠', u'\\dashv':u'⊣', + u'\\ddag':u'‡', u'\\ddagger':u'‡', u'\\ddots':u'⋱', u'\\deg':u'deg', + u'\\det':u'det', u'\\diagdown':u'╲', u'\\diagup':u'╱', + u'\\diameter':u'⌀', u'\\diamond':u'◇', u'\\diamondsuit':u'♦', + u'\\dim':u'dim', u'\\div':u'÷', u'\\divideontimes':u'⋇', + u'\\dotdiv':u'∸', u'\\doteq':u'≐', u'\\doteqdot':u'≑', u'\\dotplus':u'∔', + u'\\dots':u'…', u'\\doublebarwedge':u'⌆', u'\\downarrow':u'↓', + u'\\downdownarrows':u'⇊', u'\\downharpoonleft':u'⇃', + u'\\downharpoonright':u'⇂', u'\\dsub':u'⩤', u'\\earth':u'♁', + u'\\eighthnote':u'♪', u'\\ell':u'ℓ', u'\\emptyset':u'∅', + u'\\eqcirc':u'≖', u'\\eqcolon':u'≕', u'\\eqsim':u'≂', u'\\euro':u'€', + u'\\exists':u'∃', u'\\exp':u'exp', u'\\fallingdotseq':u'≒', + u'\\fcmp':u'⨾', u'\\female':u'♀', u'\\flat':u'♭', u'\\forall':u'∀', + u'\\fourth':u'⁗', u'\\frown':u'⌢', u'\\frownie':u'☹', u'\\gcd':u'gcd', u'\\gemini':u'♊', u'\\geq)':u'≥', u'\\geqq':u'≧', u'\\geqslant':u'≥', u'\\gets':u'←', u'\\gg':u'≫', u'\\ggg':u'⋙', u'\\gimel':u'ℷ', u'\\gneqq':u'≩', u'\\gnsim':u'⋧', u'\\gtrdot':u'⋗', u'\\gtreqless':u'⋚', @@ -439,41 +459,44 @@ class FormulaConfig(object): u'\\hslash':u'ℏ', u'\\idotsint':u'<span class="bigsymbol">∫⋯∫</span>', u'\\iiint':u'<span class="bigsymbol">∭</span>', u'\\iint':u'<span class="bigsymbol">∬</span>', u'\\imath':u'ı', - u'\\inf':u'inf', u'\\infty':u'∞', u'\\invneg':u'⌐', u'\\jmath':u'ȷ', - u'\\jupiter':u'♃', u'\\ker':u'ker', u'\\land':u'∧', - u'\\landupint':u'<span class="bigsymbol">∱</span>', u'\\langle':u'⟨', - u'\\lbrace':u'{', u'\\lbrace)':u'{', u'\\lbrack':u'[', u'\\lceil':u'⌈', - u'\\ldots':u'…', u'\\leadsto':u'⇝', u'\\leftarrow)':u'←', - u'\\leftarrowtail':u'↢', u'\\leftarrowtobar':u'⇤', + u'\\inf':u'inf', u'\\infty':u'∞', u'\\intercal':u'⊺', + u'\\interleave':u'⫴', u'\\invamp':u'⅋', u'\\invneg':u'⌐', + u'\\jmath':u'ȷ', u'\\jupiter':u'♃', u'\\ker':u'ker', u'\\land':u'∧', + u'\\landupint':u'<span class="bigsymbol">∱</span>', u'\\lang':u'⟪', + u'\\langle':u'⟨', u'\\lblot':u'⦉', u'\\lbrace':u'{', u'\\lbrace)':u'{', + u'\\lbrack':u'[', u'\\lceil':u'⌈', u'\\ldots':u'…', u'\\leadsto':u'⇝', + u'\\leftarrow)':u'←', u'\\leftarrowtail':u'↢', u'\\leftarrowtobar':u'⇤', u'\\leftharpoondown':u'↽', u'\\leftharpoonup':u'↼', u'\\leftleftarrows':u'⇇', u'\\leftleftharpoons':u'⥢', u'\\leftmoon':u'☾', u'\\leftrightarrow':u'↔', u'\\leftrightarrows':u'⇆', u'\\leftrightharpoons':u'⇋', u'\\leftthreetimes':u'⋋', u'\\leo':u'♌', u'\\leq)':u'≤', u'\\leqq':u'≦', u'\\leqslant':u'≤', u'\\lessdot':u'⋖', u'\\lesseqgtr':u'⋛', u'\\lesseqqgtr':u'⪋', u'\\lessgtr':u'≶', - u'\\lesssim':u'≲', u'\\lfloor':u'⌊', u'\\lg':u'lg', u'\\lhd':u'⊲', - u'\\libra':u'♎', u'\\lightning':u'↯', u'\\liminf':u'liminf', - u'\\limsup':u'limsup', u'\\ll':u'≪', u'\\lll':u'⋘', u'\\ln':u'ln', + u'\\lesssim':u'≲', u'\\lfloor':u'⌊', u'\\lg':u'lg', u'\\lgroup':u'⟮', + u'\\lhd':u'⊲', u'\\libra':u'♎', u'\\lightning':u'↯', u'\\limg':u'⦇', + u'\\liminf':u'liminf', u'\\limsup':u'limsup', u'\\ll':u'≪', + u'\\llbracket':u'⟦', u'\\llcorner':u'⌞', u'\\lll':u'⋘', u'\\ln':u'ln', u'\\lneqq':u'≨', u'\\lnot':u'¬', u'\\lnsim':u'⋦', u'\\log':u'log', u'\\longleftarrow':u'⟵', u'\\longleftrightarrow':u'⟷', u'\\longmapsto':u'⟼', u'\\longrightarrow':u'⟶', u'\\looparrowleft':u'↫', u'\\looparrowright':u'↬', u'\\lor':u'∨', u'\\lozenge':u'◊', - u'\\ltimes':u'⋉', u'\\lyxlock':u'', u'\\male':u'♂', u'\\maltese':u'✠', - u'\\mapsfrom':u'↤', u'\\mapsto':u'↦', u'\\mathcircumflex':u'^', - u'\\max':u'max', u'\\measuredangle':u'∡', u'\\mercury':u'☿', - u'\\mho':u'℧', u'\\mid':u'∣', u'\\min':u'min', u'\\models':u'⊨', - u'\\mp':u'∓', u'\\multimap':u'⊸', u'\\nLeftarrow':u'⇍', - u'\\nLeftrightarrow':u'⇎', u'\\nRightarrow':u'⇏', u'\\nVDash':u'⊯', - u'\\nabla':u'∇', u'\\napprox':u'≉', u'\\natural':u'♮', u'\\ncong':u'≇', - u'\\nearrow':u'↗', u'\\neg':u'¬', u'\\neg)':u'¬', u'\\neptune':u'♆', - u'\\nequiv':u'≢', u'\\newline':u'<br/>', u'\\nexists':u'∄', - u'\\ngeqslant':u'≱', u'\\ngtr':u'≯', u'\\ngtrless':u'≹', u'\\ni':u'∋', - u'\\ni)':u'∋', u'\\nleftarrow':u'↚', u'\\nleftrightarrow':u'↮', - u'\\nleqslant':u'≰', u'\\nless':u'≮', u'\\nlessgtr':u'≸', u'\\nmid':u'∤', - u'\\nolimits':u'', u'\\nonumber':u'', u'\\not':u'¬', u'\\not<':u'≮', - u'\\not=':u'≠', u'\\not>':u'≯', u'\\notbackslash':u'⍀', u'\\notin':u'∉', - u'\\notni':u'∌', u'\\notslash':u'⌿', u'\\nparallel':u'∦', - u'\\nprec':u'⊀', u'\\nrightarrow':u'↛', u'\\nsim':u'≁', u'\\nsimeq':u'≄', + u'\\lrcorner':u'⌟', u'\\ltimes':u'⋉', u'\\lyxlock':u'', u'\\male':u'♂', + u'\\maltese':u'✠', u'\\mapsfrom':u'↤', u'\\mapsto':u'↦', + u'\\mathcircumflex':u'^', u'\\max':u'max', u'\\measuredangle':u'∡', + u'\\medbullet':u'⚫', u'\\medcirc':u'⚪', u'\\mercury':u'☿', u'\\mho':u'℧', + u'\\mid':u'∣', u'\\min':u'min', u'\\models':u'⊨', u'\\mp':u'∓', + u'\\multimap':u'⊸', u'\\nLeftarrow':u'⇍', u'\\nLeftrightarrow':u'⇎', + u'\\nRightarrow':u'⇏', u'\\nVDash':u'⊯', u'\\nabla':u'∇', + u'\\napprox':u'≉', u'\\natural':u'♮', u'\\ncong':u'≇', u'\\nearrow':u'↗', + u'\\neg':u'¬', u'\\neg)':u'¬', u'\\neptune':u'♆', u'\\nequiv':u'≢', + u'\\newline':u'<br/>', u'\\nexists':u'∄', u'\\ngeqslant':u'≱', + u'\\ngtr':u'≯', u'\\ngtrless':u'≹', u'\\ni':u'∋', u'\\ni)':u'∋', + u'\\nleftarrow':u'↚', u'\\nleftrightarrow':u'↮', u'\\nleqslant':u'≰', + u'\\nless':u'≮', u'\\nlessgtr':u'≸', u'\\nmid':u'∤', u'\\nolimits':u'', + u'\\nonumber':u'', u'\\not':u'¬', u'\\not<':u'≮', u'\\not=':u'≠', + u'\\not>':u'≯', u'\\notbackslash':u'⍀', u'\\notin':u'∉', u'\\notni':u'∌', + u'\\notslash':u'⌿', u'\\nparallel':u'∦', u'\\nprec':u'⊀', + u'\\nrightarrow':u'↛', u'\\nsim':u'≁', u'\\nsimeq':u'≄', u'\\nsqsubset':u'⊏̸', u'\\nsubseteq':u'⊈', u'\\nsucc':u'⊁', u'\\nsucccurlyeq':u'⋡', u'\\nsupset':u'⊅', u'\\nsupseteq':u'⊉', u'\\ntriangleleft':u'⋪', u'\\ntrianglelefteq':u'⋬', @@ -485,31 +508,40 @@ class FormulaConfig(object): u'\\ointclockwise':u'<span class="bigsymbol">∲</span>', u'\\ointctrclockwise':u'<span class="bigsymbol">∳</span>', u'\\ominus':u'⊖', u'\\oplus':u'⊕', u'\\oslash':u'⊘', u'\\otimes':u'⊗', - u'\\owns':u'∋', u'\\parallel':u'∥', u'\\partial':u'∂', u'\\perp':u'⊥', - u'\\pisces':u'♓', u'\\pitchfork':u'⋔', u'\\pluto':u'♇', u'\\pm':u'±', - u'\\pointer':u'➪', u'\\pounds':u'£', u'\\prec':u'≺', - u'\\preccurlyeq':u'≼', u'\\preceq':u'≼', u'\\precsim':u'≾', - u'\\prime':u'′', u'\\prompto':u'∝', u'\\qquad':u' ', u'\\quad':u' ', - u'\\quarternote':u'♩', u'\\rangle':u'⟩', u'\\rbrace':u'}', - u'\\rbrace)':u'}', u'\\rbrack':u']', u'\\rceil':u'⌉', u'\\rfloor':u'⌋', - u'\\rhd':u'⊳', u'\\rightarrow)':u'→', u'\\rightarrowtail':u'↣', + u'\\owns':u'∋', u'\\parallel':u'∥', u'\\partial':u'∂', u'\\pencil':u'✎', + u'\\perp':u'⊥', u'\\pisces':u'♓', u'\\pitchfork':u'⋔', u'\\pluto':u'♇', + u'\\pm':u'±', u'\\pointer':u'➪', u'\\pointright':u'☞', u'\\pounds':u'£', + u'\\prec':u'≺', u'\\preccurlyeq':u'≼', u'\\preceq':u'≼', + u'\\precsim':u'≾', u'\\prime':u'′', u'\\prompto':u'∝', u'\\qoppa':u'ϙ', + u'\\qquad':u' ', u'\\quad':u' ', u'\\quarternote':u'♩', + u'\\radiation':u'☢', u'\\rang':u'⟫', u'\\rangle':u'⟩', u'\\rblot':u'⦊', + u'\\rbrace':u'}', u'\\rbrace)':u'}', u'\\rbrack':u']', u'\\rceil':u'⌉', + u'\\recycle':u'♻', u'\\rfloor':u'⌋', u'\\rgroup':u'⟯', u'\\rhd':u'⊳', + u'\\rightangle':u'∟', u'\\rightarrow)':u'→', u'\\rightarrowtail':u'↣', u'\\rightarrowtobar':u'⇥', u'\\rightharpoondown':u'⇁', u'\\rightharpoonup':u'⇀', u'\\rightharpooondown':u'⇁', u'\\rightharpooonup':u'⇀', u'\\rightleftarrows':u'⇄', u'\\rightleftharpoons':u'⇌', u'\\rightmoon':u'☽', u'\\rightrightarrows':u'⇉', u'\\rightrightharpoons':u'⥤', - u'\\rightthreetimes':u'⋌', u'\\risingdotseq':u'≓', u'\\rtimes':u'⋊', + u'\\rightthreetimes':u'⋌', u'\\rimg':u'⦈', u'\\risingdotseq':u'≓', + u'\\rrbracket':u'⟧', u'\\rsub':u'⩥', u'\\rtimes':u'⋊', u'\\sagittarius':u'♐', u'\\saturn':u'♄', u'\\scorpio':u'♏', - u'\\searrow':u'↘', u'\\sec':u'sec', u'\\setminus':u'∖', u'\\sharp':u'♯', - u'\\simeq':u'≃', u'\\sin':u'sin', u'\\sinh':u'sinh', u'\\slash':u'∕', - u'\\smile':u'⌣', u'\\smiley':u'☺', u'\\spadesuit':u'♠', - u'\\sphericalangle':u'∢', u'\\sqcap':u'⊓', u'\\sqcup':u'⊔', - u'\\sqsubset':u'⊏', u'\\sqsubseteq':u'⊑', u'\\sqsupset':u'⊐', - u'\\sqsupseteq':u'⊒', u'\\square':u'□', u'\\star':u'⋆', + u'\\searrow':u'↘', u'\\sec':u'sec', u'\\second':u'″', u'\\setminus':u'∖', + u'\\sharp':u'♯', u'\\simeq':u'≃', u'\\sin':u'sin', u'\\sinh':u'sinh', + u'\\sixteenthnote':u'♬', u'\\skull':u'☠', u'\\slash':u'∕', + u'\\smallsetminus':u'∖', u'\\smalltriangledown':u'▿', + u'\\smalltriangleleft':u'◃', u'\\smalltriangleright':u'▹', + u'\\smalltriangleup':u'▵', u'\\smile':u'⌣', u'\\smiley':u'☺', + u'\\spadesuit':u'♠', u'\\spddot':u'¨', u'\\sphat':u'', + u'\\sphericalangle':u'∢', u'\\spot':u'⦁', u'\\sptilde':u'~', + u'\\sqcap':u'⊓', u'\\sqcup':u'⊔', u'\\sqsubset':u'⊏', + u'\\sqsubseteq':u'⊑', u'\\sqsupset':u'⊐', u'\\sqsupseteq':u'⊒', + u'\\square':u'□', u'\\sslash':u'⫽', u'\\star':u'⋆', u'\\steaming':u'☕', u'\\subseteqq':u'⫅', u'\\subsetneqq':u'⫋', u'\\succ':u'≻', u'\\succcurlyeq':u'≽', u'\\succeq':u'≽', u'\\succnsim':u'⋩', u'\\succsim':u'≿', u'\\sun':u'☼', u'\\sup':u'sup', u'\\supseteqq':u'⫆', - u'\\supsetneqq':u'⫌', u'\\surd':u'√', u'\\swarrow':u'↙', u'\\tan':u'tan', + u'\\supsetneqq':u'⫌', u'\\surd':u'√', u'\\swarrow':u'↙', + u'\\swords':u'⚔', u'\\talloblong':u'⫾', u'\\tan':u'tan', u'\\tanh':u'tanh', u'\\taurus':u'♉', u'\\textasciicircum':u'^', u'\\textasciitilde':u'~', u'\\textbackslash':u'\\', u'\\textcopyright':u'©\'', u'\\textdegree':u'°', u'\\textellipsis':u'…', @@ -520,20 +552,21 @@ class FormulaConfig(object): u'\\textregistered':u'®', u'\\textrightarrow':u'→', u'\\textsection':u'§', u'\\texttrademark':u'™', u'\\texttwosuperior':u'²', u'\\textvisiblespace':u' ', - u'\\therefore':u'∴', u'\\top':u'⊤', u'\\triangle':u'△', + u'\\therefore':u'∴', u'\\third':u'‴', u'\\top':u'⊤', u'\\triangle':u'△', u'\\triangleleft':u'⊲', u'\\trianglelefteq':u'⊴', u'\\triangleq':u'≜', u'\\triangleright':u'▷', u'\\trianglerighteq':u'⊵', u'\\twoheadleftarrow':u'↞', u'\\twoheadrightarrow':u'↠', - u'\\twonotes':u'♫', u'\\udot':u'⊍', u'\\unlhd':u'⊴', u'\\unrhd':u'⊵', - u'\\unrhl':u'⊵', u'\\uparrow':u'↑', u'\\updownarrow':u'↕', - u'\\upharpoonleft':u'↿', u'\\upharpoonright':u'↾', u'\\uplus':u'⊎', - u'\\upuparrows':u'⇈', u'\\uranus':u'♅', u'\\vDash':u'⊨', - u'\\varclubsuit':u'♧', u'\\vardiamondsuit':u'♦', u'\\varheartsuit':u'♥', - u'\\varnothing':u'∅', u'\\varspadesuit':u'♤', u'\\vdash':u'⊢', - u'\\vdots':u'⋮', u'\\vee':u'∨', u'\\vee)':u'∨', u'\\veebar':u'⊻', - u'\\vert':u'∣', u'\\virgo':u'♍', u'\\wedge':u'∧', u'\\wedge)':u'∧', - u'\\wp':u'℘', u'\\wr':u'≀', u'\\yen':u'¥', u'\\{':u'{', u'\\|':u'∥', - u'\\}':u'}', + u'\\twonotes':u'♫', u'\\udot':u'⊍', u'\\ulcorner':u'⌜', u'\\unlhd':u'⊴', + u'\\unrhd':u'⊵', u'\\unrhl':u'⊵', u'\\uparrow':u'↑', + u'\\updownarrow':u'↕', u'\\upharpoonleft':u'↿', u'\\upharpoonright':u'↾', + u'\\uplus':u'⊎', u'\\upuparrows':u'⇈', u'\\uranus':u'♅', + u'\\urcorner':u'⌝', u'\\vDash':u'⊨', u'\\varclubsuit':u'♧', + u'\\vardiamondsuit':u'♦', u'\\varheartsuit':u'♥', u'\\varnothing':u'∅', + u'\\varspadesuit':u'♤', u'\\vdash':u'⊢', u'\\vdots':u'⋮', u'\\vee':u'∨', + u'\\vee)':u'∨', u'\\veebar':u'⊻', u'\\vert':u'∣', u'\\virgo':u'♍', + u'\\warning':u'⚠', u'\\wasylozenge':u'⌑', u'\\wedge':u'∧', + u'\\wedge)':u'∧', u'\\wp':u'℘', u'\\wr':u'≀', u'\\yen':u'¥', + u'\\yinyang':u'☯', u'\\{':u'{', u'\\|':u'∥', u'\\}':u'}', } decoratedcommand = { @@ -580,7 +613,9 @@ class FormulaConfig(object): } hybridfunctions = { - + u'\\addcontentsline':[u'{$p!}{$q!}{$r!}',u'f0{}',u'ignored',], + u'\\addtocontents':[u'{$p!}{$q!}',u'f0{}',u'ignored',], + u'\\backmatter':[u'',u'f0{}',u'ignored',], u'\\binom':[u'{$1}{$2}',u'f2{(}f0{f1{$1}f1{$2}}f2{)}',u'span class="binom"',u'span class="binomstack"',u'span class="bigsymbol"',], u'\\boxed':[u'{$1}',u'f0{$1}',u'span class="boxed"',], u'\\cfrac':[u'[$p!]{$1}{$2}',u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u'span class="fullfraction"',u'span class="numerator align-$p"',u'span class="denominator"',u'span class="ignored"',], @@ -589,15 +624,21 @@ class FormulaConfig(object): u'\\dbinom':[u'{$1}{$2}',u'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})',u'span class="binomial"',u'span class="binomrow"',u'span class="binomcell"',], u'\\dfrac':[u'{$1}{$2}',u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u'span class="fullfraction"',u'span class="numerator"',u'span class="denominator"',u'span class="ignored"',], u'\\displaystyle':[u'{$1}',u'f0{$1}',u'span class="displaystyle"',], + u'\\fancyfoot':[u'[$p!]{$q!}',u'f0{}',u'ignored',], + u'\\fancyhead':[u'[$p!]{$q!}',u'f0{}',u'ignored',], u'\\fbox':[u'{$1}',u'f0{$1}',u'span class="fbox"',], u'\\fboxrule':[u'{$p!}',u'f0{}',u'ignored',], u'\\fboxsep':[u'{$p!}',u'f0{}',u'ignored',], u'\\fcolorbox':[u'{$p!}{$q!}{$1}',u'f0{$1}',u'span class="boxed" style="border-color: $p; background: $q;"',], u'\\frac':[u'{$1}{$2}',u'f0{f3{(}f1{$1}f3{)/(}f2{$2}f3{)}}',u'span class="fraction"',u'span class="numerator"',u'span class="denominator"',u'span class="ignored"',], u'\\framebox':[u'[$p!][$q!]{$1}',u'f0{$1}',u'span class="framebox align-$q" style="width: $p;"',], + u'\\frontmatter':[u'',u'f0{}',u'ignored',], u'\\href':[u'[$o]{$u!}{$t!}',u'f0{$t}',u'a href="$u"',], u'\\hspace':[u'{$p!}',u'f0{ }',u'span class="hspace" style="width: $p;"',], u'\\leftroot':[u'{$p!}',u'f0{ }',u'span class="leftroot" style="width: $p;px"',], + u'\\mainmatter':[u'',u'f0{}',u'ignored',], + u'\\markboth':[u'{$p!}{$q!}',u'f0{}',u'ignored',], + u'\\markright':[u'{$p!}',u'f0{}',u'ignored',], u'\\nicefrac':[u'{$1}{$2}',u'f0{f1{$1}⁄f2{$2}}',u'span class="fraction"',u'sup class="numerator"',u'sub class="denominator"',u'span class="ignored"',], u'\\parbox':[u'[$p!]{$w!}{$1}',u'f0{1}',u'div class="Boxed" style="width: $w;"',], u'\\raisebox':[u'{$p!}{$1}',u'f0{$1.font}',u'span class="raisebox" style="vertical-align: $p;"',], @@ -610,6 +651,7 @@ class FormulaConfig(object): u'\\tbinom':[u'{$1}{$2}',u'(f0{f1{f2{$1}}f1{f2{ }}f1{f2{$2}}})',u'span class="binomial"',u'span class="binomrow"',u'span class="binomcell"',], u'\\textcolor':[u'{$p!}{$1}',u'f0{$1}',u'span style="color: $p;"',], u'\\textstyle':[u'{$1}',u'f0{$1}',u'span class="textstyle"',], + u'\\thispagestyle':[u'{$p!}',u'f0{}',u'ignored',], u'\\unit':[u'[$0]{$1}',u'$0f0{$1.font}',u'span class="unit"',], u'\\unitfrac':[u'[$0]{$1}{$2}',u'$0f0{f1{$1.font}⁄f2{$2.font}}',u'span class="fraction"',u'sup class="unit"',u'sub class="unit"',], u'\\uproot':[u'{$p!}',u'f0{ }',u'span class="uproot" style="width: $p;px"',], @@ -627,24 +669,24 @@ class FormulaConfig(object): } limitcommands = { - u'\\int':u'∫', u'\\intop':u'∫', u'\\lim':u'lim', u'\\prod':u'∏', - u'\\smallint':u'∫', u'\\sum':u'∑', + u'\\biginterleave':u'⫼', u'\\bigsqcap':u'⨅', u'\\fint':u'⨏', + u'\\iiiint':u'⨌', u'\\int':u'∫', u'\\intop':u'∫', u'\\lim':u'lim', + u'\\prod':u'∏', u'\\smallint':u'∫', u'\\sqint':u'⨖', u'\\sum':u'∑', + u'\\varointclockwise':u'∲', u'\\varprod':u'⨉', u'\\zcmp':u'⨟', + u'\\zhide':u'⧹', u'\\zpipe':u'⨠', u'\\zproject':u'⨡', } - # TODO: setting for simple enlarged vs. piecewise symbols - for key in (u'\\int', u'\\intop', u'\\prod', u'\\sum'): - limitcommands[key] = '<span class="symbol">%s</span>' % limitcommands[key] misccommands = { u'\\limits':u'LimitPreviousCommand', u'\\newcommand':u'MacroDefinition', u'\\renewcommand':u'MacroDefinition', u'\\setcounter':u'SetCounterFunction', u'\\tag':u'FormulaTag', - u'\\tag*':u'FormulaTag', + u'\\tag*':u'FormulaTag', u'\\today':u'TodayCommand', } modified = { - u'\n':u'', u' ':u'', u'$':u'', u'&':u' ', u'\'':u'’', u'+':u' + ', - u',':u', ', u'-':u' − ', u'/':u' ⁄ ', u'<':u' < ', u'=':u' = ', - u'>':u' > ', u'@':u'', u'~':u'', + u'\n':u'', u' ':u'', u'$':u'', u'&':u' ', u'\'':u'’', u'+':u' + ', + u',':u', ', u'-':u' − ', u'/':u' ⁄ ', u':':u' : ', u'<':u' < ', + u'=':u' = ', u'>':u' > ', u'@':u'', u'~':u'', } onefunctions = { @@ -664,13 +706,58 @@ class FormulaConfig(object): } spacedcommands = { - u'\\Leftrightarrow':u'⇔', u'\\Rightarrow':u'⇒', u'\\approx':u'≈', - u'\\dashrightarrow':u'⇢', u'\\equiv':u'≡', u'\\ge':u'≥', u'\\geq':u'≥', + u'\\Bot':u'⫫', u'\\Doteq':u'≑', u'\\DownArrowBar':u'⤓', + u'\\DownLeftTeeVector':u'⥞', u'\\DownLeftVectorBar':u'⥖', + u'\\DownRightTeeVector':u'⥟', u'\\DownRightVectorBar':u'⥗', + u'\\Equal':u'⩵', u'\\LeftArrowBar':u'⇤', u'\\LeftDownTeeVector':u'⥡', + u'\\LeftDownVectorBar':u'⥙', u'\\LeftTeeVector':u'⥚', + u'\\LeftTriangleBar':u'⧏', u'\\LeftUpTeeVector':u'⥠', + u'\\LeftUpVectorBar':u'⥘', u'\\LeftVectorBar':u'⥒', + u'\\Leftrightarrow':u'⇔', u'\\Longmapsfrom':u'⟽', u'\\Longmapsto':u'⟾', + u'\\MapsDown':u'↧', u'\\MapsUp':u'↥', u'\\Nearrow':u'⇗', + u'\\NestedGreaterGreater':u'⪢', u'\\NestedLessLess':u'⪡', + u'\\NotGreaterLess':u'≹', u'\\NotGreaterTilde':u'≵', + u'\\NotLessTilde':u'≴', u'\\Nwarrow':u'⇖', u'\\Proportion':u'∷', + u'\\RightArrowBar':u'⇥', u'\\RightDownTeeVector':u'⥝', + u'\\RightDownVectorBar':u'⥕', u'\\RightTeeVector':u'⥛', + u'\\RightTriangleBar':u'⧐', u'\\RightUpTeeVector':u'⥜', + u'\\RightUpVectorBar':u'⥔', u'\\RightVectorBar':u'⥓', + u'\\Rightarrow':u'⇒', u'\\Same':u'⩶', u'\\Searrow':u'⇘', + u'\\Swarrow':u'⇙', u'\\Top':u'⫪', u'\\UpArrowBar':u'⤒', u'\\VDash':u'⊫', + u'\\approx':u'≈', u'\\approxeq':u'≊', u'\\backsim':u'∽', u'\\barin':u'⋶', + u'\\barleftharpoon':u'⥫', u'\\barrightharpoon':u'⥭', u'\\bij':u'⤖', + u'\\coloneq':u'≔', u'\\corresponds':u'≙', u'\\curlyeqprec':u'⋞', + u'\\curlyeqsucc':u'⋟', u'\\dashrightarrow':u'⇢', u'\\dlsh':u'↲', + u'\\downdownharpoons':u'⥥', u'\\downuparrows':u'⇵', + u'\\downupharpoons':u'⥯', u'\\drsh':u'↳', u'\\eqslantgtr':u'⪖', + u'\\eqslantless':u'⪕', u'\\equiv':u'≡', u'\\ffun':u'⇻', u'\\finj':u'⤕', + u'\\ge':u'≥', u'\\geq':u'≥', u'\\ggcurly':u'⪼', u'\\gnapprox':u'⪊', + u'\\gneq':u'⪈', u'\\gtrapprox':u'⪆', u'\\hash':u'⋕', u'\\iddots':u'⋰', u'\\implies':u' ⇒ ', u'\\in':u'∈', u'\\le':u'≤', u'\\leftarrow':u'←', - u'\\leq':u'≤', u'\\ne':u'≠', u'\\neq':u'≠', u'\\not\\in':u'∉', - u'\\propto':u'∝', u'\\rightarrow':u'→', u'\\rightsquigarrow':u'⇝', - u'\\sim':u'~', u'\\subset':u'⊂', u'\\subseteq':u'⊆', u'\\supset':u'⊃', - u'\\supseteq':u'⊇', u'\\times':u'×', u'\\to':u'→', + u'\\leftarrowtriangle':u'⇽', u'\\leftbarharpoon':u'⥪', + u'\\leftrightarrowtriangle':u'⇿', u'\\leftrightharpoon':u'⥊', + u'\\leftrightharpoondown':u'⥐', u'\\leftrightharpoonup':u'⥎', + u'\\leftrightsquigarrow':u'↭', u'\\leftslice':u'⪦', + u'\\leftsquigarrow':u'⇜', u'\\leftupdownharpoon':u'⥑', u'\\leq':u'≤', + u'\\lessapprox':u'⪅', u'\\llcurly':u'⪻', u'\\lnapprox':u'⪉', + u'\\lneq':u'⪇', u'\\longmapsfrom':u'⟻', u'\\multimapboth':u'⧟', + u'\\multimapdotbothA':u'⊶', u'\\multimapdotbothB':u'⊷', + u'\\multimapinv':u'⟜', u'\\nVdash':u'⊮', u'\\ne':u'≠', u'\\neq':u'≠', + u'\\ngeq':u'≱', u'\\nleq':u'≰', u'\\nni':u'∌', u'\\not\\in':u'∉', + u'\\notasymp':u'≭', u'\\npreceq':u'⋠', u'\\nsqsubseteq':u'⋢', + u'\\nsqsupseteq':u'⋣', u'\\nsubset':u'⊄', u'\\nsucceq':u'⋡', + u'\\pfun':u'⇸', u'\\pinj':u'⤔', u'\\precapprox':u'⪷', u'\\preceqq':u'⪳', + u'\\precnapprox':u'⪹', u'\\precnsim':u'⋨', u'\\propto':u'∝', + u'\\psur':u'⤀', u'\\rightarrow':u'→', u'\\rightarrowtriangle':u'⇾', + u'\\rightbarharpoon':u'⥬', u'\\rightleftharpoon':u'⥋', + u'\\rightslice':u'⪧', u'\\rightsquigarrow':u'⇝', + u'\\rightupdownharpoon':u'⥏', u'\\sim':u'~', u'\\strictfi':u'⥼', + u'\\strictif':u'⥽', u'\\subset':u'⊂', u'\\subseteq':u'⊆', + u'\\subsetneq':u'⊊', u'\\succapprox':u'⪸', u'\\succeqq':u'⪴', + u'\\succnapprox':u'⪺', u'\\supset':u'⊃', u'\\supseteq':u'⊇', + u'\\supsetneq':u'⊋', u'\\times':u'×', u'\\to':u'→', + u'\\updownarrows':u'⇅', u'\\updownharpoons':u'⥮', u'\\upupharpoons':u'⥣', + u'\\vartriangleleft':u'⊲', u'\\vartriangleright':u'⊳', } starts = { @@ -695,7 +782,7 @@ class FormulaConfig(object): unmodified = { - u'characters':[u'.',u'*',u'€',u'(',u')',u'[',u']',u':',u'·',u'!',u';',u'|',u'§',u'"',], + u'characters':[u'.',u'*',u'€',u'(',u')',u'[',u']',u'·',u'!',u';',u'|',u'§',u'"',], } urls = { @@ -706,7 +793,7 @@ class GeneralConfig(object): "Configuration class from elyxer.config file" version = { - u'date':u'2011-06-27', u'lyxformat':u'413', u'number':u'1.2.3', + u'date':u'2015-02-26', u'lyxformat':u'413', u'number':u'1.2.5', } class HeaderConfig(object): @@ -735,6 +822,7 @@ class ImageConfig(object): u'imagemagick':u'convert[ -density $scale][ -define $format:use-cropbox=true] "$input" "$output"', u'inkscape':u'inkscape "$input" --export-png="$output"', + u'lyx':u'lyx -C "$input" "$output"', } cropboxformats = { @@ -860,6 +948,10 @@ class TagConfig(object): u'Comment':u'', u'Greyedout':u'span class="greyedout"', u'Note':u'', } + script = { + u'subscript':u'sub', u'superscript':u'sup', + } + shaped = { u'italic':u'i', u'slanted':u'i', u'smallcaps':u'span class="versalitas"', } @@ -889,7 +981,8 @@ class TranslationConfig(object): languages = { u'american':u'en', u'british':u'en', u'deutsch':u'de', u'dutch':u'nl', - u'english':u'en', u'french':u'fr', u'ngerman':u'de', u'spanish':u'es', + u'english':u'en', u'french':u'fr', u'ngerman':u'de', u'russian':u'ru', + u'spanish':u'es', } @@ -936,7 +1029,7 @@ class CommandLineParser(object): initial = args[0] del args[0] return key, self.readquoted(args, initial) - value = args[0] + value = args[0].decode('utf-8') del args[0] if isinstance(current, list): current.append(value) @@ -945,8 +1038,10 @@ class CommandLineParser(object): def readquoted(self, args, initial): "Read a value between quotes" + Trace.error('Oops') value = initial[1:] while len(args) > 0 and not args[0].endswith('"') and not args[0].startswith('--'): + Trace.error('Appending ' + args[0]) value += ' ' + args[0] del args[0] if len(args) == 0 or args[0].startswith('--'): @@ -983,6 +1078,7 @@ class Options(object): unicode = False iso885915 = False css = [] + favicon = '' title = None directory = None destdirectory = None @@ -1062,6 +1158,8 @@ class Options(object): Options.copyimages = True if Options.css == []: Options.css = ['http://elyxer.nongnu.org/lyx.css'] + if Options.favicon == '': + pass # no default favicon if Options.html: Options.simplemath = True if Options.toc and not Options.tocfor: @@ -1069,6 +1167,8 @@ class Options(object): Options.tocfor = Options.toctarget if Options.nocopy: Trace.error('Option --nocopy is deprecated; it is no longer needed') + if Options.jsmath: + Trace.error('Option --jsmath is deprecated; use --mathjax instead') # set in Trace if necessary for param in dir(Trace): if param.endswith('mode'): @@ -1088,6 +1188,7 @@ class Options(object): return Options.marginfoot = False Options.letterfoot = False + Options.hoverfoot = False options = Options.footnotes.split(',') for option in options: footoption = option + 'foot' @@ -1113,7 +1214,8 @@ class Options(object): Trace.error(' Options for HTML output:') Trace.error(' --title "title": set the generated page title') Trace.error(' --css "file.css": use a custom CSS file') - Trace.error(' --embedcss "file.css": embed styles from elyxer.a CSS file into the output') + Trace.error(' --embedcss "file.css": embed styles from a CSS file into the output') + Trace.error(' --favicon "icon.ico": insert the specified favicon in the header.') Trace.error(' --html: output HTML 4.0 instead of the default XHTML') Trace.error(' --unicode: full Unicode output') Trace.error(' --iso885915: output a document with ISO-8859-15 encoding') @@ -1143,8 +1245,8 @@ class Options(object): Trace.error(' --notoclabels: omit the part labels in the TOC, such as Chapter') Trace.error(' --lowmem: do the conversion on the fly (conserve memory)') Trace.error(' --raw: generate HTML without header or footer.') - Trace.error(' --jsmath "URL": use jsMath from elyxer.the given URL to display equations') - Trace.error(' --mathjax "URL": use MathJax from elyxer.the given URL to display equations') + Trace.error(' --mathjax remote: use MathJax remotely to display equations') + Trace.error(' --mathjax "URL": use MathJax from the given URL to display equations') Trace.error(' --googlecharts: use Google Charts to generate formula images') Trace.error(' --template "file": use a template, put everything in <!--$content-->') Trace.error(' --copyright: add a copyright notice at the bottom') @@ -1152,6 +1254,7 @@ class Options(object): Trace.error(' --toc: (deprecated) create a table of contents') Trace.error(' --toctarget "page": (deprecated) generate a TOC for the given page') Trace.error(' --nocopy: (deprecated) maintained for backwards compatibility') + Trace.error(' --jsmath "URL": use jsMath from the given URL to display equations') sys.exit() def showversion(self): @@ -3536,11 +3639,17 @@ class BarredText(TaggedText): return self.output.tag = TagConfig.barred[self.type] -class LangLine(BlackBox): +class LangLine(TaggedText): "A line with language information" def process(self): - self.lang = self.header[1] + "Only generate a span with lang info when the language is recognized." + lang = self.header[1] + if not lang in TranslationConfig.languages: + self.output = ContentsOutput() + return + isolang = TranslationConfig.languages[lang] + self.output = TaggedOutput().settag('span lang="' + isolang + '"', False) class InsetLength(BlackBox): "A length measure inside an inset." @@ -3908,8 +4017,7 @@ class Reference(Link): self.replace('@', partkey and partkey.number) self.replace(u'¶', partkey and partkey.tocentry) if not '$' in self.formatted or not partkey or not partkey.titlecontents: - if '$' in self.formatted: - Trace.error('No title in ' + unicode(partkey)) + # there is a $ left, but it should go away on preprocessing self.contents = [Constant(self.formatted)] return pieces = self.formatted.split('$') @@ -4481,6 +4589,8 @@ class BeginCommand(CommandBit): FormulaCommand.types += [BeginCommand] +import datetime + class CombiningFunction(OneParamFunction): @@ -4697,6 +4807,16 @@ class BracketProcessor(MathsProcessor): command.output = ContentsOutput() command.contents = bracket.getcontents() +class TodayCommand(EmptyCommand): + "Shows today's date." + + commandmap = None + + def parsebit(self, pos): + "Parse a command without parameters" + self.output = FixedOutput() + self.html = [datetime.date.today().strftime('%b %d, %Y')] + FormulaCommand.types += [ DecoratingFunction, CombiningFunction, LimitCommand, BracketCommand, diff --git a/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py b/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py new file mode 100644 index 0000000..ccc5593 --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/utils/math/tex2mathml_extern.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# :Id: $Id: tex2mathml_extern.py 7861 2015-04-10 23:48:51Z milde $ +# :Copyright: © 2015 Günter Milde. +# :License: Released under the terms of the `2-Clause BSD license`_, in short: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause + +# Wrappers for TeX->MathML conversion by external tools +# ===================================================== + +import subprocess + +document_template = r"""\documentclass{article} +\usepackage{amsmath} +\begin{document} +%s +\end{document} +""" + +def latexml(math_code, reporter=None): + """Convert LaTeX math code to MathML with LaTeXML_ + + .. _LaTeXML: http://dlmf.nist.gov/LaTeXML/ + """ + p = subprocess.Popen(['latexml', + '-', # read from stdin + # '--preload=amsmath', + '--inputencoding=utf8', + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + p.stdin.write((document_template % math_code).encode('utf8')) + p.stdin.close() + latexml_code = p.stdout.read() + latexml_err = p.stderr.read().decode('utf8') + if reporter and latexml_err.find('Error') >= 0 or not latexml_code: + reporter.error(latexml_err) + + post_p = subprocess.Popen(['latexmlpost', + '-', + '--nonumbersections', + '--format=xhtml', + # '--linelength=78', # experimental + '--' + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + post_p.stdin.write(latexml_code) + post_p.stdin.close() + result = post_p.stdout.read().decode('utf8') + post_p_err = post_p.stderr.read().decode('utf8') + if reporter and post_p_err.find('Error') >= 0 or not result: + reporter.error(post_p_err) + + # extract MathML code: + start,end = result.find('<math'), result.find('</math>')+7 + result = result[start:end] + if 'class="ltx_ERROR' in result: + raise SyntaxError(result) + return result + +def ttm(math_code, reporter=None): + """Convert LaTeX math code to MathML with TtM_ + + .. _TtM: http://hutchinson.belmont.ma.us/tth/mml/ + """ + p = subprocess.Popen(['ttm', + # '-i', # italic font for equations. Default roman. + '-u', # unicode character encoding. (Default iso-8859-1). + '-r', # output raw MathML (no preamble or postlude) + ], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + p.stdin.write((document_template % math_code).encode('utf8')) + p.stdin.close() + result = p.stdout.read() + err = p.stderr.read().decode('utf8') + if err.find('**** Unknown') >= 0: + msg = '\n'.join([line for line in err.splitlines() + if line.startswith('****')]) + raise SyntaxError('\nMessage from external converter TtM:\n'+ msg) + if reporter and err.find('**** Error') >= 0 or not result: + reporter.error(err) + start,end = result.find('<math'), result.find('</math>')+7 + result = result[start:end] + return result + +def blahtexml(math_code, inline=True, reporter=None): + """Convert LaTeX math code to MathML with blahtexml_ + + .. _blahtexml: http://gva.noekeon.org/blahtexml/ + """ + options = ['--mathml', + '--indented', + '--spacing', 'moderate', + '--mathml-encoding', 'raw', + '--other-encoding', 'raw', + '--doctype-xhtml+mathml', + '--annotate-TeX', + ] + if inline: + mathmode_arg = '' + else: + mathmode_arg = 'mode="display"' + options.append('--displaymath') + + p = subprocess.Popen(['blahtexml']+options, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True) + p.stdin.write(math_code.encode('utf8')) + p.stdin.close() + result = p.stdout.read().decode('utf8') + err = p.stderr.read().decode('utf8') + + print err + if result.find('<error>') >= 0: + raise SyntaxError('\nMessage from external converter blahtexml:\n' + +result[result.find('<message>')+9:result.find('</message>')]) + if reporter and (err.find('**** Error') >= 0 or not result): + reporter.error(err) + start,end = result.find('<markup>')+9, result.find('</markup>') + result = ('<math xmlns="http://www.w3.org/1998/Math/MathML"%s>\n' + '%s</math>\n') % (mathmode_arg, result[start:end]) + return result + +# self-test + +if __name__ == "__main__": + example = ur'\frac{\partial \sin^2(\alpha)}{\partial \vec r} \varpi \, \text{Grüße}' + # print latexml(example).encode('utf8') + # print ttm(example)#.encode('utf8') + print blahtexml(example).encode('utf8') diff --git a/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py b/docutils/src/main/resources/docutils/docutils/utils/smartquotes.py index 68b1e83..ae5ccc4 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 7716 2013-08-21 21:54:57Z milde $ +# :Id: $Id: smartquotes.py 7933 2016-01-13 21:09:13Z milde $ # :Copyright: © 2010 Günter Milde, # original `SmartyPants`_: © 2003 John Gruber # smartypants.py: © 2004, 2007 Chad Miller @@ -43,7 +43,7 @@ Authors `John Gruber`_ did all of the hard work of writing this software in Perl for `Movable Type`_ and almost all of this useful documentation. `Chad Miller`_ ported it to Python to use with Pyblosxom_. -Adapted to Docutils_ by Günter Milde +Adapted to Docutils_ by Günter Milde. Additional Credits ================== diff --git a/docutils/src/main/resources/docutils/docutils/utils/urischemes.py b/docutils/src/main/resources/docutils/docutils/utils/urischemes.py index 53d76eb..253bc5f 100644 --- a/docutils/src/main/resources/docutils/docutils/utils/urischemes.py +++ b/docutils/src/main/resources/docutils/docutils/utils/urischemes.py @@ -1,4 +1,4 @@ -# $Id: urischemes.py 7464 2012-06-25 13:16:03Z milde $ +# $Id: urischemes.py 7922 2015-09-22 15:28:09Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -113,7 +113,7 @@ schemes = { 'tel': ('a connection to a terminal that handles normal voice ' 'telephone calls, a voice mailbox or another voice messaging ' 'system or a service that can be operated using DTMF tones; ' - 'RFC 2806.'), + 'RFC 3966.'), 'telephone': 'telephone', 'telnet': 'Reference to interactive sessions; RFC 4248', 'tftp': 'Trivial File Transfer Protocol; RFC 3617', diff --git a/docutils/src/main/resources/docutils/docutils/writers/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/__init__.py index 5e254e1..3208c8a 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/__init__.py @@ -1,4 +1,4 @@ -# $Id: __init__.py 7648 2013-04-18 07:36:22Z milde $ +# $Id: __init__.py 7969 2016-08-18 21:40:00Z milde $ # Author: David Goodger <goodger@python.org> # Copyright: This module has been placed in the public domain. @@ -120,13 +120,18 @@ class UnfilteredWriter(Writer): _writer_aliases = { - 'html': 'html4css1', + 'html': 'html4css1', # may change to html5 some day + 'html4': 'html4css1', + 'html5': 'html5_polyglot', 'latex': 'latex2e', 'pprint': 'pseudoxml', 'pformat': 'pseudoxml', 'pdf': 'rlpdf', - 'xml': 'docutils_xml', - 's5': 's5_html'} + 's5': 's5_html', + 'xelatex': 'xetex', + 'xhtml': 'html5_polyglot', + 'xhtml10': 'html4css1', + 'xml': 'docutils_xml'} def get_writer_class(writer_name): """Return the Writer class from the `writer_name` module.""" diff --git a/docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/_html_base.py similarity index 62% copy from docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py copy to docutils/src/main/resources/docutils/docutils/writers/_html_base.py index 1af053e..a03231c 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html4css1/__init__.py +++ b/docutils/src/main/resources/docutils/docutils/writers/_html_base.py @@ -1,24 +1,26 @@ -# $Id: __init__.py 7753 2014-06-24 14:52:59Z milde $ -# Author: David Goodger -# Maintainer: docutils-develop@lists.sourceforge.net -# Copyright: This module has been placed in the public domain. - -""" -Simple HyperText Markup Language document tree Writer. - -The output conforms to the XHTML version 1.0 Transitional DTD -(*almost* strict). The output contains a minimum of formatting -information. The cascading style sheet "html4css1.css" is required -for proper viewing with a modern graphical browser. -""" - -__docformat__ = 'reStructuredText' - +#!/usr/bin/env python +# -*- coding: utf8 -*- +# :Author: David Goodger, Günter Milde +# Based on the html4css1 writer by David Goodger. +# :Maintainer: docutils-develop@lists.sourceforge.net +# :Revision: $Revision: 7977 $ +# :Date: $Date: 2005-06-28$ +# :Copyright: © 2016 David Goodger, Günter Milde +# :License: Released under the terms of the `2-Clause BSD license`_, in short: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause + + +# _html_base.py: common definitions for Docutils HTML writers +# ============================================================ import sys -import os import os.path -import time import re import urllib try: # check for the Python Imaging Library @@ -31,130 +33,27 @@ except ImportError: except ImportError: PIL = None import docutils -from docutils import frontend, nodes, utils, writers, languages, io +from docutils import nodes, utils, writers, languages, io from docutils.utils.error_reporting import SafeString from docutils.transforms import writer_aux -from docutils.utils.math import unichar2tex, pick_math_environment, math2html -from docutils.utils.math.latex2mathml import parse_latex_math +from docutils.utils.math import (unichar2tex, pick_math_environment, + math2html, latex2mathml, tex2mathml_extern) + class Writer(writers.Writer): - supported = ('html', 'html4css1', 'xhtml') + supported = ('html', 'xhtml') # update in subclass """Formats this writer supports.""" - default_stylesheet = 'html4css1.css' - default_stylesheet_dirs = ['.', utils.relative_path( - os.path.join(os.getcwd(), 'dummy'), os.path.dirname(__file__))] - + # default_stylesheets = [] # set in subclass! + # default_stylesheet_dirs = ['.'] # set in subclass! default_template = 'template.txt' - - default_template_path = utils.relative_path( - os.path.join(os.getcwd(), 'dummy'), - os.path.join(os.path.dirname(__file__), default_template)) - - settings_spec = ( - 'HTML-Specific Options', - None, - (('Specify the template file (UTF-8 encoded). Default is "%s".' - % default_template_path, - ['--template'], - {'default': default_template_path, 'metavar': '<file>'}), - ('Comma separated list of stylesheet URLs. ' - 'Overrides previous --stylesheet and --stylesheet-path settings.', - ['--stylesheet'], - {'metavar': '<URL[,URL,...]>', 'overrides': 'stylesheet_path', - 'validator': frontend.validate_comma_separated_list}), - ('Comma separated list of stylesheet paths. ' - 'Relative paths are expanded if a matching file is found in ' - 'the --stylesheet-dirs. With --link-stylesheet, ' - 'the path is rewritten relative to the output HTML file. ' - 'Default: "%s"' % default_stylesheet, - ['--stylesheet-path'], - {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', - 'validator': frontend.validate_comma_separated_list, - 'default': [default_stylesheet]}), - ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' - 'files must be accessible during processing. This is the default.', - ['--embed-stylesheet'], - {'default': 1, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Link to the stylesheet(s) in the output HTML file. ' - 'Default: embed stylesheets.', - ['--link-stylesheet'], - {'dest': 'embed_stylesheet', 'action': 'store_false'}), - ('Comma-separated list of directories where stylesheets are found. ' - 'Used by --stylesheet-path when expanding relative path arguments. ' - 'Default: "%s"' % default_stylesheet_dirs, - ['--stylesheet-dirs'], - {'metavar': '<dir[,dir,...]>', - 'validator': frontend.validate_comma_separated_list, - 'default': default_stylesheet_dirs}), - ('Specify the initial header level. Default is 1 for "<h1>". ' - 'Does not affect document title & subtitle (see --no-doc-title).', - ['--initial-header-level'], - {'choices': '1 2 3 4 5 6'.split(), 'default': '1', - 'metavar': '<level>'}), - ('Specify the maximum width (in characters) for one-column field ' - 'names. Longer field names will span an entire row of the table ' - 'used to render the field list. Default is 14 characters. ' - 'Use 0 for "no limit".', - ['--field-name-limit'], - {'default': 14, 'metavar': '<level>', - 'validator': frontend.validate_nonnegative_int}), - ('Specify the maximum width (in characters) for options in option ' - 'lists. Longer options will span an entire row of the table used ' - 'to render the option list. Default is 14 characters. ' - 'Use 0 for "no limit".', - ['--option-limit'], - {'default': 14, 'metavar': '<level>', - 'validator': frontend.validate_nonnegative_int}), - ('Format for footnote references: one of "superscript" or ' - '"brackets". Default is "brackets".', - ['--footnote-references'], - {'choices': ['superscript', 'brackets'], 'default': 'brackets', - 'metavar': '<format>', - 'overrides': 'trim_footnote_reference_space'}), - ('Format for block quote attributions: one of "dash" (em-dash ' - 'prefix), "parentheses"/"parens", or "none". Default is "dash".', - ['--attribution'], - {'choices': ['dash', 'parentheses', 'parens', 'none'], - 'default': 'dash', 'metavar': '<format>'}), - ('Remove extra vertical whitespace between items of "simple" bullet ' - 'lists and enumerated lists. Default: enabled.', - ['--compact-lists'], - {'default': 1, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Disable compact simple bullet and enumerated lists.', - ['--no-compact-lists'], - {'dest': 'compact_lists', 'action': 'store_false'}), - ('Remove extra vertical whitespace between items of simple field ' - 'lists. Default: enabled.', - ['--compact-field-lists'], - {'default': 1, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Disable compact simple field lists.', - ['--no-compact-field-lists'], - {'dest': 'compact_field_lists', 'action': 'store_false'}), - ('Added to standard table classes. ' - 'Defined styles: "borderless". Default: ""', - ['--table-style'], - {'default': ''}), - ('Math output format, one of "MathML", "HTML", "MathJax" ' - 'or "LaTeX". Default: "HTML math.css"', - ['--math-output'], - {'default': 'HTML math.css'}), - ('Omit the XML declaration. Use with caution.', - ['--no-xml-declaration'], - {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false', - 'validator': frontend.validate_boolean}), - ('Obfuscate email addresses to confuse harvesters while still ' - 'keeping email links usable with standards-compliant browsers.', - ['--cloak-email-addresses'], - {'action': 'store_true', 'validator': frontend.validate_boolean}),)) + # default_template_path = ... # set in subclass! + # settings_spec = ... # set in subclass! settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'} - config_section = 'html4css1 writer' + # config_section = ... # set in subclass! config_section_dependencies = ('writers',) visitor_attributes = ( @@ -167,10 +66,6 @@ class Writer(writers.Writer): def get_transforms(self): return writers.Writer.get_transforms(self) + [writer_aux.Admonitions] - def __init__(self): - writers.Writer.__init__(self) - self.translator_class = HTMLTranslator - def translate(self): self.visitor = visitor = self.translator_class(self.document) self.document.walkabout(visitor) @@ -200,62 +95,21 @@ class Writer(writers.Writer): self.parts[part] = ''.join(getattr(self, part)) -class HTMLTranslator(nodes.NodeVisitor): - - """ - This HTML writer has been optimized to produce visually compact - lists (less vertical whitespace). HTML's mixed content models - allow list items to contain "<li><p>body elements</p></li>" or - "<li>just text</li>" or even "<li>text<p>and body - elements</p>combined</li>", each with different effects. It would - be best to stick with strict body elements in list items, but they - affect vertical spacing in browsers (although they really - shouldn't). - - Here is an outline of the optimization: - - - Check for and omit <p> tags in "simple" lists: list items - contain either a single paragraph, a nested simple list, or a - paragraph followed by a nested simple list. This means that - this list can be compact: - - - Item 1. - - Item 2. - - But this list cannot be compact: - - Item 1. - - This second paragraph forces space between list items. - - - Item 2. - - - In non-list contexts, omit <p> tags on a paragraph if that - paragraph is the only child of its parent (footnotes & citations - are allowed a label first). +class HTMLTranslator(nodes.NodeVisitor): - - Regardless of the above, in definitions, table cells, field bodies, - option descriptions, and list items, mark the first child with - 'class="first"' and the last child with 'class="last"'. The stylesheet - sets the margins (top & bottom respectively) to 0 for these elements. + """Generic Docutils to HTML translator. - The ``no_compact_lists`` setting (``--no-compact-lists`` command-line - option) disables list whitespace optimization. - """ + See the html4css1 and html5_polyglott for writers for full featured HTML + writers. """ xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n' - doctype = ( - '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' - ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n') + doctype = '<!DOCTYPE html>\n' doctype_mathml = doctype head_prefix_template = ('<html xmlns="http://www.w3.org/1999/xhtml"' ' xml:lang="%(lang)s" lang="%(lang)s">\n<head>\n') - content_type = ('<meta http-equiv="Content-Type"' - ' content="text/html; charset=%s" />\n') - content_type_mathml = ('<meta http-equiv="Content-Type"' - ' content="application/xhtml+xml; charset=%s" />\n') - + content_type = ('<meta charset="%s"/>\n') generator = ('<meta name="generator" content="Docutils %s: ' 'http://docutils.sourceforge.net/" />\n') @@ -264,9 +118,9 @@ class HTMLTranslator(nodes.NodeVisitor): # 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/ - mathjax_url = ('http://cdn.mathjax.org/mathjax/latest/MathJax.js?' - 'config=TeX-AMS-MML_HTMLorMML') # may be overwritten by custom URL appended to "mathjax" + mathjax_url = ('https://cdn.mathjax.org/mathjax/latest/MathJax.js?' + 'config=TeX-AMS_CHTML') stylesheet_link = '<link rel="stylesheet" href="%s" type="text/css" />\n' embedded_stylesheet = '<style type="text/css">\n\n%s\n</style>\n' @@ -274,6 +128,14 @@ class HTMLTranslator(nodes.NodeVisitor): sollbruchstelle = re.compile(r'.+\W\W.+|[-?].+', re.U) # wrap point inside word lang_attribute = 'lang' # name changes to 'xml:lang' in XHTML 1.1 + special_characters = {ord('&'): u'&', + ord('<'): u'<', + ord('"'): u'"', + ord('>'): u'>', + ord('@'): u'@', # may thwart address harvesters + } + """Character references for characters with a special meaning in HTML.""" + def __init__(self, document): nodes.NodeVisitor.__init__(self, document) self.settings = settings = document.settings @@ -285,6 +147,7 @@ class HTMLTranslator(nodes.NodeVisitor): if settings.xml_declaration: self.head_prefix.append(self.xml_declaration % settings.output_encoding) + # self.content_type = "" # encoding not interpolated: self.html_prolog.append(self.xml_declaration) self.head = self.meta[:] @@ -308,13 +171,15 @@ class HTMLTranslator(nodes.NodeVisitor): # A heterogenous stack used in conjunction with the tree traversal. # Make sure that the pops correspond to the pushes: self.context = [] - self.topic_classes = [] + + self.topic_classes = [] # TODO: replace with self_in_contents self.colspecs = [] self.compact_p = True self.compact_simple = False self.compact_field_list = False self.in_docinfo = False self.in_sidebar = False + self.in_footnote_list = False self.title = [] self.subtitle = [] self.header = [] @@ -325,7 +190,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.html_body = [] self.in_document_title = 0 # len(self.body) or 0 self.in_mailto = False - self.author_in_authors = False + self.author_in_authors = False # for html4css1 self.math_header = [] def astext(self): @@ -336,16 +201,11 @@ class HTMLTranslator(nodes.NodeVisitor): def encode(self, text): """Encode special characters in `text` & return.""" + # Use only named entities known in both XML and HTML + # other characters are automatically encoded "by number" if required. # @@@ A codec to do these and all other HTML entities would be nice. text = unicode(text) - return text.translate({ - ord('&'): u'&', - ord('<'): u'<', - ord('"'): u'"', - ord('>'): u'>', - ord('@'): u'@', # may thwart some address harvesters - # TODO: convert non-breaking space only if needed? - 0xa0: u' '}) # non-breaking space + return text.translate(self.special_characters) def cloak_mailto(self, uri): """Try to hide a mailto: URL from harvesters.""" @@ -428,8 +288,11 @@ class HTMLTranslator(nodes.NodeVisitor): # may be targets inside of references, but nested "a" # elements aren't allowed in XHTML (even if they do # not all have a "href" attribute). - if empty: - # Empty tag. Insert target right in front of element. + if empty or isinstance(node, + (nodes.bullet_list, nodes.enumerated_list, + nodes.definition_list, nodes.field_list, + nodes.option_list, nodes.docinfo)): + # Insert target right in front of element. prefix.append('<span id="%s"></span>' % id) else: # Non-empty tag. Place the auxiliary <span> tag @@ -471,10 +334,6 @@ class HTMLTranslator(nodes.NodeVisitor): return child['classes'].append(class_) - def set_first_last(self, node): - self.set_class_on_child(node, 'first', 0) - self.set_class_on_child(node, 'last', -1) - def visit_Text(self, node): text = node.astext() encoded = self.encode(text) @@ -501,20 +360,21 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_address(self, node): self.visit_docinfo_item(node, 'address', meta=False) - self.body.append(self.starttag(node, 'pre', CLASS='address')) + self.body.append(self.starttag(node, 'pre', + suffix= '', CLASS='address')) def depart_address(self, node): self.body.append('\n</pre>\n') self.depart_docinfo_item() def visit_admonition(self, node): + node['classes'].insert(0, 'admonition') self.body.append(self.starttag(node, 'div')) - self.set_first_last(node) def depart_admonition(self, node=None): self.body.append('</div>\n') - attribution_formats = {'dash': ('—', ''), + attribution_formats = {'dash': (u'\u2014', ''), 'parentheses': ('(', ')'), 'parens': ('(', ')'), 'none': ('', '')} @@ -529,21 +389,19 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append(self.context.pop() + '</p>\n') def visit_author(self, node): - if isinstance(node.parent, nodes.authors): - if self.author_in_authors: - self.body.append('\n<br />') - else: + if not(isinstance(node.parent, nodes.authors)): self.visit_docinfo_item(node, 'author') + self.body.append('<p>') def depart_author(self, node): + self.body.append('</p>') if isinstance(node.parent, nodes.authors): - self.author_in_authors = True + self.body.append('\n') else: self.depart_docinfo_item() def visit_authors(self, node): self.visit_docinfo_item(node, 'authors') - self.author_in_authors = False # initialize def depart_authors(self, node): self.depart_docinfo_item() @@ -560,17 +418,38 @@ class HTMLTranslator(nodes.NodeVisitor): try: node.walk(visitor) except nodes.NodeFound: - return None + return False else: - return 1 + return True + + # Compact lists + # ------------ + # Include definition lists and field lists (in addition to ordered + # and unordered lists) in the test if a list is "simple" (cf. the + # html4css1.HTMLTranslator docstring and the SimpleListChecker class at + # the end of this file). def is_compactable(self, node): - return ('compact' in node['classes'] - or (self.settings.compact_lists - and 'open' not in node['classes'] - and (self.compact_simple - or self.topic_classes == ['contents'] - or self.check_simple_list(node)))) + # print "is_compactable %s ?" % node.__class__, + # explicite class arguments have precedence + if 'compact' in node['classes']: + return True + if 'open' in node['classes']: + return False + # check config setting: + if (isinstance(node, (nodes.field_list, nodes.definition_list)) + and not self.settings.compact_field_lists): + # print "`compact-field-lists` is False" + return False + if (isinstance(node, (nodes.enumerated_list, nodes.bullet_list)) + and not self.settings.compact_lists): + # print "`compact-lists` is False" + return False + # more special cases: + if (self.topic_classes == ['contents']): # TODO: self.in_contents + return True + # check the list items: + return self.check_simple_list(node) def visit_bullet_list(self, node): atts = {} @@ -592,18 +471,22 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_caption(self, node): self.body.append('</p>\n') + # citations + # --------- + # Use definition list instead of table for bibliographic references. + # Join adjacent citation entries. + def visit_citation(self, node): - self.body.append(self.starttag(node, 'table', - CLASS='docutils citation', - frame="void", rules="none")) - self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' - '<tbody valign="top">\n' - '<tr>') - self.footnote_backrefs(node) + if not self.in_footnote_list: + self.body.append('<dl class="citation">\n') + self.in_footnote_list = True def depart_citation(self, node): - self.body.append('</td></tr>\n' - '</tbody>\n</table>\n') + self.body.append('</dd>\n') + if not isinstance(node.next_node(descend=False, siblings=True), + nodes.citation): + self.body.append('</dl>\n') + self.in_footnote_list = False def visit_citation_reference(self, node): href = '#' @@ -619,8 +502,11 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_citation_reference(self, node): self.body.append(']</a>') + # classifier + # ---------- + # don't insert classifier-delimiter here (done by CSS) + def visit_classifier(self, node): - self.body.append(' <span class="classifier-delimiter">:</span> ') self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) def depart_classifier(self, node): @@ -632,17 +518,21 @@ class HTMLTranslator(nodes.NodeVisitor): node.parent.stubs.append(node.attributes.get('stub')) def depart_colspec(self, node): - pass - - def write_colspecs(self): - width = 0 - for node in self.colspecs: - width += node['colwidth'] + # 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 / width + 0.5) + colwidth = int(node['colwidth'] * 100.0 / total_width + 0.5) self.body.append(self.emptytag(node, 'col', - width='%i%%' % colwidth)) - self.colspecs = [] + style='width: %i%%' % colwidth)) + self.body.append('</colgroup>\n') def visit_comment(self, node, sub=re.compile('-(?=-)').sub): @@ -663,7 +553,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</div>\n') def visit_container(self, node): - self.body.append(self.starttag(node, 'div', CLASS='container')) + self.body.append(self.starttag(node, 'div', CLASS='docutils container')) def depart_container(self, node): self.body.append('</div>\n') @@ -695,66 +585,61 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_definition(self, node): self.body.append('</dt>\n') self.body.append(self.starttag(node, 'dd', '')) - self.set_first_last(node) def depart_definition(self, node): self.body.append('</dd>\n') def visit_definition_list(self, node): - self.body.append(self.starttag(node, 'dl', CLASS='docutils')) + classes = node.setdefault('classes', []) + if self.is_compactable(node): + classes.append('simple') + self.body.append(self.starttag(node, 'dl')) def depart_definition_list(self, node): self.body.append('</dl>\n') def visit_definition_list_item(self, node): - pass + # pass class arguments, ids and names to definition term: + node.children[0]['classes'] = ( + node.get('classes', []) + node.children[0].get('classes', [])) + node.children[0]['ids'] = ( + node.get('ids', []) + node.children[0].get('ids', [])) + node.children[0]['names'] = ( + node.get('names', []) + node.children[0].get('names', [])) def depart_definition_list_item(self, node): pass def visit_description(self, node): - self.body.append(self.starttag(node, 'td', '')) - self.set_first_last(node) + self.body.append(self.starttag(node, 'dd', '')) def depart_description(self, node): - self.body.append('</td>') + self.body.append('</dd>\n') def visit_docinfo(self, node): - self.context.append(len(self.body)) - self.body.append(self.starttag(node, 'table', - CLASS='docinfo', - frame="void", rules="none")) - self.body.append('<col class="docinfo-name" />\n' - '<col class="docinfo-content" />\n' - '<tbody valign="top">\n') - self.in_docinfo = True + classes = 'docinfo' + if (self.is_compactable(node)): + classes += ' simple' + self.body.append(self.starttag(node, 'dl', CLASS=classes)) def depart_docinfo(self, node): - self.body.append('</tbody>\n</table>\n') - self.in_docinfo = False - start = self.context.pop() - self.docinfo = self.body[start:] - self.body = [] + self.body.append('</dl>\n') def visit_docinfo_item(self, node, name, meta=True): if meta: meta_tag = '<meta name="%s" content="%s" />\n' \ % (name, self.attval(node.astext())) self.add_meta(meta_tag) - self.body.append(self.starttag(node, 'tr', '')) - self.body.append('<th class="docinfo-name">%s:</th>\n<td>' - % self.language.labels[name]) - if len(node): - if isinstance(node[0], nodes.Element): - node[0]['classes'].append('first') - if isinstance(node[-1], nodes.Element): - node[-1]['classes'].append('last') + self.body.append('<dt class="%s">%s</dt>\n' + % (name, self.language.labels[name])) + self.body.append(self.starttag(node, 'dd', '', CLASS=name)) def depart_docinfo_item(self): - self.body.append('</td></tr>\n') + self.body.append('</dd>\n') def visit_doctest_block(self, node): - self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) + self.body.append(self.starttag(node, 'pre', suffix='', + CLASS='code python doctest')) def depart_doctest_block(self, node): self.body.append('\n</pre>\n') @@ -812,110 +697,61 @@ class HTMLTranslator(nodes.NodeVisitor): node.parent.column += node['morecols'] self.body.append(self.starttag(node, tagname, '', **atts)) self.context.append('</%s>\n' % tagname.lower()) - if len(node) == 0: # empty cell - self.body.append(' ') - self.set_first_last(node) + # TODO: why does the html4css1 writer insert an NBSP into empty cells? + # if len(node) == 0: # empty cell + # self.body.append(' ') # no-break space def depart_entry(self, node): self.body.append(self.context.pop()) def visit_enumerated_list(self, node): - """ - The 'start' attribute does not conform to HTML 4.01's strict.dtd, but - CSS1 doesn't help. CSS2 isn't widely enough supported yet to be - usable. - """ atts = {} if 'start' in node: atts['start'] = node['start'] if 'enumtype' in node: atts['class'] = node['enumtype'] - # @@@ To do: prefix, suffix. How? Change prefix/suffix to a - # single "format" attribute? Use CSS2? - old_compact_simple = self.compact_simple - self.context.append((self.compact_simple, self.compact_p)) - self.compact_p = None - self.compact_simple = self.is_compactable(node) - if self.compact_simple and not old_compact_simple: + if self.is_compactable(node): atts['class'] = (atts.get('class', '') + ' simple').strip() self.body.append(self.starttag(node, 'ol', **atts)) def depart_enumerated_list(self, node): - self.compact_simple, self.compact_p = self.context.pop() self.body.append('</ol>\n') - def visit_field(self, node): - self.body.append(self.starttag(node, 'tr', '', CLASS='field')) - - def depart_field(self, node): - self.body.append('</tr>\n') + def visit_field_list(self, node): + # Keep simple paragraphs in the field_body to enable CSS + # rule to start body on new line if the label is too long + classes = 'field-list' + if (self.is_compactable(node)): + classes += ' simple' + self.body.append(self.starttag(node, 'dl', CLASS=classes)) - def visit_field_body(self, node): - self.body.append(self.starttag(node, 'td', '', CLASS='field-body')) - self.set_class_on_child(node, 'first', 0) - field = node.parent - if (self.compact_field_list or - isinstance(field.parent, nodes.docinfo) or - field.parent.index(field) == len(field.parent) - 1): - # If we are in a compact list, the docinfo, or if this is - # the last field of the field list, do not add vertical - # space after last element. - self.set_class_on_child(node, 'last', -1) + def depart_field_list(self, node): + self.body.append('</dl>\n') - def depart_field_body(self, node): - self.body.append('</td>\n') + def visit_field(self, node): + pass - def visit_field_list(self, node): - self.context.append((self.compact_field_list, self.compact_p)) - self.compact_p = None - if 'compact' in node['classes']: - self.compact_field_list = True - elif (self.settings.compact_field_lists - and 'open' not in node['classes']): - self.compact_field_list = True - if self.compact_field_list: - for field in node: - field_body = field[-1] - assert isinstance(field_body, nodes.field_body) - children = [n for n in field_body - if not isinstance(n, nodes.Invisible)] - if not (len(children) == 0 or - len(children) == 1 and - isinstance(children[0], - (nodes.paragraph, nodes.line_block))): - self.compact_field_list = False - break - self.body.append(self.starttag(node, 'table', frame='void', - rules='none', - CLASS='docutils field-list')) - self.body.append('<col class="field-name" />\n' - '<col class="field-body" />\n' - '<tbody valign="top">\n') + def depart_field(self, node): + pass - def depart_field_list(self, node): - self.body.append('</tbody>\n</table>\n') - self.compact_field_list, self.compact_p = self.context.pop() + # as field is ignored, pass class arguments to field-name and field-body: def visit_field_name(self, node): - atts = {} - if self.in_docinfo: - atts['class'] = 'docinfo-name' - else: - atts['class'] = 'field-name' - if ( self.settings.field_name_limit - and len(node.astext()) > self.settings.field_name_limit): - atts['colspan'] = 2 - self.context.append('</tr>\n' - + self.starttag(node.parent, 'tr', '', - CLASS='field') - + '<td> </td>') - else: - self.context.append('') - self.body.append(self.starttag(node, 'th', '', **atts)) + self.body.append(self.starttag(node, 'dt', '', + CLASS=''.join(node.parent['classes']))) def depart_field_name(self, node): - self.body.append(':</th>') - self.body.append(self.context.pop()) + self.body.append('</dt>\n') + + def visit_field_body(self, node): + self.body.append(self.starttag(node, 'dd', '', + CLASS=''.join(node.parent['classes']))) + # prevent misalignment of following content if the field is empty: + if not node.children: + self.body.append('<p></p>') + + def depart_field_body(self, node): + self.body.append('</dd>\n') def visit_figure(self, node): atts = {'class': 'figure'} @@ -928,6 +764,7 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_figure(self, node): self.body.append('</div>\n') + # use HTML 5 <footer> element? def visit_footer(self, node): self.context.append(len(self.body)) @@ -941,65 +778,43 @@ class HTMLTranslator(nodes.NodeVisitor): self.body_suffix[:0] = footer del self.body[start:] + # footnotes + # --------- + # use definition list instead of table for footnote text + + # TODO: use the new HTML5 element <aside>? (Also for footnote text) def visit_footnote(self, node): - self.body.append(self.starttag(node, 'table', - CLASS='docutils footnote', - frame="void", rules="none")) - self.body.append('<colgroup><col class="label" /><col /></colgroup>\n' - '<tbody valign="top">\n' - '<tr>') - self.footnote_backrefs(node) - - def footnote_backrefs(self, node): - backlinks = [] - backrefs = node['backrefs'] - if self.settings.footnote_backlinks and backrefs: - if len(backrefs) == 1: - self.context.append('') - self.context.append('</a>') - self.context.append('<a class="fn-backref" href="#%s">' - % backrefs[0]) - else: - i = 1 - for backref in backrefs: - backlinks.append('<a class="fn-backref" href="#%s">%s</a>' - % (backref, i)) - i += 1 - self.context.append('<em>(%s)</em> ' % ', '.join(backlinks)) - self.context += ['', ''] - else: - self.context.append('') - self.context += ['', ''] - # If the node does not only consist of a label. - if len(node) > 1: - # If there are preceding backlinks, we do not set class - # 'first', because we need to retain the top-margin. - if not backlinks: - node[1]['classes'].append('first') - node[-1]['classes'].append('last') + if not self.in_footnote_list: + classes = 'footnote ' + self.settings.footnote_references + self.body.append('<dl class="%s">\n'%classes) + self.in_footnote_list = True def depart_footnote(self, node): - self.body.append('</td></tr>\n' - '</tbody>\n</table>\n') + self.body.append('</dd>\n') + if not isinstance(node.next_node(descend=False, siblings=True), + nodes.footnote): + self.body.append('</dl>\n') + self.in_footnote_list = False def visit_footnote_reference(self, node): href = '#' + node['refid'] - format = self.settings.footnote_references - if format == 'brackets': - suffix = '[' - self.context.append(']') - else: - assert format == 'superscript' - suffix = '<sup>' - self.context.append('</sup>') - self.body.append(self.starttag(node, 'a', suffix, - CLASS='footnote-reference', href=href)) + classes = 'footnote-reference ' + self.settings.footnote_references + self.body.append(self.starttag(node, 'a', '', #suffix, + CLASS=classes, href=href)) def depart_footnote_reference(self, node): - self.body.append(self.context.pop() + '</a>') + self.body.append('</a>') + # Docutils-generated text: put section numbers in a span for CSS styling: def visit_generated(self, node): - pass + if 'sectnum' in node['classes']: + # get section number (strip trailing no-break-spaces) + sectnum = node.astext().rstrip(u' ') + # print sectnum.encode('utf-8') + self.body.append('<span class="sectnum">%s</span> ' + % self.encode(sectnum)) + # Content already processed: + raise nodes.SkipNode def depart_generated(self, node): pass @@ -1016,16 +831,16 @@ class HTMLTranslator(nodes.NodeVisitor): self.header.extend(header) del self.body[start:] + # Image types to place in an <object> element + object_image_types = {'.swf': 'application/x-shockwave-flash'} + def visit_image(self, node): atts = {} uri = node['uri'] - # place SVG and SWF images in an <object> element - types = {'.svg': 'image/svg+xml', - '.swf': 'application/x-shockwave-flash'} ext = os.path.splitext(uri)[1].lower() - if ext in ('.svg', '.swf'): + if ext in self.object_image_types: atts['data'] = uri - atts['type'] = types[ext] + atts['type'] = self.object_image_types[ext] else: atts['src'] = uri atts['alt'] = node.get('alt', uri) @@ -1077,8 +892,7 @@ class HTMLTranslator(nodes.NodeVisitor): suffix = '\n' if 'align' in node: atts['class'] = 'align-%s' % node['align'] - self.context.append('') - if ext in ('.svg', '.swf'): # place in an object element, + if ext in self.object_image_types: # do NOT use an empty tag: incorrect rendering in browsers self.body.append(self.starttag(node, 'object', suffix, **atts) + node.get('alt', uri) + '</object>' + suffix) @@ -1086,7 +900,8 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append(self.emptytag(node, 'img', suffix, **atts)) def depart_image(self, node): - self.body.append(self.context.pop()) + # self.body.append(self.context.pop()) + pass def visit_inline(self, node): self.body.append(self.starttag(node, 'span', '')) @@ -1094,14 +909,35 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_inline(self, node): self.body.append('</span>') + # footnote and citation labels: def visit_label(self, node): - # Context added in footnote_backrefs. - self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), - CLASS='label')) + if (isinstance(node.parent, nodes.footnote)): + classes = self.settings.footnote_references + else: + classes = 'brackets' + # pass parent node to get id into starttag: + self.body.append(self.starttag(node.parent, 'dt', '', CLASS='label')) + self.body.append(self.starttag(node, 'span', '', CLASS=classes)) + # footnote/citation backrefs: + if self.settings.footnote_backlinks: + backrefs = node.parent['backrefs'] + if len(backrefs) == 1: + self.body.append('<a class="fn-backref" href="#%s">' + % backrefs[0]) def depart_label(self, node): - # Context added in footnote_backrefs. - self.body.append(']%s</td><td>%s' % (self.context.pop(), self.context.pop())) + if self.settings.footnote_backlinks: + backrefs = node.parent['backrefs'] + if len(backrefs) == 1: + self.body.append('</a>') + self.body.append('</span>') + if self.settings.footnote_backlinks and len(backrefs) > 1: + # Python 2.4 fails with enumerate(backrefs, 1) + backlinks = ['<a href="#%s">%s</a>' % (ref, i+1) + for (i, ref) in enumerate(backrefs)] + self.body.append('<span class="fn-backref">(%s)</span>' + % ','.join(backlinks)) + self.body.append('</dt>\n<dd>') def visit_legend(self, node): self.body.append(self.starttag(node, 'div', CLASS='legend')) @@ -1125,12 +961,11 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_list_item(self, node): self.body.append(self.starttag(node, 'li', '')) - if len(node): - node[0]['classes'].append('first') def depart_list_item(self, node): self.body.append('</li>\n') + # inline literal def visit_literal(self, node): # special case: "code" role classes = node.get('classes', []) @@ -1140,24 +975,20 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append(self.starttag(node, 'code', '')) return self.body.append( - self.starttag(node, 'tt', '', CLASS='docutils literal')) + self.starttag(node, 'span', '', CLASS='docutils literal')) text = node.astext() + # remove hard line breaks (except if in a parsed-literal block) + if not isinstance(node.parent, nodes.literal_block): + text = text.replace('\n', ' ') + # Protect text like ``--an-option`` and the regular expression + # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping for token in self.words_and_spaces.findall(text): - if token.strip(): - # Protect text like "--an-option" and the regular expression - # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping - if self.sollbruchstelle.search(token): - self.body.append('<span class="pre">%s</span>' - % self.encode(token)) - else: - self.body.append(self.encode(token)) - elif token in ('\n', ' '): - # Allow breaks at whitespace: - self.body.append(token) + if token.strip() and self.sollbruchstelle.search(token): + self.body.append('<span class="pre">%s</span>' + % self.encode(token)) else: - # Protect runs of multiple spaces; the last space can wrap: - self.body.append(' ' * (len(token) - 1) + ' ') - self.body.append('</tt>') + self.body.append(self.encode(token)) + self.body.append('</span>') # Content already processed: raise nodes.SkipNode @@ -1166,46 +997,55 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</code>') def visit_literal_block(self, node): - self.body.append(self.starttag(node, 'pre', CLASS='literal-block')) + self.body.append(self.starttag(node, 'pre', '', CLASS='literal-block')) + if 'code' in node.get('classes', []): + self.body.append('<code>') def depart_literal_block(self, node): - self.body.append('\n</pre>\n') + if 'code' in node.get('classes', []): + self.body.append('</code>') + self.body.append('</pre>\n') + + # Mathematics: + # As there is no native HTML math support, we provide alternatives + # for the math-output: LaTeX and MathJax simply wrap the content, + # HTML and MathML also convert the math_code. + # HTML container + math_tags = {# math_output: (block, inline, class-arguments) + 'mathml': ('div', '', ''), + 'html': ('div', 'span', 'formula'), + 'mathjax': ('div', 'span', 'math'), + 'latex': ('pre', 'tt', 'math'), + } def visit_math(self, node, math_env=''): # If the method is called from visit_math_block(), math_env != ''. - # As there is no native HTML math support, we provide alternatives: - # LaTeX and MathJax math_output modes simply wrap the content, - # HTML and MathML math_output modes also convert the math_code. - if self.math_output not in ('mathml', 'html', 'mathjax', 'latex'): + if self.math_output not in self.math_tags: self.document.reporter.error( 'math-output format "%s" not supported ' 'falling back to "latex"'% self.math_output) self.math_output = 'latex' - # - # HTML container - tags = {# math_output: (block, inline, class-arguments) - 'mathml': ('div', '', ''), - 'html': ('div', 'span', 'formula'), - 'mathjax': ('div', 'span', 'math'), - 'latex': ('pre', 'tt', 'math'), - } - tag = tags[self.math_output][math_env == ''] - clsarg = tags[self.math_output][2] + tag = self.math_tags[self.math_output][math_env == ''] + clsarg = self.math_tags[self.math_output][2] # LaTeX container wrappers = {# math_mode: (inline, block) - 'mathml': (None, None), + '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}'), 'latex': (None, None), } wrapper = wrappers[self.math_output][math_env != ''] + if self.math_output == 'mathml' and (not self.math_output_options or + self.math_output_options[0] == 'blahtexml'): + wrapper = None # get and wrap content math_code = node.astext().translate(unichar2tex.uni2tex_table) - if wrapper and math_env: - math_code = wrapper % (math_env, math_code, math_env) - elif wrapper: - math_code = wrapper % math_code + if wrapper: + try: # wrapper with three "%s" + math_code = wrapper % (math_env, math_code, math_env) + except TypeError: # wrapper with one "%s" + math_code = wrapper % math_code # settings and conversion if self.math_output in ('latex', 'mathjax'): math_code = self.encode(math_code) @@ -1222,11 +1062,29 @@ class HTMLTranslator(nodes.NodeVisitor): math2html.DocumentParameters.displaymode = (math_env != '') math_code = math2html.math2html(math_code) elif self.math_output == 'mathml': - self.doctype = self.doctype_mathml - self.content_type = self.content_type_mathml + if 'XHTML 1' in self.doctype: + self.doctype = self.doctype_mathml + self.content_type = self.content_type_mathml + converter = ' '.join(self.math_output_options).lower() try: - mathml_tree = parse_latex_math(math_code, inline=not(math_env)) - math_code = ''.join(mathml_tree.xml()) + if converter == 'latexml': + math_code = tex2mathml_extern.latexml(math_code, + self.document.reporter) + elif converter == 'ttm': + math_code = tex2mathml_extern.ttm(math_code, + self.document.reporter) + elif converter == 'blahtexml': + math_code = tex2mathml_extern.blahtexml(math_code, + inline=not(math_env), + reporter=self.document.reporter) + elif not converter: + math_code = latex2mathml.tex2mathml(math_code, + inline=not(math_env)) + else: + self.document.reporter.error('option "%s" not supported ' + 'with math-output "MathML"') + except OSError: + raise OSError('is "latexmlmath" in your PATH?') except SyntaxError, err: err_node = self.document.reporter.error(err, base_node=node) self.visit_system_message(err_node) @@ -1265,6 +1123,8 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_math_block(self, node): pass # never reached + # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 + # HTML5/polyglot recommends using both def visit_meta(self, node): meta = self.emptytag(node, 'meta', **node.non_default_attributes()) self.add_meta(meta) @@ -1277,13 +1137,13 @@ class HTMLTranslator(nodes.NodeVisitor): self.head.append(tag) def visit_option(self, node): - if self.context[-1]: - self.body.append(', ') self.body.append(self.starttag(node, 'span', '', CLASS='option')) def depart_option(self, node): self.body.append('</span>') - self.context[-1] += 1 + if isinstance(node.next_node(descend=False, siblings=True), + nodes.option): + self.body.append(', ') def visit_option_argument(self, node): self.body.append(node.get('delimiter', ' ')) @@ -1293,39 +1153,24 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</var>') def visit_option_group(self, node): - atts = {} - if ( self.settings.option_limit - and len(node.astext()) > self.settings.option_limit): - atts['colspan'] = 2 - self.context.append('</tr>\n<tr><td> </td>') - else: - self.context.append('') - self.body.append( - self.starttag(node, 'td', CLASS='option-group', **atts)) + self.body.append(self.starttag(node, 'dt', '')) self.body.append('<kbd>') - self.context.append(0) # count number of options def depart_option_group(self, node): - self.context.pop() - self.body.append('</kbd></td>\n') - self.body.append(self.context.pop()) + self.body.append('</kbd></dt>\n') def visit_option_list(self, node): self.body.append( - self.starttag(node, 'table', CLASS='docutils option-list', - frame="void", rules="none")) - self.body.append('<col class="option" />\n' - '<col class="description" />\n' - '<tbody valign="top">\n') + self.starttag(node, 'dl', CLASS='option-list')) def depart_option_list(self, node): - self.body.append('</tbody>\n</table>\n') + self.body.append('</dl>\n') def visit_option_list_item(self, node): - self.body.append(self.starttag(node, 'tr', '')) + pass def depart_option_list_item(self, node): - self.body.append('</tr>\n') + pass def visit_option_string(self, node): pass @@ -1339,45 +1184,29 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_organization(self, node): self.depart_docinfo_item() - def should_be_compact_paragraph(self, node): - """ - Determine if the <p> tags around paragraph ``node`` can be omitted. - """ - if (isinstance(node.parent, nodes.document) or - isinstance(node.parent, nodes.compound)): - # Never compact paragraphs in document or compound. - return False - for key, value in node.attlist(): - if (node.is_not_default(key) and - not (key == 'classes' and value in - ([], ['first'], ['last'], ['first', 'last']))): - # Attribute which needs to survive. - return False - first = isinstance(node.parent[0], nodes.label) # skip label - for child in node.parent.children[first:]: - # only first paragraph can be compact - if isinstance(child, nodes.Invisible): - continue - if child is node: - break - return False - parent_length = len([n for n in node.parent if not isinstance( - n, (nodes.Invisible, nodes.label))]) - if ( self.compact_simple - or self.compact_field_list - or self.compact_p and parent_length == 1): - return True - return False + # Do not omit <p> tags + # -------------------- + # + # The HTML4CSS1 writer does this to "produce + # visually compact lists (less vertical whitespace)". This writer + # relies on CSS rules for"visual compactness". + # + # * In XHTML 1.1, e.g. a <blockquote> element may not contain + # character data, so you cannot drop the <p> tags. + # * Keeping simple paragraphs in the field_body enables a CSS + # rule to start the field-body on a new line if the label is too long + # * it makes the code simpler. + # + # TODO: omit paragraph tags in simple table cells? def visit_paragraph(self, node): - if self.should_be_compact_paragraph(node): - self.context.append('') - else: - self.body.append(self.starttag(node, 'p', '')) - self.context.append('</p>\n') + self.body.append(self.starttag(node, 'p', '')) def depart_paragraph(self, node): - self.body.append(self.context.pop()) + self.body.append('</p>') + if not (isinstance(node.parent, (nodes.list_item, nodes.entry)) and + (len(node.parent) == 1)): + self.body.append('\n') def visit_problematic(self, node): if node.hasattr('refid'): @@ -1446,6 +1275,7 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_rubric(self, node): self.body.append('</p>\n') + # TODO: use the new HTML 5 element <section>? def visit_section(self, node): self.section_level += 1 self.body.append( @@ -1455,10 +1285,10 @@ class HTMLTranslator(nodes.NodeVisitor): self.section_level -= 1 self.body.append('</div>\n') + # TODO: use the new HTML5 element <aside>? (Also for footnote text) def visit_sidebar(self, node): self.body.append( self.starttag(node, 'div', CLASS='sidebar')) - self.set_first_last(node) self.in_sidebar = True def depart_sidebar(self, node): @@ -1490,24 +1320,22 @@ class HTMLTranslator(nodes.NodeVisitor): def visit_substitution_reference(self, node): self.unimplemented_visit(node) + # h1–h6 elements must not be used to markup subheadings, subtitles, + # alternative titles and taglines unless intended to be the heading for a + # new section or subsection. + # -- http://www.w3.org/TR/html/sections.html#headings-and-sections def visit_subtitle(self, node): if isinstance(node.parent, nodes.sidebar): - self.body.append(self.starttag(node, 'p', '', - CLASS='sidebar-subtitle')) - self.context.append('</p>\n') + classes = 'sidebar-subtitle' elif isinstance(node.parent, nodes.document): - self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle')) - self.context.append('</h2>\n') + classes = 'subtitle' self.in_document_title = len(self.body) elif isinstance(node.parent, nodes.section): - tag = 'h%s' % (self.section_level + self.initial_header_level - 1) - self.body.append( - self.starttag(node, tag, '', CLASS='section-subtitle') + - self.starttag({}, 'span', '', CLASS='section-subtitle')) - self.context.append('</span></%s>\n' % tag) + classes = 'section-subtitle' + self.body.append(self.starttag(node, 'p', '', CLASS=classes)) def depart_subtitle(self, node): - self.body.append(self.context.pop()) + self.body.append('</p>\n') if self.in_document_title: self.subtitle = self.body[self.in_document_title:-1] self.in_document_title = 0 @@ -1543,22 +1371,26 @@ class HTMLTranslator(nodes.NodeVisitor): else: line = '' self.body.append('System Message: %s/%s ' - '(<tt class="docutils">%s</tt>%s)%s</p>\n' + '(<span class="docutils literal">%s</span>%s)%s</p>\n' % (node['type'], node['level'], self.encode(node['source']), line, backref_text)) def depart_system_message(self, node): self.body.append('</div>\n') + # tables + # ------ + # no hard-coded border setting in the table head:: + def visit_table(self, node): - self.context.append(self.compact_p) - self.compact_p = True - classes = ' '.join(['docutils', self.settings.table_style]).strip() - self.body.append( - self.starttag(node, 'table', CLASS=classes, border="1")) + classes = [cls.strip(u' \t\n') + for cls in self.settings.table_style.split(',')] + if 'align' in node: + classes.append('align-%s' % node['align']) + tag = self.starttag(node, 'table', CLASS=' '.join(classes)) + self.body.append(tag) def depart_table(self, node): - self.compact_p = self.context.pop() self.body.append('</table>\n') def visit_target(self, node): @@ -1572,10 +1404,9 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_target(self, node): self.body.append(self.context.pop()) + # no hard-coded vertical alignment in table body def visit_tbody(self, node): - self.write_colspecs() - self.body.append(self.context.pop()) # '</colgroup>\n' or '' - self.body.append(self.starttag(node, 'tbody', valign='top')) + self.body.append(self.starttag(node, 'tbody')) def depart_tbody(self, node): self.body.append('</tbody>\n') @@ -1591,21 +1422,14 @@ class HTMLTranslator(nodes.NodeVisitor): pass def visit_tgroup(self, node): - # Mozilla needs <colgroup>: - self.body.append(self.starttag(node, 'colgroup')) - # Appended by thead or tbody: - self.context.append('</colgroup>\n') + self.colspecs = [] node.stubs = [] def depart_tgroup(self, node): pass 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')) + self.body.append(self.starttag(node, 'thead')) def depart_thead(self, node): self.body.append('</thead>\n') @@ -1666,13 +1490,17 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_title_reference(self, node): self.body.append('</cite>') + # TODO: use the new HTML5 element <aside>? (Also for footnote text) def visit_topic(self, node): self.body.append(self.starttag(node, 'div', CLASS='topic')) self.topic_classes = node['classes'] + # TODO: replace with :: + # self.in_contents = 'contents' in node['classes'] def depart_topic(self, node): self.body.append('</div>\n') self.topic_classes = [] + # TODO self.in_contents = False def visit_transition(self, node): self.body.append(self.emptytag(node, 'hr', CLASS='docutils')) @@ -1698,39 +1526,73 @@ class SimpleListChecker(nodes.GenericNodeVisitor): Here "simple" means a list item containing nothing other than a single paragraph, a simple list, or a paragraph followed by a simple list. + + This version also checks for simple field lists and docinfo. """ def default_visit(self, node): raise nodes.NodeFound - def visit_bullet_list(self, node): - pass - - def visit_enumerated_list(self, node): - pass - def visit_list_item(self, node): - children = [] - for child in node.children: - if not isinstance(child, nodes.Invisible): - children.append(child) + # print "visiting list item", node.__class__ + children = [child for child in node.children + if not isinstance(child, nodes.Invisible)] + # print "has %s visible children" % len(children) if (children and isinstance(children[0], nodes.paragraph) - and (isinstance(children[-1], nodes.bullet_list) - or isinstance(children[-1], nodes.enumerated_list))): + and (isinstance(children[-1], nodes.bullet_list) or + isinstance(children[-1], nodes.enumerated_list) or + isinstance(children[-1], nodes.field_list))): children.pop() + # print "%s children remain" % len(children) if len(children) <= 1: return else: + # print "found", child.__class__, "in", node.__class__ raise nodes.NodeFound - def visit_paragraph(self, node): - raise nodes.SkipNode + def pass_node(self, node): + pass - def invisible_visit(self, node): - """Invisible nodes should be ignored.""" + def ignore_node(self, node): + # ignore nodes that are never complex (can contain only inline nodes) raise nodes.SkipNode - visit_comment = invisible_visit - visit_substitution_definition = invisible_visit - visit_target = invisible_visit - visit_pending = invisible_visit + # Paragraphs and text + visit_Text = ignore_node + visit_paragraph = ignore_node + + # Lists + visit_bullet_list = pass_node + visit_enumerated_list = pass_node + visit_docinfo = pass_node + + # Docinfo nodes: + visit_author = ignore_node + visit_authors = visit_list_item + visit_address = visit_list_item + visit_contact = pass_node + visit_copyright = ignore_node + visit_date = ignore_node + visit_organization = ignore_node + visit_status = ignore_node + visit_version = visit_list_item + + # Definition list: + visit_definition_list = pass_node + visit_definition_list_item = pass_node + visit_term = ignore_node + visit_classifier = pass_node + visit_definition = visit_list_item + + # Field list: + visit_field_list = pass_node + visit_field = pass_node + # the field body corresponds to a list item + visit_field_body = visit_list_item + visit_field_name = ignore_node + + # Invisible nodes should be ignored. + visit_comment = ignore_node + visit_substitution_definition = ignore_node + visit_target = ignore_node + visit_pending = ignore_node diff --git a/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py b/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py index 036817b..79f3dbd 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py +++ b/docutils/src/main/resources/docutils/docutils/writers/docutils_xml.py @@ -1,4 +1,4 @@ -# $Id: docutils_xml.py 7497 2012-08-16 15:17:29Z milde $ +# $Id: docutils_xml.py 7966 2016-08-18 13:06:09Z milde $ # Author: David Goodger, Paul Tremblay, Guenter Milde # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -46,7 +46,7 @@ class Writer(writers.Writer): ['--newlines'], {'action': 'store_true', 'validator': frontend.validate_boolean}), ('Generate XML with indents and newlines.', - ['--indents'], + ['--indents'], #@ TODO use integer value for number of spaces? {'action': 'store_true', 'validator': frontend.validate_boolean}), ('Omit the XML declaration. Use with caution.', ['--no-xml-declaration'], @@ -105,9 +105,10 @@ class XMLTranslator(nodes.GenericNodeVisitor): self.newline = '\n' if settings.indents: self.newline = '\n' - self.indent = ' ' + self.indent = ' ' #@ TODO make this configurable? self.level = 0 # indentation level self.in_simple = 0 # level of nesting inside mixed-content elements + self.fixed_text = 0 # level of nesting inside FixedText elements # Output self.output = [] @@ -125,13 +126,19 @@ class XMLTranslator(nodes.GenericNodeVisitor): # generic visit and depart methods # -------------------------------- + simple_nodes = (nodes.TextElement, + nodes.image, nodes.colspec, nodes.transition) # empty elements + def default_visit(self, node): """Default node visit method.""" if not self.in_simple: self.output.append(self.indent*self.level) self.output.append(node.starttag(xml.sax.saxutils.quoteattr)) self.level += 1 - if isinstance(node, nodes.TextElement): + # @@ make nodes.literal an instance of FixedTextElement? + if isinstance(node, (nodes.FixedTextElement, nodes.literal)): + self.fixed_text += 1 + if isinstance(node, self.simple_nodes): self.in_simple += 1 if not self.in_simple: self.output.append(self.newline) @@ -142,7 +149,9 @@ class XMLTranslator(nodes.GenericNodeVisitor): if not self.in_simple: self.output.append(self.indent*self.level) self.output.append(node.endtag()) - if isinstance(node, nodes.TextElement): + if isinstance(node, (nodes.FixedTextElement, nodes.literal)): + self.fixed_text -= 1 + if isinstance(node, self.simple_nodes): self.in_simple -= 1 if not self.in_simple: self.output.append(self.newline) @@ -153,6 +162,9 @@ class XMLTranslator(nodes.GenericNodeVisitor): def visit_Text(self, node): text = xml.sax.saxutils.escape(node.astext()) + # indent text if we are not in a FixedText element: + if not self.fixed_text: + text = text.replace('\n', '\n'+self.indent*self.level) self.output.append(text) def depart_Text(self, node): 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 1af053e..7853496 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 7753 2014-06-24 14:52:59Z milde $ +# $Id: __init__.py 7977 2016-11-29 12:00:39Z milde $ # Author: David Goodger # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -14,43 +14,28 @@ for proper viewing with a modern graphical browser. __docformat__ = 'reStructuredText' - -import sys -import os import os.path -import time -import re -import urllib -try: # check for the Python Imaging Library - import PIL.Image -except ImportError: - try: # sometimes PIL modules are put in PYTHONPATH's root - import Image - class PIL(object): pass # dummy wrapper - PIL.Image = Image - except ImportError: - PIL = None import docutils -from docutils import frontend, nodes, utils, writers, languages, io -from docutils.utils.error_reporting import SafeString +from docutils import frontend, nodes, writers, io from docutils.transforms import writer_aux -from docutils.utils.math import unichar2tex, pick_math_environment, math2html -from docutils.utils.math.latex2mathml import parse_latex_math +from docutils.writers import _html_base -class Writer(writers.Writer): +class Writer(writers._html_base.Writer): - supported = ('html', 'html4css1', 'xhtml') + supported = ('html', 'html4', 'html4css1', 'xhtml', 'xhtml10') """Formats this writer supports.""" - default_stylesheet = 'html4css1.css' - default_stylesheet_dirs = ['.', utils.relative_path( - os.path.join(os.getcwd(), 'dummy'), os.path.dirname(__file__))] + default_stylesheets = ['html4css1.css'] + default_stylesheet_dirs = ['.', + os.path.abspath(os.path.dirname(__file__)), + # for math.css + os.path.abspath(os.path.join( + os.path.dirname(os.path.dirname(__file__)), 'html5_polyglot')) + ] default_template = 'template.txt' - - default_template_path = utils.relative_path( - os.path.join(os.getcwd(), 'dummy'), - os.path.join(os.path.dirname(__file__), default_template)) + default_template_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), default_template) settings_spec = ( 'HTML-Specific Options', @@ -68,11 +53,11 @@ class Writer(writers.Writer): 'Relative paths are expanded if a matching file is found in ' 'the --stylesheet-dirs. With --link-stylesheet, ' 'the path is rewritten relative to the output HTML file. ' - 'Default: "%s"' % default_stylesheet, + 'Default: "%s"' % ','.join(default_stylesheets), ['--stylesheet-path'], {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', 'validator': frontend.validate_comma_separated_list, - 'default': [default_stylesheet]}), + 'default': default_stylesheets}), ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' 'files must be accessible during processing. This is the default.', ['--embed-stylesheet'], @@ -152,65 +137,24 @@ class Writer(writers.Writer): ['--cloak-email-addresses'], {'action': 'store_true', 'validator': frontend.validate_boolean}),)) - settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'} - config_section = 'html4css1 writer' - config_section_dependencies = ('writers',) - - visitor_attributes = ( - 'head_prefix', 'head', 'stylesheet', 'body_prefix', - 'body_pre_docinfo', 'docinfo', 'body', 'body_suffix', - 'title', 'subtitle', 'header', 'footer', 'meta', 'fragment', - 'html_prolog', 'html_head', 'html_title', 'html_subtitle', - 'html_body') - - def get_transforms(self): - return writers.Writer.get_transforms(self) + [writer_aux.Admonitions] def __init__(self): - writers.Writer.__init__(self) + self.parts = {} self.translator_class = HTMLTranslator - def translate(self): - self.visitor = visitor = self.translator_class(self.document) - self.document.walkabout(visitor) - for attr in self.visitor_attributes: - setattr(self, attr, getattr(visitor, attr)) - self.output = self.apply_template() - - def apply_template(self): - template_file = open(self.document.settings.template, 'rb') - template = unicode(template_file.read(), 'utf-8') - template_file.close() - subs = self.interpolation_dict() - return template % subs - - def interpolation_dict(self): - subs = {} - settings = self.document.settings - for attr in self.visitor_attributes: - subs[attr] = ''.join(getattr(self, attr)).rstrip('\n') - subs['encoding'] = settings.output_encoding - subs['version'] = docutils.__version__ - return subs - - def assemble_parts(self): - writers.Writer.assemble_parts(self) - for part in self.visitor_attributes: - self.parts[part] = ''.join(getattr(self, part)) - - -class HTMLTranslator(nodes.NodeVisitor): +class HTMLTranslator(writers._html_base.HTMLTranslator): """ - This HTML writer has been optimized to produce visually compact + The html4css1 writer has been optimized to produce visually compact lists (less vertical whitespace). HTML's mixed content models allow list items to contain "<li><p>body elements</p></li>" or "<li>just text</li>" or even "<li>text<p>and body elements</p>combined</li>", each with different effects. It would be best to stick with strict body elements in list items, but they - affect vertical spacing in browsers (although they really + affect vertical spacing in older browsers (although they really shouldn't). + The html5_polyglot writer solves this using CSS2. Here is an outline of the optimization: @@ -243,291 +187,46 @@ class HTMLTranslator(nodes.NodeVisitor): option) disables list whitespace optimization. """ - xml_declaration = '<?xml version="1.0" encoding="%s" ?>\n' + # The following definitions are required for display in browsers limited + # to CSS1 or backwards compatible behaviour of the writer: + doctype = ( '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"' ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n') - doctype_mathml = doctype - head_prefix_template = ('<html xmlns="http://www.w3.org/1999/xhtml"' - ' xml:lang="%(lang)s" lang="%(lang)s">\n<head>\n') content_type = ('<meta http-equiv="Content-Type"' ' content="text/html; charset=%s" />\n') content_type_mathml = ('<meta http-equiv="Content-Type"' ' content="application/xhtml+xml; charset=%s" />\n') - generator = ('<meta name="generator" content="Docutils %s: ' - 'http://docutils.sourceforge.net/" />\n') - - # 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/ - mathjax_url = ('http://cdn.mathjax.org/mathjax/latest/MathJax.js?' - 'config=TeX-AMS-MML_HTMLorMML') - # may be overwritten by custom URL appended to "mathjax" - - 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 - lang_attribute = 'lang' # name changes to 'xml:lang' in XHTML 1.1 - - def __init__(self, document): - nodes.NodeVisitor.__init__(self, document) - self.settings = settings = document.settings - lcode = settings.language_code - self.language = languages.get_language(lcode, document.reporter) - self.meta = [self.generator % docutils.__version__] - self.head_prefix = [] - self.html_prolog = [] - if settings.xml_declaration: - self.head_prefix.append(self.xml_declaration - % settings.output_encoding) - # encoding not interpolated: - self.html_prolog.append(self.xml_declaration) - self.head = self.meta[:] - self.stylesheet = [self.stylesheet_call(path) - for path in utils.get_stylesheet_list(settings)] - self.body_prefix = ['</head>\n<body>\n'] - # document title, subtitle display - self.body_pre_docinfo = [] - # author, date, etc. - self.docinfo = [] - self.body = [] - self.fragment = [] - self.body_suffix = ['</body>\n</html>\n'] - self.section_level = 0 - self.initial_header_level = int(settings.initial_header_level) - - self.math_output = settings.math_output.split() - 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 = [] - self.topic_classes = [] - self.colspecs = [] - self.compact_p = True - self.compact_simple = False - self.compact_field_list = False - self.in_docinfo = False - self.in_sidebar = False - self.title = [] - self.subtitle = [] - self.header = [] - self.footer = [] - self.html_head = [self.content_type] # charset not interpolated - self.html_title = [] - self.html_subtitle = [] - self.html_body = [] - self.in_document_title = 0 # len(self.body) or 0 - self.in_mailto = False - self.author_in_authors = False - self.math_header = [] - - def astext(self): - return ''.join(self.head_prefix + self.head - + self.stylesheet + self.body_prefix - + self.body_pre_docinfo + self.docinfo - + self.body + self.body_suffix) - - def encode(self, text): - """Encode special characters in `text` & return.""" - # @@@ A codec to do these and all other HTML entities would be nice. - text = unicode(text) - return text.translate({ - ord('&'): u'&', - ord('<'): u'<', - ord('"'): u'"', - ord('>'): u'>', - ord('@'): u'@', # may thwart some address harvesters - # TODO: convert non-breaking space only if needed? - 0xa0: u' '}) # non-breaking space - - def cloak_mailto(self, uri): - """Try to hide a mailto: URL from harvesters.""" - # Encode "@" using a URL octet reference (see RFC 1738). - # Further cloaking with HTML entities will be done in the - # `attval` function. - return uri.replace('@', '%40') - - def cloak_email(self, addr): - """Try to hide the link text of a email link from harversters.""" - # Surround at-signs and periods with <span> tags. ("@" has - # already been encoded to "@" by the `encode` method.) - addr = addr.replace('@', '<span>@</span>') - addr = addr.replace('.', '<span>.</span>') - return addr - - def attval(self, text, - whitespace=re.compile('[\n\r\t\v\f]')): - """Cleanse, HTML encode, and return attribute value text.""" - encoded = self.encode(whitespace.sub(' ', text)) - if self.in_mailto and self.settings.cloak_email_addresses: - # Cloak at-signs ("%40") and periods with HTML entities. - encoded = encoded.replace('%40', '%40') - encoded = encoded.replace('.', '.') - return encoded - - def stylesheet_call(self, path): - """Return code to reference or embed stylesheet file `path`""" - if self.settings.embed_stylesheet: - try: - content = io.FileInput(source_path=path, - encoding='utf-8').read() - self.settings.record_dependencies.add(path) - except IOError, err: - msg = u"Cannot embed stylesheet '%s': %s." % ( - path, SafeString(err.strerror)) - self.document.reporter.error(msg) - return '<--- %s --->\n' % msg - return self.embedded_stylesheet % content - # else link to style file: - if self.settings.stylesheet_path: - # adapt path relative to output (cf. config.html#stylesheet-path) - path = utils.relative_path(self.settings._destination, path) - return self.stylesheet_link % self.encode(path) - - def starttag(self, node, tagname, suffix='\n', empty=False, **attributes): - """ - Construct and return a start tag given a node (id & class attributes - are extracted), tag name, and optional attributes. - """ - tagname = tagname.lower() - prefix = [] - atts = {} - ids = [] - for (name, value) in attributes.items(): - atts[name.lower()] = value - classes = [] - languages = [] - # unify class arguments and move language specification - for cls in node.get('classes', []) + atts.pop('class', '').split() : - if cls.startswith('language-'): - languages.append(cls[9:]) - elif cls.strip() and cls not in classes: - classes.append(cls) - if languages: - # attribute name is 'lang' in XHTML 1.0 but 'xml:lang' in 1.1 - atts[self.lang_attribute] = languages[0] - if classes: - atts['class'] = ' '.join(classes) - assert 'id' not in atts - ids.extend(node.get('ids', [])) - if 'ids' in atts: - ids.extend(atts['ids']) - del atts['ids'] - if ids: - atts['id'] = ids[0] - for id in ids[1:]: - # Add empty "span" elements for additional IDs. Note - # that we cannot use empty "a" elements because there - # may be targets inside of references, but nested "a" - # elements aren't allowed in XHTML (even if they do - # not all have a "href" attribute). - if empty: - # Empty tag. Insert target right in front of element. - prefix.append('<span id="%s"></span>' % id) - else: - # Non-empty tag. Place the auxiliary <span> tag - # *inside* the element, as the first child. - suffix += '<span id="%s"></span>' % id - attlist = atts.items() - attlist.sort() - parts = [tagname] - for name, value in attlist: - # value=None was used for boolean attributes without - # value, but this isn't supported by XHTML. - assert value is not None - if isinstance(value, list): - values = [unicode(v) for v in value] - parts.append('%s="%s"' % (name.lower(), - self.attval(' '.join(values)))) - else: - parts.append('%s="%s"' % (name.lower(), - self.attval(unicode(value)))) - if empty: - infix = ' /' - else: - infix = '' - return ''.join(prefix) + '<%s%s>' % (' '.join(parts), infix) + suffix + # encode also non-breaking space + special_characters = dict(_html_base.HTMLTranslator.special_characters) + special_characters[0xa0] = u' ' - def emptytag(self, node, tagname, suffix='\n', **attributes): - """Construct and return an XML-compatible empty tag.""" - return self.starttag(node, tagname, suffix, empty=True, **attributes) - - def set_class_on_child(self, node, class_, index=0): - """ - Set class `class_` on the visible child no. index of `node`. - Do nothing if node has fewer children than `index`. - """ - children = [n for n in node if not isinstance(n, nodes.Invisible)] - try: - child = children[index] - except IndexError: - return - child['classes'].append(class_) + # use character reference for dash (not valid in HTML5) + attribution_formats = {'dash': ('—', ''), + 'parentheses': ('(', ')'), + 'parens': ('(', ')'), + 'none': ('', '')} + # ersatz for first/last pseudo-classes missing in CSS1 def set_first_last(self, node): self.set_class_on_child(node, 'first', 0) self.set_class_on_child(node, 'last', -1) - def visit_Text(self, node): - text = node.astext() - encoded = self.encode(text) - if self.in_mailto and self.settings.cloak_email_addresses: - encoded = self.cloak_email(encoded) - self.body.append(encoded) - - def depart_Text(self, node): - pass - - def visit_abbreviation(self, node): - # @@@ implementation incomplete ("title" attribute) - self.body.append(self.starttag(node, 'abbr', '')) - - def depart_abbreviation(self, node): - self.body.append('</abbr>') - - def visit_acronym(self, node): - # @@@ implementation incomplete ("title" attribute) - self.body.append(self.starttag(node, 'acronym', '')) - - def depart_acronym(self, node): - self.body.append('</acronym>') - + # add newline after opening tag def visit_address(self, node): self.visit_docinfo_item(node, 'address', meta=False) self.body.append(self.starttag(node, 'pre', CLASS='address')) - def depart_address(self, node): - self.body.append('\n</pre>\n') - self.depart_docinfo_item() + # ersatz for first/last pseudo-classes def visit_admonition(self, node): + node['classes'].insert(0, 'admonition') self.body.append(self.starttag(node, 'div')) self.set_first_last(node) - def depart_admonition(self, node=None): - self.body.append('</div>\n') - - attribution_formats = {'dash': ('—', ''), - 'parentheses': ('(', ')'), - 'parens': ('(', ')'), - 'none': ('', '')} - - def visit_attribution(self, node): - prefix, suffix = self.attribution_formats[self.settings.attribution] - self.context.append(suffix) - self.body.append( - self.starttag(node, 'p', prefix, CLASS='attribution')) - - def depart_attribution(self, node): - self.body.append(self.context.pop() + '</p>\n') - + # author, authors: use <br> instead of paragraphs def visit_author(self, node): if isinstance(node.parent, nodes.authors): if self.author_in_authors: @@ -548,21 +247,8 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_authors(self, node): self.depart_docinfo_item() - def visit_block_quote(self, node): - self.body.append(self.starttag(node, 'blockquote')) - - def depart_block_quote(self, node): - self.body.append('</blockquote>\n') - - def check_simple_list(self, node): - """Check for a simple list that can be rendered compactly.""" - visitor = SimpleListChecker(self.document) - try: - node.walk(visitor) - except nodes.NodeFound: - return None - else: - return 1 + # Compact lists: + # exclude definition lists and field lists (non-compact by default) def is_compactable(self, node): return ('compact' in node['classes'] @@ -570,28 +256,10 @@ class HTMLTranslator(nodes.NodeVisitor): and 'open' not in node['classes'] and (self.compact_simple or self.topic_classes == ['contents'] + # TODO: self.in_contents or self.check_simple_list(node)))) - def visit_bullet_list(self, node): - atts = {} - old_compact_simple = self.compact_simple - self.context.append((self.compact_simple, self.compact_p)) - self.compact_p = None - self.compact_simple = self.is_compactable(node) - if self.compact_simple and not old_compact_simple: - atts['class'] = 'simple' - self.body.append(self.starttag(node, 'ul', **atts)) - - def depart_bullet_list(self, node): - self.compact_simple, self.compact_p = self.context.pop() - self.body.append('</ul>\n') - - def visit_caption(self, node): - self.body.append(self.starttag(node, 'p', '', CLASS='caption')) - - def depart_caption(self, node): - self.body.append('</p>\n') - + # citations: Use table for bibliographic references. def visit_citation(self, node): self.body.append(self.starttag(node, 'table', CLASS='docutils citation', @@ -605,32 +273,12 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</td></tr>\n' '</tbody>\n</table>\n') - def visit_citation_reference(self, node): - href = '#' - if 'refid' in node: - href += node['refid'] - elif 'refname' in node: - href += self.document.nameids[node['refname']] - # else: # TODO system message (or already in the transform)? - # 'Citation reference missing.' - self.body.append(self.starttag( - node, 'a', '[', CLASS='citation-reference', href=href)) - - def depart_citation_reference(self, node): - self.body.append(']</a>') - + # insert classifier-delimiter (not required with CSS2) def visit_classifier(self, node): self.body.append(' <span class="classifier-delimiter">:</span> ') self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) - def depart_classifier(self, node): - self.body.append('</span>') - - 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')) - + # rewritten in _html_base (support for "auto" width) def depart_colspec(self, node): pass @@ -644,74 +292,17 @@ class HTMLTranslator(nodes.NodeVisitor): width='%i%%' % colwidth)) self.colspecs = [] - def visit_comment(self, node, - sub=re.compile('-(?=-)').sub): - """Escape double-dashes in comment text.""" - self.body.append('<!-- %s -->\n' % sub('- ', node.astext())) - # Content already processed: - raise nodes.SkipNode - - def visit_compound(self, node): - self.body.append(self.starttag(node, 'div', CLASS='compound')) - if len(node) > 1: - node[0]['classes'].append('compound-first') - node[-1]['classes'].append('compound-last') - for child in node[1:-1]: - child['classes'].append('compound-middle') - - def depart_compound(self, node): - self.body.append('</div>\n') - - def visit_container(self, node): - self.body.append(self.starttag(node, 'div', CLASS='container')) - - def depart_container(self, node): - self.body.append('</div>\n') - - def visit_contact(self, node): - self.visit_docinfo_item(node, 'contact', meta=False) - - def depart_contact(self, node): - self.depart_docinfo_item() - - def visit_copyright(self, node): - self.visit_docinfo_item(node, 'copyright') - - def depart_copyright(self, node): - self.depart_docinfo_item() - - def visit_date(self, node): - self.visit_docinfo_item(node, 'date') - - def depart_date(self, node): - self.depart_docinfo_item() - - def visit_decoration(self, node): - pass - - def depart_decoration(self, node): - pass - + # ersatz for first/last pseudo-classes def visit_definition(self, node): self.body.append('</dt>\n') self.body.append(self.starttag(node, 'dd', '')) self.set_first_last(node) - def depart_definition(self, node): - self.body.append('</dd>\n') - + # don't add "simple" class value def visit_definition_list(self, node): self.body.append(self.starttag(node, 'dl', CLASS='docutils')) - def depart_definition_list(self, node): - self.body.append('</dl>\n') - - def visit_definition_list_item(self, node): - pass - - def depart_definition_list_item(self, node): - pass - + # use a table for description lists def visit_description(self, node): self.body.append(self.starttag(node, 'td', '')) self.set_first_last(node) @@ -719,6 +310,7 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_description(self, node): self.body.append('</td>') + # use table for docinfo def visit_docinfo(self, node): self.context.append(len(self.body)) self.body.append(self.starttag(node, 'table', @@ -753,77 +345,22 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_docinfo_item(self): self.body.append('</td></tr>\n') + # add newline after opening tag def visit_doctest_block(self, node): self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) - def depart_doctest_block(self, node): - self.body.append('\n</pre>\n') - - def visit_document(self, node): - self.head.append('<title>%s</title>\n' - % self.encode(node.get('title', ''))) - - def depart_document(self, node): - self.head_prefix.extend([self.doctype, - self.head_prefix_template % - {'lang': self.settings.language_code}]) - self.html_prolog.append(self.doctype) - self.meta.insert(0, self.content_type % self.settings.output_encoding) - self.head.insert(0, self.content_type % self.settings.output_encoding) - if self.math_header: - if self.math_output == 'mathjax': - self.head.extend(self.math_header) - else: - self.stylesheet.extend(self.math_header) - # skip content-type meta tag with interpolated charset value: - self.html_head.extend(self.head[1:]) - self.body_prefix.append(self.starttag(node, 'div', CLASS='document')) - self.body_suffix.insert(0, '</div>\n') - self.fragment.extend(self.body) # self.fragment is the "naked" body - self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo - + self.docinfo + self.body - + self.body_suffix[:-1]) - assert not self.context, 'len(context) = %s' % len(self.context) - - def visit_emphasis(self, node): - self.body.append(self.starttag(node, 'em', '')) - - def depart_emphasis(self, node): - self.body.append('</em>') - + # insert an NBSP into empty cells, ersatz for first/last def visit_entry(self, node): - atts = {'class': []} - if isinstance(node.parent.parent, nodes.thead): - atts['class'].append('head') - if node.parent.parent.parent.stubs[node.parent.column]: - # "stubs" list is an attribute of the tgroup element - atts['class'].append('stub') - if atts['class']: - tagname = 'th' - atts['class'] = ' '.join(atts['class']) - else: - tagname = 'td' - del atts['class'] - node.parent.column += 1 - if 'morerows' in node: - atts['rowspan'] = node['morerows'] + 1 - if 'morecols' in node: - atts['colspan'] = node['morecols'] + 1 - node.parent.column += node['morecols'] - self.body.append(self.starttag(node, tagname, '', **atts)) - self.context.append('</%s>\n' % tagname.lower()) + writers._html_base.HTMLTranslator.visit_entry(self, node) if len(node) == 0: # empty cell self.body.append(' ') self.set_first_last(node) - def depart_entry(self, node): - self.body.append(self.context.pop()) - + # ersatz for first/last pseudo-classes def visit_enumerated_list(self, node): """ The 'start' attribute does not conform to HTML 4.01's strict.dtd, but - CSS1 doesn't help. CSS2 isn't widely enough supported yet to be - usable. + cannot be emulated in CSS1 (HTML 5 reincludes it). """ atts = {} if 'start' in node: @@ -844,6 +381,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.compact_simple, self.compact_p = self.context.pop() self.body.append('</ol>\n') + # use table for field-list: def visit_field(self, node): self.body.append(self.starttag(node, 'tr', '', CLASS='field')) @@ -906,7 +444,7 @@ class HTMLTranslator(nodes.NodeVisitor): and len(node.astext()) > self.settings.field_name_limit): atts['colspan'] = 2 self.context.append('</tr>\n' - + self.starttag(node.parent, 'tr', '', + + self.starttag(node.parent, 'tr', '', CLASS='field') + '<td> </td>') else: @@ -917,30 +455,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append(':</th>') self.body.append(self.context.pop()) - def visit_figure(self, node): - atts = {'class': 'figure'} - if node.get('width'): - atts['style'] = 'width: %s' % node['width'] - if node.get('align'): - atts['class'] += " align-" + node['align'] - self.body.append(self.starttag(node, 'div', **atts)) - - def depart_figure(self, node): - self.body.append('</div>\n') - - def visit_footer(self, node): - self.context.append(len(self.body)) - - def depart_footer(self, node): - start = self.context.pop() - footer = [self.starttag(node, 'div', CLASS='footer'), - '<hr class="footer" />\n'] - footer.extend(self.body[start:]) - footer.append('\n</div>\n') - self.footer.extend(footer) - self.body_suffix[:0] = footer - del self.body[start:] - + # use table for footnote text def visit_footnote(self, node): self.body.append(self.starttag(node, 'table', CLASS='docutils footnote', @@ -960,11 +475,10 @@ class HTMLTranslator(nodes.NodeVisitor): self.context.append('<a class="fn-backref" href="#%s">' % backrefs[0]) else: - i = 1 - for backref in backrefs: + # Python 2.4 fails with enumerate(backrefs, 1) + for (i, backref) in enumerate(backrefs): backlinks.append('<a class="fn-backref" href="#%s">%s</a>' - % (backref, i)) - i += 1 + % (backref, i+1)) self.context.append('<em>(%s)</em> ' % ', '.join(backlinks)) self.context += ['', ''] else: @@ -982,6 +496,7 @@ class HTMLTranslator(nodes.NodeVisitor): self.body.append('</td></tr>\n' '</tbody>\n</table>\n') + # insert markers in text as pseudo-classes are not supported in CSS1: def visit_footnote_reference(self, node): href = '#' + node['refid'] format = self.settings.footnote_references @@ -998,139 +513,34 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_footnote_reference(self, node): self.body.append(self.context.pop() + '</a>') + # just pass on generated text def visit_generated(self, node): pass - def depart_generated(self, node): - pass - - def visit_header(self, node): - self.context.append(len(self.body)) - - def depart_header(self, node): - start = self.context.pop() - header = [self.starttag(node, 'div', CLASS='header')] - header.extend(self.body[start:]) - header.append('\n<hr class="header"/>\n</div>\n') - self.body_prefix.extend(header) - self.header.extend(header) - del self.body[start:] - - def visit_image(self, node): - atts = {} - uri = node['uri'] - # place SVG and SWF images in an <object> element - types = {'.svg': 'image/svg+xml', - '.swf': 'application/x-shockwave-flash'} - ext = os.path.splitext(uri)[1].lower() - if ext in ('.svg', '.swf'): - atts['data'] = uri - atts['type'] = types[ext] - else: - atts['src'] = uri - atts['alt'] = node.get('alt', uri) - # image size - if 'width' in node: - atts['width'] = node['width'] - if 'height' in node: - atts['height'] = node['height'] - if 'scale' in node: - if (PIL and not ('width' in node and 'height' in node) - and self.settings.file_insertion_enabled): - imagepath = urllib.url2pathname(uri) - try: - img = PIL.Image.open( - imagepath.encode(sys.getfilesystemencoding())) - except (IOError, UnicodeEncodeError): - pass # TODO: warn? - else: - self.settings.record_dependencies.add( - imagepath.replace('\\', '/')) - if 'width' not in atts: - atts['width'] = '%dpx' % img.size[0] - if 'height' not in atts: - atts['height'] = '%dpx' % img.size[1] - del img - for att_name in 'width', 'height': - if att_name in atts: - match = re.match(r'([0-9.]+)(\S*)$', atts[att_name]) - assert match - atts[att_name] = '%s%s' % ( - float(match.group(1)) * (float(node['scale']) / 100), - match.group(2)) - style = [] - for att_name in 'width', 'height': - if att_name in atts: - if re.match(r'^[0-9.]+$', atts[att_name]): - # Interpret unitless values as pixels. - atts[att_name] += 'px' - style.append('%s: %s;' % (att_name, atts[att_name])) - del atts[att_name] - if style: - atts['style'] = ' '.join(style) - if (isinstance(node.parent, nodes.TextElement) or - (isinstance(node.parent, nodes.reference) and - not isinstance(node.parent.parent, nodes.TextElement))): - # Inline context or surrounded by <a>...</a>. - suffix = '' - else: - suffix = '\n' - if 'align' in node: - atts['class'] = 'align-%s' % node['align'] - self.context.append('') - if ext in ('.svg', '.swf'): # place in an object element, - # do NOT use an empty tag: incorrect rendering in browsers - self.body.append(self.starttag(node, 'object', suffix, **atts) + - node.get('alt', uri) + '</object>' + suffix) - else: - self.body.append(self.emptytag(node, 'img', suffix, **atts)) - - def depart_image(self, node): - self.body.append(self.context.pop()) - - def visit_inline(self, node): - self.body.append(self.starttag(node, 'span', '')) - - def depart_inline(self, node): - self.body.append('</span>') + # Image types to place in an <object> element + # SVG not supported by IE up to version 8 + # (html4css1 strives for IE6 compatibility) + object_image_types = {'.svg': 'image/svg+xml', + '.swf': 'application/x-shockwave-flash'} + # use table for footnote text, + # context added in footnote_backrefs. def visit_label(self, node): - # Context added in footnote_backrefs. self.body.append(self.starttag(node, 'td', '%s[' % self.context.pop(), CLASS='label')) def depart_label(self, node): - # Context added in footnote_backrefs. self.body.append(']%s</td><td>%s' % (self.context.pop(), self.context.pop())) - def visit_legend(self, node): - self.body.append(self.starttag(node, 'div', CLASS='legend')) - - def depart_legend(self, node): - self.body.append('</div>\n') - - def visit_line(self, node): - self.body.append(self.starttag(node, 'div', suffix='', CLASS='line')) - if not len(node): - self.body.append('<br />') - - def depart_line(self, node): - self.body.append('</div>\n') - - def visit_line_block(self, node): - self.body.append(self.starttag(node, 'div', CLASS='line-block')) - - def depart_line_block(self, node): - self.body.append('</div>\n') + # ersatz for first/last pseudo-classes def visit_list_item(self, node): self.body.append(self.starttag(node, 'li', '')) if len(node): node[0]['classes'].append('first') - def depart_list_item(self, node): - self.body.append('</li>\n') - + # use <tt> (not supported by HTML5), + # cater for limited styling options in CSS1 using hard-coded NBSPs def visit_literal(self, node): # special case: "code" role classes = node.get('classes', []) @@ -1161,137 +571,15 @@ class HTMLTranslator(nodes.NodeVisitor): # Content already processed: raise nodes.SkipNode - def depart_literal(self, node): - # skipped unless literal element is from "code" role: - self.body.append('</code>') - + # add newline after opening tag, don't use <code> for code def visit_literal_block(self, node): self.body.append(self.starttag(node, 'pre', CLASS='literal-block')) + # add newline def depart_literal_block(self, node): self.body.append('\n</pre>\n') - def visit_math(self, node, math_env=''): - # If the method is called from visit_math_block(), math_env != ''. - - # As there is no native HTML math support, we provide alternatives: - # LaTeX and MathJax math_output modes simply wrap the content, - # HTML and MathML math_output modes also convert the math_code. - if self.math_output not in ('mathml', 'html', 'mathjax', 'latex'): - self.document.reporter.error( - 'math-output format "%s" not supported ' - 'falling back to "latex"'% self.math_output) - self.math_output = 'latex' - # - # HTML container - tags = {# math_output: (block, inline, class-arguments) - 'mathml': ('div', '', ''), - 'html': ('div', 'span', 'formula'), - 'mathjax': ('div', 'span', 'math'), - 'latex': ('pre', 'tt', 'math'), - } - tag = tags[self.math_output][math_env == ''] - clsarg = tags[self.math_output][2] - # LaTeX container - wrappers = {# math_mode: (inline, block) - 'mathml': (None, None), - 'html': ('$%s$', u'\\begin{%s}\n%s\n\\end{%s}'), - 'mathjax': ('\(%s\)', u'\\begin{%s}\n%s\n\\end{%s}'), - 'latex': (None, None), - } - wrapper = wrappers[self.math_output][math_env != ''] - # get and wrap content - math_code = node.astext().translate(unichar2tex.uni2tex_table) - if wrapper and math_env: - math_code = wrapper % (math_env, math_code, math_env) - elif wrapper: - math_code = wrapper % math_code - # settings and conversion - 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: - self.mathjax_url = self.math_output_options[0] - self.math_header = [self.mathjax_script % self.mathjax_url] - elif self.math_output == 'html': - if self.math_output_options and not self.math_header: - self.math_header = [self.stylesheet_call( - utils.find_file_in_dirs(s, self.settings.stylesheet_dirs)) - for s in self.math_output_options[0].split(',')] - # TODO: fix display mode in matrices and fractions - math2html.DocumentParameters.displaymode = (math_env != '') - math_code = math2html.math2html(math_code) - elif self.math_output == 'mathml': - self.doctype = self.doctype_mathml - self.content_type = self.content_type_mathml - try: - mathml_tree = parse_latex_math(math_code, inline=not(math_env)) - math_code = ''.join(mathml_tree.xml()) - except SyntaxError, err: - err_node = self.document.reporter.error(err, base_node=node) - self.visit_system_message(err_node) - self.body.append(self.starttag(node, 'p')) - self.body.append(u','.join(err.args)) - self.body.append('</p>\n') - self.body.append(self.starttag(node, 'pre', - CLASS='literal-block')) - self.body.append(self.encode(math_code)) - self.body.append('\n</pre>\n') - self.depart_system_message(err_node) - raise nodes.SkipNode - # append to document body - if tag: - self.body.append(self.starttag(node, tag, - suffix='\n'*bool(math_env), - CLASS=clsarg)) - self.body.append(math_code) - if math_env: # block mode (equation, display) - self.body.append('\n') - if tag: - self.body.append('</%s>' % tag) - if math_env: - self.body.append('\n') - # Content already processed: - raise nodes.SkipNode - - def depart_math(self, node): - pass # never reached - - def visit_math_block(self, node): - # print node.astext().encode('utf8') - math_env = pick_math_environment(node.astext()) - self.visit_math(node, math_env=math_env) - - def depart_math_block(self, node): - pass # never reached - - def visit_meta(self, node): - meta = self.emptytag(node, 'meta', **node.non_default_attributes()) - self.add_meta(meta) - - def depart_meta(self, node): - pass - - def add_meta(self, tag): - self.meta.append(tag) - self.head.append(tag) - - def visit_option(self, node): - if self.context[-1]: - self.body.append(', ') - self.body.append(self.starttag(node, 'span', '', CLASS='option')) - - def depart_option(self, node): - self.body.append('</span>') - self.context[-1] += 1 - - def visit_option_argument(self, node): - self.body.append(node.get('delimiter', ' ')) - self.body.append(self.starttag(node, 'var', '')) - - def depart_option_argument(self, node): - self.body.append('</var>') - + # use table for option list def visit_option_group(self, node): atts = {} if ( self.settings.option_limit @@ -1327,18 +615,8 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_option_list_item(self, node): self.body.append('</tr>\n') - def visit_option_string(self, node): - pass - - def depart_option_string(self, node): - pass - - def visit_organization(self, node): - self.visit_docinfo_item(node, 'organization') - - def depart_organization(self, node): - self.depart_docinfo_item() - + # Omit <p> tags to produce visually compact lists (less vertical + # whitespace) as CSS styling requires CSS2. def should_be_compact_paragraph(self, node): """ Determine if the <p> tags around paragraph ``node`` can be omitted. @@ -1379,117 +657,28 @@ class HTMLTranslator(nodes.NodeVisitor): def depart_paragraph(self, node): self.body.append(self.context.pop()) - def visit_problematic(self, node): - if node.hasattr('refid'): - self.body.append('<a href="#%s">' % node['refid']) - self.context.append('</a>') - else: - self.context.append('') - self.body.append(self.starttag(node, 'span', '', CLASS='problematic')) - - def depart_problematic(self, node): - self.body.append('</span>') - self.body.append(self.context.pop()) - - def visit_raw(self, node): - if 'html' in node.get('format', '').split(): - t = isinstance(node.parent, nodes.TextElement) and 'span' or 'div' - if node['classes']: - self.body.append(self.starttag(node, t, suffix='')) - self.body.append(node.astext()) - if node['classes']: - self.body.append('</%s>' % t) - # Keep non-HTML raw text out of output: - raise nodes.SkipNode - - def visit_reference(self, node): - atts = {'class': 'reference'} - if 'refuri' in node: - atts['href'] = node['refuri'] - if ( self.settings.cloak_email_addresses - and atts['href'].startswith('mailto:')): - atts['href'] = self.cloak_mailto(atts['href']) - self.in_mailto = True - atts['class'] += ' external' - else: - assert 'refid' in node, \ - 'References must have "refuri" or "refid" attribute.' - atts['href'] = '#' + node['refid'] - atts['class'] += ' internal' - if not isinstance(node.parent, nodes.TextElement): - assert len(node) == 1 and isinstance(node[0], nodes.image) - atts['class'] += ' image-reference' - self.body.append(self.starttag(node, 'a', '', **atts)) - - def depart_reference(self, node): - self.body.append('</a>') - if not isinstance(node.parent, nodes.TextElement): - self.body.append('\n') - self.in_mailto = False - - def visit_revision(self, node): - self.visit_docinfo_item(node, 'revision', meta=False) - - def depart_revision(self, node): - self.depart_docinfo_item() - - def visit_row(self, node): - self.body.append(self.starttag(node, 'tr', '')) - node.column = 0 - - def depart_row(self, node): - self.body.append('</tr>\n') - - def visit_rubric(self, node): - self.body.append(self.starttag(node, 'p', '', CLASS='rubric')) - - def depart_rubric(self, node): - self.body.append('</p>\n') - - def visit_section(self, node): - self.section_level += 1 - self.body.append( - self.starttag(node, 'div', CLASS='section')) - - def depart_section(self, node): - self.section_level -= 1 - self.body.append('</div>\n') - + # ersatz for first/last pseudo-classes def visit_sidebar(self, node): self.body.append( self.starttag(node, 'div', CLASS='sidebar')) self.set_first_last(node) self.in_sidebar = True - def depart_sidebar(self, node): - self.body.append('</div>\n') - self.in_sidebar = False - - def visit_status(self, node): - self.visit_docinfo_item(node, 'status', meta=False) - - def depart_status(self, node): - self.depart_docinfo_item() - - def visit_strong(self, node): - self.body.append(self.starttag(node, 'strong', '')) - - def depart_strong(self, node): - self.body.append('</strong>') - + # <sub> not allowed in <pre> def visit_subscript(self, node): - self.body.append(self.starttag(node, 'sub', '')) + if isinstance(node.parent, nodes.literal_block): + self.body.append(self.starttag(node, 'span', '', + CLASS='subscript')) + else: + self.body.append(self.starttag(node, 'sub', '')) def depart_subscript(self, node): - self.body.append('</sub>') - - def visit_substitution_definition(self, node): - """Internal only.""" - raise nodes.SkipNode - - def visit_substitution_reference(self, node): - self.unimplemented_visit(node) + if isinstance(node.parent, nodes.literal_block): + self.body.append('</span>') + else: + self.body.append('</sub>') + # Use <h*> for subtitles (deprecated in HTML 5) def visit_subtitle(self, node): if isinstance(node.parent, nodes.sidebar): self.body.append(self.starttag(node, 'p', '', @@ -1515,12 +704,21 @@ class HTMLTranslator(nodes.NodeVisitor): self.html_subtitle.extend(self.body) del self.body[:] + # <sup> not allowed in <pre> in HTML 4 def visit_superscript(self, node): - self.body.append(self.starttag(node, 'sup', '')) + if isinstance(node.parent, nodes.literal_block): + self.body.append(self.starttag(node, 'span', '', + CLASS='superscript')) + else: + self.body.append(self.starttag(node, 'sup', '')) def depart_superscript(self, node): - self.body.append('</sup>') + if isinstance(node.parent, nodes.literal_block): + self.body.append('</span>') + else: + self.body.append('</sup>') + # <tt> element deprecated in HTML 5 def visit_system_message(self, node): self.body.append(self.starttag(node, 'div', CLASS='system-message')) self.body.append('<p class="system-message-title">') @@ -1547,59 +745,34 @@ class HTMLTranslator(nodes.NodeVisitor): % (node['type'], node['level'], self.encode(node['source']), line, backref_text)) - def depart_system_message(self, node): - self.body.append('</div>\n') - + # "hard coded" border setting def visit_table(self, node): self.context.append(self.compact_p) self.compact_p = True - classes = ' '.join(['docutils', self.settings.table_style]).strip() + classes = ['docutils', self.settings.table_style] + if 'align' in node: + classes.append('align-%s' % node['align']) self.body.append( - self.starttag(node, 'table', CLASS=classes, border="1")) + self.starttag(node, 'table', CLASS=' '.join(classes), border="1")) def depart_table(self, node): self.compact_p = self.context.pop() self.body.append('</table>\n') - def visit_target(self, node): - if not ('refuri' in node or 'refid' in node - or 'refname' in node): - self.body.append(self.starttag(node, 'span', '', CLASS='target')) - self.context.append('</span>') - else: - self.context.append('') - - def depart_target(self, node): - self.body.append(self.context.pop()) - + # 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') - - def visit_term(self, node): - self.body.append(self.starttag(node, 'dt', '')) - - def depart_term(self, node): - """ - Leave the end tag to `self.visit_definition()`, in case there's a - classifier. - """ - pass - + # rewritten in _html_base def visit_tgroup(self, node): - # Mozilla needs <colgroup>: self.body.append(self.starttag(node, 'colgroup')) # Appended by thead or tbody: self.context.append('</colgroup>\n') node.stubs = [] - def depart_tgroup(self, node): - pass - + # rewritten in _html_base def visit_thead(self, node): self.write_colspecs() self.body.append(self.context.pop()) # '</colgroup>\n' @@ -1607,91 +780,8 @@ class HTMLTranslator(nodes.NodeVisitor): self.context.append('') self.body.append(self.starttag(node, 'thead', valign='bottom')) - def depart_thead(self, node): - self.body.append('</thead>\n') - - def visit_title(self, node): - """Only 6 section levels are supported by HTML.""" - check_id = 0 # TODO: is this a bool (False) or a counter? - close_tag = '</p>\n' - if isinstance(node.parent, nodes.topic): - self.body.append( - self.starttag(node, 'p', '', CLASS='topic-title first')) - elif isinstance(node.parent, nodes.sidebar): - self.body.append( - self.starttag(node, 'p', '', CLASS='sidebar-title')) - elif isinstance(node.parent, nodes.Admonition): - self.body.append( - self.starttag(node, 'p', '', CLASS='admonition-title')) - elif isinstance(node.parent, nodes.table): - self.body.append( - self.starttag(node, 'caption', '')) - close_tag = '</caption>\n' - elif isinstance(node.parent, nodes.document): - self.body.append(self.starttag(node, 'h1', '', CLASS='title')) - close_tag = '</h1>\n' - self.in_document_title = len(self.body) - else: - assert isinstance(node.parent, nodes.section) - h_level = self.section_level + self.initial_header_level - 1 - atts = {} - if (len(node.parent) >= 2 and - isinstance(node.parent[1], nodes.subtitle)): - atts['CLASS'] = 'with-subtitle' - self.body.append( - self.starttag(node, 'h%s' % h_level, '', **atts)) - atts = {} - if node.hasattr('refid'): - atts['class'] = 'toc-backref' - atts['href'] = '#' + node['refid'] - if atts: - self.body.append(self.starttag({}, 'a', '', **atts)) - close_tag = '</a></h%s>\n' % (h_level) - else: - close_tag = '</h%s>\n' % (h_level) - self.context.append(close_tag) - - def depart_title(self, node): - self.body.append(self.context.pop()) - if self.in_document_title: - self.title = self.body[self.in_document_title:-1] - self.in_document_title = 0 - self.body_pre_docinfo.extend(self.body) - self.html_title.extend(self.body) - del self.body[:] - - def visit_title_reference(self, node): - self.body.append(self.starttag(node, 'cite', '')) - def depart_title_reference(self, node): - self.body.append('</cite>') - - def visit_topic(self, node): - self.body.append(self.starttag(node, 'div', CLASS='topic')) - self.topic_classes = node['classes'] - - def depart_topic(self, node): - self.body.append('</div>\n') - self.topic_classes = [] - - def visit_transition(self, node): - self.body.append(self.emptytag(node, 'hr', CLASS='docutils')) - - def depart_transition(self, node): - pass - - def visit_version(self, node): - self.visit_docinfo_item(node, 'version', meta=False) - - def depart_version(self, node): - self.depart_docinfo_item() - - def unimplemented_visit(self, node): - raise NotImplementedError('visiting unimplemented node type: %s' - % node.__class__.__name__) - - -class SimpleListChecker(nodes.GenericNodeVisitor): +class SimpleListChecker(writers._html_base.SimpleListChecker): """ Raise `nodes.NodeFound` if non-simple list item is encountered. @@ -1700,15 +790,6 @@ class SimpleListChecker(nodes.GenericNodeVisitor): paragraph, a simple list, or a paragraph followed by a simple list. """ - def default_visit(self, node): - raise nodes.NodeFound - - def visit_bullet_list(self, node): - pass - - def visit_enumerated_list(self, node): - pass - def visit_list_item(self, node): children = [] for child in node.children: @@ -1723,14 +804,20 @@ class SimpleListChecker(nodes.GenericNodeVisitor): else: raise nodes.NodeFound - def visit_paragraph(self, node): - raise nodes.SkipNode + # def visit_bullet_list(self, node): + # pass - def invisible_visit(self, node): - """Invisible nodes should be ignored.""" - raise nodes.SkipNode + # def visit_enumerated_list(self, node): + # pass - visit_comment = invisible_visit - visit_substitution_definition = invisible_visit - visit_target = invisible_visit - visit_pending = invisible_visit + # def visit_paragraph(self, node): + # raise nodes.SkipNode + + def visit_definition_list(self, node): + raise nodes.NodeFound + + def visit_docinfo(self, node): + raise nodes.NodeFound + + def visit_definition_list(self, node): + raise nodes.NodeFound diff --git a/docutils/src/main/resources/docutils/docutils/writers/html4css1/html4css1.css b/docutils/src/main/resources/docutils/docutils/writers/html4css1/html4css1.css index 39a0369..427d33e 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/html4css1/html4css1.css +++ b/docutils/src/main/resources/docutils/docutils/writers/html4css1/html4css1.css @@ -1,6 +1,6 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 7614 2013-02-21 15:55:51Z milde $ +:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. @@ -28,6 +28,14 @@ table.borderless td, table.borderless th { .hidden { display: none } +.subscript { + vertical-align: sub; + font-size: smaller } + +.superscript { + vertical-align: super; + font-size: smaller } + a.toc-backref { text-decoration: none ; color: black } @@ -152,12 +160,12 @@ h2.subtitle { hr.docutils { width: 75% } -img.align-left, .figure.align-left, object.align-left { +img.align-left, .figure.align-left, object.align-left, table.align-left { clear: left ; float: left ; margin-right: 1em } -img.align-right, .figure.align-right, object.align-right { +img.align-right, .figure.align-right, object.align-right, table.align-right { clear: right ; float: right ; margin-left: 1em } @@ -168,6 +176,11 @@ img.align-center, .figure.align-center, object.align-center { margin-right: auto; } +table.align-center { + margin-left: auto; + margin-right: auto; +} + .align-left { text-align: left } @@ -185,6 +198,15 @@ div.align-right { /* div.align-center * { */ /* text-align: left } */ +.align-top { + vertical-align: top } + +.align-middle { + vertical-align: middle } + +.align-bottom { + vertical-align: bottom } + ol.simple, ul.simple { margin-bottom: 1em } 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 new file mode 100644 index 0000000..c14c5ee --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/__init__.py @@ -0,0 +1,200 @@ +# .. coding: utf8 +# :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: +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. +# This file is offered as-is, without any warranty. +# +# .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause + +# Use "best practice" as recommended by the W3C: +# http://www.w3.org/2009/cheatsheet/ + +""" +Plain HyperText Markup Language document tree Writer. + +The output conforms to the `HTML 5` specification. + +The cascading style sheet "minimal.css" is required for proper viewing, +the style sheet "plain.css" improves reading experience. +""" +__docformat__ = 'reStructuredText' + +import os.path +import docutils +from docutils import frontend, nodes, writers, io +from docutils.transforms import writer_aux +from docutils.writers import _html_base + +class Writer(writers._html_base.Writer): + + supported = ('html', 'html5', 'html4', 'xhtml', 'xhtml10') + """Formats this writer supports.""" + + default_stylesheets = ['minimal.css','plain.css'] + default_stylesheet_dirs = ['.', os.path.abspath(os.path.dirname(__file__))] + + default_template = 'template.txt' + default_template_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), default_template) + + settings_spec = ( + 'HTML-Specific Options', + None, + (('Specify the template file (UTF-8 encoded). Default is "%s".' + % default_template_path, + ['--template'], + {'default': default_template_path, 'metavar': '<file>'}), + ('Comma separated list of stylesheet URLs. ' + 'Overrides previous --stylesheet and --stylesheet-path settings.', + ['--stylesheet'], + {'metavar': '<URL[,URL,...]>', 'overrides': 'stylesheet_path', + 'validator': frontend.validate_comma_separated_list}), + ('Comma separated list of stylesheet paths. ' + 'Relative paths are expanded if a matching file is found in ' + 'the --stylesheet-dirs. With --link-stylesheet, ' + 'the path is rewritten relative to the output HTML file. ' + 'Default: "%s"' % ','.join(default_stylesheets), + ['--stylesheet-path'], + {'metavar': '<file[,file,...]>', 'overrides': 'stylesheet', + 'validator': frontend.validate_comma_separated_list, + 'default': default_stylesheets}), + ('Embed the stylesheet(s) in the output HTML file. The stylesheet ' + 'files must be accessible during processing. This is the default.', + ['--embed-stylesheet'], + {'default': 1, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Link to the stylesheet(s) in the output HTML file. ' + 'Default: embed stylesheets.', + ['--link-stylesheet'], + {'dest': 'embed_stylesheet', 'action': 'store_false'}), + ('Comma-separated list of directories where stylesheets are found. ' + 'Used by --stylesheet-path when expanding relative path arguments. ' + 'Default: "%s"' % default_stylesheet_dirs, + ['--stylesheet-dirs'], + {'metavar': '<dir[,dir,...]>', + 'validator': frontend.validate_comma_separated_list, + 'default': default_stylesheet_dirs}), + ('Specify the initial header level. Default is 1 for "<h1>". ' + 'Does not affect document title & subtitle (see --no-doc-title).', + ['--initial-header-level'], + {'choices': '1 2 3 4 5 6'.split(), 'default': '1', + 'metavar': '<level>'}), + ('Format for footnote references: one of "superscript" or ' + '"brackets". Default is "brackets".', + ['--footnote-references'], + {'choices': ['superscript', 'brackets'], 'default': 'brackets', + 'metavar': '<format>', + 'overrides': 'trim_footnote_reference_space'}), + ('Format for block quote attributions: one of "dash" (em-dash ' + 'prefix), "parentheses"/"parens", or "none". Default is "dash".', + ['--attribution'], + {'choices': ['dash', 'parentheses', 'parens', 'none'], + 'default': 'dash', 'metavar': '<format>'}), + ('Remove extra vertical whitespace between items of "simple" bullet ' + 'lists and enumerated lists. Default: enabled.', + ['--compact-lists'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Disable compact simple bullet and enumerated lists.', + ['--no-compact-lists'], + {'dest': 'compact_lists', 'action': 'store_false'}), + ('Remove extra vertical whitespace between items of simple field ' + 'lists. Default: enabled.', + ['--compact-field-lists'], + {'default': True, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Disable compact simple field lists.', + ['--no-compact-field-lists'], + {'dest': 'compact_field_lists', 'action': 'store_false'}), + ('Added to standard table classes. ' + 'Defined styles: borderless, booktabs, ' + 'align-left, align-center, align-right, colwidths-auto. ' + 'Default: ""', + ['--table-style'], + {'default': ''}), + ('Math output format (one of "MathML", "HTML", "MathJax", ' + 'or "LaTeX") and option(s). ' + 'Default: "HTML math.css"', + ['--math-output'], + {'default': 'HTML math.css'}), + ('Prepend an XML declaration. (Thwarts HTML5 conformance.) ' + 'Default: False', + ['--xml-declaration'], + {'default': False, 'action': 'store_true', + 'validator': frontend.validate_boolean}), + ('Omit the XML declaration.', + ['--no-xml-declaration'], + {'dest': 'xml_declaration', 'action': 'store_false'}), + ('Obfuscate email addresses to confuse harvesters while still ' + 'keeping email links usable with standards-compliant browsers.', + ['--cloak-email-addresses'], + {'action': 'store_true', 'validator': frontend.validate_boolean}),)) + + config_section = 'html-plain writer' + + def __init__(self): + self.parts = {} + self.translator_class = HTMLTranslator + + +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) + + + # <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 + def visit_authors(self, node): + self.visit_docinfo_item(node, 'authors', meta=False) + + def visit_copyright(self, node): + self.visit_docinfo_item(node, 'copyright', meta=False) + + # no meta tag in HTML 5 + def visit_date(self, node): + self.visit_docinfo_item(node, 'date', meta=False) + + # TODO: use HTML 5 <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): + + # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 + # HTML5/polyglot recommends using both + def visit_meta(self, node): + if node.hasattr('lang'): + node['xml:lang'] = node['lang'] + # del(node['lang']) + meta = self.emptytag(node, 'meta', **node.non_default_attributes()) + self.add_meta(meta) + + # no meta tag in HTML 5 + def visit_organization(self, node): + self.visit_docinfo_item(node, 'organization', meta=False) + + # TODO: use the new HTML 5 element <section>? + # def visit_section(self, node): + + # TODO: use the new HTML5 element <aside>? + # def visit_topic(self, node): diff --git a/docutils/src/main/resources/docutils/docutils/writers/html4css1/math.css b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/math.css similarity index 100% rename from docutils/src/main/resources/docutils/docutils/writers/html4css1/math.css rename to docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/math.css 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 new file mode 100644 index 0000000..5eb5b3d --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/minimal.css @@ -0,0 +1,260 @@ +/* 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 $ */ +/* :Copyright: © 2015 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ + +/* This CSS2.1_ stylesheet defines rules for Docutils elements without */ +/* HTML equivalent. It is required to make the document semantic visible. */ +/* */ +/* .. _CSS2.1: http://www.w3.org/TR/CSS2 */ +/* .. _validates: http://jigsaw.w3.org/css-validator/validator$link */ + +/* alignment of text and inline objects inside block objects*/ +.align-left { text-align: left; } +.align-right { text-align: right; } +.align-center { clear: both; text-align: center; } +.align-top { vertical-align: top; } +.align-middle { vertical-align: middle; } +.align-bottom { vertical-align: bottom; } + +/* titles */ +h1.title, p.subtitle { + text-align: center; +} +p.admonition-title, +p.topic-title, +p.sidebar-title, +p.rubric, +p.system-message-title { + font-weight: bold; +} +h1 + p.subtitle, +h1 + p.section-subtitle { + font-size: 1.6em; +} +h2 + p.section-subtitle { font-size: 1.28em; } +p.subtitle, +p.section-subtitle, +p.sidebar-subtitle { + font-weight: bold; + margin-top: -0.5em; +} +p.sidebar-title, +p.rubric { + font-size: larger; +} +p.rubric { color: maroon; } +a.toc-backref { + color: black; + text-decoration: none; } + +/* Warnings, Errors */ +div.caution p.admonition-title, +div.attention p.admonition-title, +div.danger p.admonition-title, +div.error p.admonition-title, +div.warning p.admonition-title, +div.system-messages h1, +div.error, +span.problematic, +p.system-message-title { + color: red; +} + +/* inline literals */ +span.docutils.literal { + font-family: monospace; + white-space: pre-wrap; +} +/* do not wraph at hyphens and similar: */ +.literal > span.pre { white-space: nowrap; } + +/* Lists */ + +/* compact and simple lists: no margin between items */ +.simple li, .compact li, +.simple ul, .compact ul, +.simple ol, .compact ol, +.simple > li p, .compact > li p, +dl.simple > dd, dl.compact > dd { + margin-top: 0; + margin-bottom: 0; +} + +/* Table of Contents */ +div.topic.contents { margin: 0; } +ul.auto-toc { + list-style-type: none; + padding-left: 1.5em; } + +/* Enumerated Lists */ +ol.arabic { list-style: decimal } +ol.loweralpha { list-style: lower-alpha } +ol.upperalpha { list-style: upper-alpha } +ol.lowerroman { list-style: lower-roman } +ol.upperroman { list-style: upper-roman } + +dt span.classifier { font-style: italic } +dt span.classifier:before { + font-style: normal; + margin: 0.5em; + content: ":"; +} + +/* Field Lists and drivatives */ +/* bold field name, content starts on the same line */ +dl.field-list > dt, +dl.option-list > dt, +dl.docinfo > dt, +dl.footnote > dt, +dl.citation > dt { + font-weight: bold; + clear: left; + float: left; + margin: 0; + padding: 0; + padding-right: 0.5em; +} +/* Offset for field content (corresponds to the --field-name-limit option) */ +dl.field-list > dd, +dl.option-list > dd, +dl.docinfo > dd { + margin-left: 9em; /* ca. 14 chars in the test examples */ +} +/* start field-body on a new line after long field names */ +dl.field-list > dd > *:first-child, +dl.option-list > dd > *:first-child +{ + display: inline-block; + width: 100%; + margin: 0; +} +/* field names followed by a colon */ +dl.field-list > dt:after, +dl.docinfo > dt:after { + content: ":"; +} + +/* Bibliographic Fields (docinfo) */ +pre.address { font: inherit; } +dd.authors > p { margin: 0; } + +/* Option Lists */ +dl.option-list { margin-left: 40px; } +dl.option-list > dt { font-weight: normal; } +span.option { white-space: nowrap; } + +/* Footnotes and Citations */ +dl.footnote.superscript > dd {margin-left: 1em; } +dl.footnote.brackets > dd {margin-left: 2em; } +dl > dt.label { font-weight: normal; } +a.footnote-reference.brackets:before, +dt.label > span.brackets:before { content: "["; } +a.footnote-reference.brackets:after, +dt.label > span.brackets:after { content: "]"; } +a.footnote-reference.superscript, +dl.footnote.superscript > dt.label { + vertical-align: super; + font-size: smaller; +} +dt.label > span.fn-backref { margin-left: 0.2em; } +dt.label > span.fn-backref > a { font-style: italic; } + +/* Line Blocks */ +div.line-block { display: block; } +div.line-block div.line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 40px; +} + +/* Figures, Images, and Tables */ +.figure.align-left, +img.align-left, +object.align-left, +table.align-left { + margin-right: auto; +} +.figure.align-center, +img.align-center, +object.align-center { + margin-left: auto; + margin-right: auto; + display: block; +} +table.align-center { + margin-left: auto; + margin-right: auto; +} +.figure.align-right, +img.align-right, +object.align-right, +table.align-right { + margin-left: auto; +} +/* reset inner alignment in figures and tables */ +div.align-left, div.align-center, div.align-right, +table.align-left, table.align-center, table.align-right +{ text-align: inherit } + +/* Admonitions and System Messages */ +div.admonition, +div.system-message, +div.sidebar{ + margin: 40px; + border: medium outset; + padding-right: 1em; + padding-left: 1em; +} + +/* Sidebar */ +div.sidebar { + width: 30%; + max-width: 26em; + float: right; + clear: right; +} + +/* Text Blocks */ +div.topic, +pre.literal-block, +pre.doctest-block, +pre.math, +pre.code { + margin-right: 40px; + margin-left: 40px; +} +pre.code .ln { color: gray; } /* line numbers */ + +/* Tables */ +table { border-collapse: collapse; } +td, th { + border-style: solid; + border-color: silver; + padding: 0 1ex; + border-width: thin; +} +td > p:first-child, th > p:first-child { margin-top: 0; } +td > p, th > p { margin-bottom: 0; } + +table > caption { + text-align: left; + margin-bottom: 0.25em +} + +table.borderless td, table.borderless th { + border: 0; + padding: 0; + padding-right: 0.5em /* separate table cells */ +} 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 new file mode 100644 index 0000000..aeccf7a --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/plain.css @@ -0,0 +1,288 @@ +/* CSS31_ style sheet for the output of Docutils HTML writers. */ +/* Rules for easy reading and pre-defined style variants. */ +/* */ +/* :Author: Günter Milde, based on html4css1.css by David Goodger */ +/* :Id: $Id: plain.css 7952 2016-07-26 18:15:59Z milde $ */ +/* :Copyright: © 2015 Günter Milde. */ +/* :License: Released under the terms of the `2-Clause BSD license`_, */ +/* in short: */ +/* */ +/* Copying and distribution of this file, with or without modification, */ +/* are permitted in any medium without royalty provided the copyright */ +/* notice and this notice are preserved. */ +/* */ +/* This file is offered as-is, without any warranty. */ +/* */ +/* .. _2-Clause BSD license: http://www.spdx.org/licenses/BSD-2-Clause */ +/* .. _CSS3: http://www.w3.org/TR/CSS3 */ + + +/* Document Structure */ +/* ****************** */ + +/* "page layout" */ +body { + padding: 0 5%; + margin: 8px 0; +} +div.document { + line-height:1.3; + counter-reset: table; + /* counter-reset: figure; */ + /* avoid long lines --> better reading */ + /* OTOH: lines should not be too short because of missing hyphenation, */ + max-width: 50em; + margin: auto; +} + +/* Sections */ + +/* Transitions */ + +hr.docutils { + width: 80%; + margin-top: 1em; + margin-bottom: 1em; + clear: both; +} + +/* Paragraphs */ +/* ========== */ + +/* vertical space (parskip) */ +p, ol, ul, dl, +div.line-block, +table{ + margin-top: 0.5em; + margin-bottom: 0.5em; +} +h1, h2, h3, h4, h5, h6, +dl > dd { + margin-bottom: 0.5em; +} + +/* Lists */ +/* ========== */ + +/* Definition Lists */ + +dl > dd p:first-child { margin-top: 0; } +/* :last-child is not part of CSS 2.1 (introduced in CSS 3) */ +/* dl > dd p:last-child { margin-bottom: 0; } */ + +/* lists nested in definition lists */ +/* :only-child is not part of CSS 2.1 (introduced in CSS 3) */ +dd > ul:only-child, dd > ol:only-child { padding-left: 1em; } + +/* Description Lists */ +/* styled like in most dictionaries, encyclopedias etc. */ +dl.description > dt { + font-weight: bold; + clear: left; + float: left; + margin: 0; + padding: 0; + padding-right: 0.5em; +} + +/* Field Lists */ + +/* example for custom field-name width */ +dl.field-list.narrow > dd { + margin-left: 5em; +} +/* run-in: start field-body on same line after long field names */ +dl.field-list.run-in > dd p { + display: block; +} + +/* Bibliographic Fields */ + +/* generally, bibliographic fields use special definition list dl.docinfo */ +/* but dedication and abstract are placed into "topic" divs */ +div.abstract p.topic-title { + text-align: center; +} +div.dedication { + margin: 2em 5em; + text-align: center; + font-style: italic; +} +div.dedication p.topic-title { + font-style: normal; +} + +/* Citations */ +dl.citation dt.label { + font-weight: bold; +} +span.fn-backref { + font-weight: normal; +} + +/* Text Blocks */ +/* ============ */ + +/* Literal Blocks */ +pre.literal-block, pre.doctest-block, +pre.math, pre.code { + margin-left: 1.5em; + margin-right: 1.5em +} + +/* Block Quotes */ + +blockquote, +div.topic { + margin-left: 1.5em; + margin-right: 1.5em +} +blockquote > table, +div.topic > table { + margin-top: 0; + margin-bottom: 0; +} +blockquote p.attribution, +div.topic p.attribution { + text-align: right; + margin-left: 20%; +} + +/* Tables */ +/* ====== */ + +/* th { vertical-align: bottom; } */ + +table tr { text-align: left; } + +/* "booktabs" style (no vertical lines) */ +table.booktabs { + border: 0; + border-top: 2px solid; + border-bottom: 2px solid; + border-collapse: collapse; +} +table.booktabs * { + border: 0; +} +table.booktabs th { + border-bottom: thin solid; +} + +/* numbered tables (counter defined in div.document) */ +table.numbered > caption:before { + counter-increment: table; + content: "Table " counter(table) ": "; + font-weight: bold; +} + +/* Explicit Markup Blocks */ +/* ====================== */ + +/* Footnotes and Citations */ +/* ----------------------- */ + +/* line on the left */ +dl.footnote { + padding-left: 1ex; + border-left: solid; + border-left-width: thin; +} + +/* Directives */ +/* ---------- */ + +/* Body Elements */ +/* ~~~~~~~~~~~~~ */ + +/* Images and Figures */ + +/* let content flow to the side of aligned images and figures */ +.figure.align-left, +img.align-left, +object.align-left { + display: block; + clear: left; + float: left; + margin-right: 1em +} +.figure.align-right, +img.align-right, +object.align-right { + display: block; + clear: right; + float: right; + margin-left: 1em +} +/* Stop floating sidebars, images and figures at section level 1,2,3 */ +h1, h2, h3 { clear: both; } + +/* Sidebar */ + +/* Move into the margin. In a layout with fixed margins, */ +/* it can be moved into the margin completely. */ +div.sidebar { + width: 30%; + max-width: 26em; + margin-left: 1em; + margin-right: -5.5%; + background-color: #ffffee ; +} + +/* Code */ + +pre.code, code { background-color: #eeeeee } +pre.code .ln { color: gray; } /* line numbers */ +/* basic highlighting: for a complete scheme, see */ +/* http://docutils.sourceforge.net/sandbox/stylesheets/ */ +pre.code .comment, code .comment { color: #5C6576 } +pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } +pre.code .literal.string, code .literal.string { color: #0C5404 } +pre.code .name.builtin, code .name.builtin { color: #352B84 } +pre.code .deleted, code .deleted { background-color: #DEB0A1} +pre.code .inserted, code .inserted { background-color: #A3D289} + +/* Math */ +/* styled separately (see math.css for math-output=HTML) */ + +/* Epigraph */ +/* Highlights */ +/* Pull-Quote */ +/* Compound Paragraph */ +/* Container */ + +/* can be styled in a custom stylesheet */ + +/* Document Header and Footer */ + +div.footer, div.header { + clear: both; + font-size: smaller; +} + +/* Inline Markup */ +/* ============= */ + +/* Emphasis */ +/* em */ +/* Strong Emphasis */ +/* strong */ +/* Interpreted Text */ +/* span.interpreted */ +/* Title Reference */ +/* cite */ +/* Inline Literals */ +/* possible values: normal, nowrap, pre, pre-wrap, pre-line */ +/* span.docutils.literal { white-space: pre-wrap; } */ + +/* Hyperlink References */ +a { text-decoration: none; } + +/* External Targets */ +/* span.target.external */ +/* Internal Targets */ +/* span.target.internal */ +/* Footnote References */ +/* a.footnote-reference */ +/* Citation References */ +/* a.citation-reference */ diff --git a/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/template.txt b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/template.txt new file mode 100644 index 0000000..2591bce --- /dev/null +++ b/docutils/src/main/resources/docutils/docutils/writers/html5_polyglot/template.txt @@ -0,0 +1,8 @@ +%(head_prefix)s +%(head)s +%(stylesheet)s +%(body_prefix)s +%(body_pre_docinfo)s +%(docinfo)s +%(body)s +%(body_suffix)s diff --git a/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py b/docutils/src/main/resources/docutils/docutils/writers/latex2e/__init__.py index 0d23218..112bc76 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 7745 2014-02-28 14:15:59Z milde $ +# $Id: __init__.py 7971 2016-09-13 19:11:48Z milde $ # Author: Engelbert Gruber, Günter Milde # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. @@ -34,12 +34,14 @@ class Writer(writers.Writer): """Formats this writer supports.""" default_template = 'default.tex' - default_template_path = os.path.dirname(__file__) - + default_template_path = os.path.dirname(os.path.abspath(__file__)) default_preamble = '\n'.join([r'% PDF Standard Fonts', r'\usepackage{mathptmx} % Times', r'\usepackage[scaled=.90]{helvet}', r'\usepackage{courier}']) + table_style_values = ('standard', 'booktabs','nolines', 'borderless', + 'colwidths-auto', 'colwidths-given') + settings_spec = ( 'LaTeX-Specific Options', None, @@ -54,14 +56,6 @@ class Writer(writers.Writer): ['--docutils-footnotes'], {'default': True, 'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Alias for --docutils-footnotes (deprecated)', - ['--use-latex-footnotes'], - {'action': 'store_true', - 'validator': frontend.validate_boolean}), - ('Use figure floats for footnote text (deprecated)', - ['--figure-footnotes'], - {'action': 'store_true', - 'validator': frontend.validate_boolean}), ('Format for footnote references: one of "superscript" or ' '"brackets". Default is "superscript".', ['--footnote-references'], @@ -190,9 +184,11 @@ class Writer(writers.Writer): 'above and below the table and below the header or "borderless". ' 'Default: "standard"', ['--table-style'], - {'choices': ['standard', 'booktabs','nolines', 'borderless'], - 'default': 'standard', - 'metavar': '<format>'}), + {'default': ['standard'], + 'metavar': '<format>', + 'action': 'append', + 'validator': frontend.validate_comma_separated_list, + 'choices': table_style_values}), ('LaTeX graphicx package option. ' 'Possible values are "dvips", "pdftex". "auto" includes LaTeX code ' 'to use "pdftex" if processing with pdf(la)tex and dvips otherwise. ' @@ -490,7 +486,7 @@ PreambleCmds.admonition = r""" \csname DUadmonition#1\endcsname{#2}% \else \begin{center} - \fbox{\parbox{0.9\textwidth}{#2}} + \fbox{\parbox{0.9\linewidth}{#2}} \end{center} \fi }""" @@ -509,7 +505,7 @@ PreambleCmds.color = r"""\usepackage{color}""" PreambleCmds.docinfo = r""" % docinfo (width of docinfo table) -\DUprovidelength{\DUdocinfowidth}{0.9\textwidth}""" +\DUprovidelength{\DUdocinfowidth}{0.9\linewidth}""" # PreambleCmds.docinfo._depends = 'providelength' PreambleCmds.dedication = r""" @@ -546,17 +542,6 @@ PreambleCmds.footnotes = r"""% numeric or symbol footnotes with hyperlinks \endgroup% }""" -PreambleCmds.footnote_floats = r"""% settings for footnotes as floats: -\setlength{\floatsep}{0.5em} -\setlength{\textfloatsep}{\fill} -\addtolength{\textfloatsep}{3em} -\renewcommand{\textfraction}{0.5} -\renewcommand{\topfraction}{0.5} -\renewcommand{\bottomfraction}{0.5} -\setcounter{totalnumber}{50} -\setcounter{topnumber}{50} -\setcounter{bottomnumber}{50}""" - PreambleCmds.graphicx_auto = r"""% Check output format \ifx\pdftexversion\undefined \usepackage{graphicx} @@ -612,6 +597,7 @@ PreambleCmds.linking = r""" %% hyperlinks: \ifthenelse{\isundefined{\hypersetup}}{ \usepackage[%s]{hyperref} + \usepackage{bookmark} \urlstyle{same} %% normal text font (alternatives: tt, rm, sf) }{}""" @@ -650,7 +636,7 @@ PreambleCmds.sidebar = r""" % sidebar (text outside the main text flow) \providecommand{\DUsidebar}[2][class-arg]{% \begin{center} - \colorbox[gray]{0.80}{\parbox{0.9\textwidth}{#2}} + \colorbox[gray]{0.80}{\parbox{0.9\linewidth}{#2}} \end{center} }""" @@ -711,7 +697,13 @@ PreambleCmds.transition = r""" class CharMaps(object): """LaTeX representations for active and Unicode characters.""" - # characters that always need escaping: + # characters that need escaping even in `alltt` environments: + alltt = { + ord('\\'): ur'\textbackslash{}', + ord('{'): ur'\{', + ord('}'): ur'\}', + } + # characters that normally need escaping: special = { ord('#'): ur'\#', ord('$'): ur'\$', @@ -720,9 +712,6 @@ class CharMaps(object): ord('~'): ur'\textasciitilde{}', ord('_'): ur'\_', ord('^'): ur'\textasciicircum{}', - ord('\\'): ur'\textbackslash{}', - ord('{'): ur'\{', - ord('}'): ur'\}', # straight double quotes are 'active' in many languages ord('"'): ur'\textquotedbl{}', # Square brackets are ordinary chars and cannot be escaped with '\', @@ -752,9 +741,9 @@ class CharMaps(object): } # Unicode chars that are recognized by LaTeX's utf8 encoding utf8_supported_unicode = { - 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 + 0x00AB: ur'\guillemotleft{}', # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x00bb: ur'\guillemotright{}', # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + 0x200C: ur'\textcompwordmark{}', # ZERO WIDTH NON-JOINER 0x2013: ur'\textendash{}', 0x2014: ur'\textemdash{}', 0x2018: ur'\textquoteleft{}', @@ -901,17 +890,20 @@ class Table(object): :booktabs: only horizontal lines (requires "booktabs" LaTeX package) :borderless: no borders around table cells :nolines: alias for borderless + + :colwidths-auto: column widths determined by LaTeX + :colwidths-given: use colum widths from rST source """ - def __init__(self,translator,latex_type,table_style): + def __init__(self, translator, latex_type): self._translator = translator self._latex_type = latex_type - self._table_style = table_style self._open = False # miscellaneous attributes self._attrs = {} self._col_width = [] self._rowspan = [] self.stubs = [] + self.colwidths_auto = False self._in_thead = 0 def open(self): @@ -926,13 +918,23 @@ class Table(object): self.caption = [] self._attrs = {} self.stubs = [] + self.colwidths_auto = False + def is_open(self): return self._open - def set_table_style(self, table_style): - if not table_style in ('standard','booktabs','borderless','nolines'): - return - self._table_style = table_style + def set_table_style(self, table_style, classes): + borders = [cls.replace('nolines', 'borderless') + for cls in table_style+classes + if cls in ('standard','booktabs','borderless', 'nolines')] + try: + self.borders = borders[-1] + except IndexError: + self.borders = 'standard' + self.colwidths_auto = (('colwidths-auto' in classes + and 'colwidths-given' not in table_style) + or ('colwidths-auto' in table_style + and ('colwidths-given' not in classes))) def get_latex_type(self): if self._latex_type == 'longtable' and not self.caption: @@ -948,20 +950,26 @@ class Table(object): return None def get_vertical_bar(self): - if self._table_style == 'standard': + if self.borders == 'standard': return '|' return '' # horizontal lines are drawn below a row, def get_opening(self): - return '\n'.join([r'\setlength{\DUtablewidth}{\linewidth}', - r'\begin{%s}[c]' % self.get_latex_type()]) + align_map = {'left': 'l', + 'center': 'c', + 'right': 'r'} + align = align_map.get(self.get('align') or 'center') + opening = [r'\begin{%s}[%s]' % (self.get_latex_type(), align)] + if not self.colwidths_auto: + opening.insert(0, r'\setlength{\DUtablewidth}{\linewidth}') + return '\n'.join(opening) def get_closing(self): closing = [] - if self._table_style == 'booktabs': + if self.borders == 'booktabs': closing.append(r'\bottomrule') - # elif self._table_style == 'standard': + # elif self.borders == 'standard': # closing.append(r'\hline') closing.append(r'\end{%s}' % self.get_latex_type()) return '\n'.join(closing) @@ -971,7 +979,7 @@ class Table(object): # "stubs" list is an attribute of the tgroup element: self.stubs.append(node.attributes.get('stub')) - def get_colspecs(self): + def get_colspecs(self, node): """Return column specification for longtable. Assumes reST line length being 80 characters. @@ -983,38 +991,45 @@ class Table(object): usually gets to narrow, therefore we add 1 (fiddlefactor). """ + bar = self.get_vertical_bar() + self._rowspan= [0] * len(self._col_specs) + self._col_width = [] + if self.colwidths_auto: + latex_table_spec = (bar+'l')*len(self._col_specs) + return latex_table_spec+bar width = 80 - total_width = 0.0 # first see if we get too wide. for node in self._col_specs: colwidth = float(node['colwidth']+1) / width total_width += colwidth - self._col_width = [] - self._rowspan = [] # donot make it full linewidth factor = 0.93 if total_width > 1.0: factor /= total_width - bar = self.get_vertical_bar() latex_table_spec = '' for node in self._col_specs: colwidth = factor * float(node['colwidth']+1) / width self._col_width.append(colwidth+0.005) - self._rowspan.append(0) latex_table_spec += '%sp{%.3f\\DUtablewidth}' % (bar, colwidth+0.005) return latex_table_spec+bar def get_column_width(self): """Return columnwidth for current cell (not multicell).""" - return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row-1] + try: + return '%.2f\\DUtablewidth' % self._col_width[self._cell_in_row] + except IndexError: + return '*' def get_multicolumn_width(self, start, len_): """Return sum of columnwidths for multicell.""" - mc_width = sum([width - for width in ([self._col_width[start + co - 1] - for co in range (len_)])]) - return '%.2f\\DUtablewidth' % mc_width + try: + mc_width = sum([width + for width in ([self._col_width[start + co] + for co in range (len_)])]) + return 'p{%.2f\\DUtablewidth}' % mc_width + except IndexError: + return 'l' def get_caption(self): if not self.caption: @@ -1031,17 +1046,17 @@ class Table(object): def visit_thead(self): self._in_thead += 1 - if self._table_style == 'standard': + if self.borders == 'standard': return ['\\hline\n'] - elif self._table_style == 'booktabs': + elif self.borders == 'booktabs': return ['\\toprule\n'] return [] def depart_thead(self): a = [] - #if self._table_style == 'standard': + #if self.borders == 'standard': # a.append('\\hline\n') - if self._table_style == 'booktabs': + if self.borders == 'booktabs': a.append('\\midrule\n') if self._latex_type == 'longtable': if 1 == self._translator.thead_depth(): @@ -1054,8 +1069,10 @@ class Table(object): # for longtable one could add firsthead, foot and lastfoot self._in_thead -= 1 return a + def visit_row(self): self._cell_in_row = 0 + def depart_row(self): res = [' \\\\\n'] self._cell_in_row = None # remove cell counter @@ -1063,7 +1080,7 @@ class Table(object): if (self._rowspan[i]>0): self._rowspan[i] -= 1 - if self._table_style == 'standard': + if self.borders == 'standard': rowspans = [i+1 for i in range(len(self._rowspan)) if (self._rowspan[i]<=0)] if len(rowspans)==len(self._rowspan): @@ -1086,18 +1103,22 @@ class Table(object): self._rowspan[cell] = value except: pass + def get_rowspan(self,cell): try: return self._rowspan[cell] except: return 0 + def get_entry_number(self): return self._cell_in_row + def visit_entry(self): self._cell_in_row += 1 + def is_stub_column(self): if len(self.stubs) >= self._cell_in_row: - return self.stubs[self._cell_in_row-1] + return self.stubs[self._cell_in_row] return False @@ -1142,7 +1163,7 @@ class LaTeXTranslator(nodes.NodeVisitor): insert_non_breaking_blanks = False # replace blanks by "~" insert_newline = False # add latex newline commands literal = False # literal text (block or inline) - + alltt = False # inside `alltt` environment def __init__(self, document, babel_class=Babel): nodes.NodeVisitor.__init__(self, document) @@ -1167,7 +1188,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.section_enumerator_separator = ( settings.section_enumerator_separator.replace('_', r'\_')) # literal blocks: - self.literal_block_env = '' + self.literal_block_env = 'alltt' self.literal_block_options = '' if settings.literal_block_env != '': (none, @@ -1204,16 +1225,10 @@ class LaTeXTranslator(nodes.NodeVisitor): self.settings.graphicx_option) # footnotes: self.docutils_footnotes = settings.docutils_footnotes - if settings.use_latex_footnotes: - self.docutils_footnotes = True - self.warn('`use_latex_footnotes` is deprecated. ' - 'The setting has been renamed to `docutils_footnotes` ' - 'and the alias will be removed in a future version.') - self.figure_footnotes = settings.figure_footnotes - if self.figure_footnotes: - self.docutils_footnotes = True - self.warn('The "figure footnotes" workaround/setting is strongly ' - 'deprecated and will be removed in a future version.') + # @@ table_style: list of values from fixed set: warn? + # for s in self.settings.table_style: + # if s not in Writer.table_style_values: + # self.warn('Ignoring value "%s" in "table-style" setting.' %s) # Output collection stacks # ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1271,7 +1286,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # object for a table while proccessing. self.table_stack = [] - self.active_table = Table(self, 'longtable', settings.table_style) + self.active_table = Table(self, 'longtable') # Where to collect the output of visitor methods (default: body) self.out = self.body @@ -1447,16 +1462,17 @@ class LaTeXTranslator(nodes.NodeVisitor): def encode(self, text): """Return text with 'problematic' characters escaped. - * Escape the ten 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. """ if self.verbatim: return text - # Set up the translation table: - table = CharMaps.special.copy() + table = CharMaps.alltt.copy() + if not self.alltt: + table.update(CharMaps.special) # keep the underscore in citation references if self.inside_citation_reference_label: del(table[ord('_')]) @@ -1854,7 +1870,7 @@ class LaTeXTranslator(nodes.NodeVisitor): raise nodes.SkipNode self.out.append('\\textbf{%s}: &\n\t' % self.language_label(name)) if name == 'address': - self.insert_newline = 1 + self.insert_newline = True self.out.append('{\\raggedright\n') self.context.append(' } \\\\\n') else: @@ -1954,78 +1970,74 @@ class LaTeXTranslator(nodes.NodeVisitor): self.depart_inline(node) self.out.append('}') + # Append column delimiters and advance column counter, + # if the current cell is a multi-row continuation.""" + def insert_additional_table_colum_delimiters(self): + while self.active_table.get_rowspan( + self.active_table.get_entry_number()): + self.out.append(' & ') + self.active_table.visit_entry() # increment cell count + def visit_entry(self, node): - self.active_table.visit_entry() # cell separation - # BUG: the following fails, with more than one multirow - # starting in the second column (or later) see - # ../../../test/functional/input/data/latex.txt - if self.active_table.get_entry_number() == 1: - # if the first row is a multirow, this actually is the second row. - # this gets hairy if rowspans follow each other. - if self.active_table.get_rowspan(0): - count = 0 - while self.active_table.get_rowspan(count): - count += 1 - self.out.append(' & ') - self.active_table.visit_entry() # increment cell count + if self.active_table.get_entry_number() == 0: + self.insert_additional_table_colum_delimiters() else: self.out.append(' & ') + # multirow, multicolumn - # IN WORK BUG TODO HACK continues here - # multirow in LaTeX simply will enlarge the cell over several rows - # (the following n if n is positive, the former if negative). if 'morerows' in node and 'morecols' in node: raise NotImplementedError('Cells that ' - 'span multiple rows *and* columns are not supported, sorry.') + 'span multiple rows *and* columns currently not supported, sorry.') + # TODO: should be possible with LaTeX, see e.g. + # http://texblog.org/2012/12/21/multi-column-and-multi-row-cells-in-latex-tabl... + # multirow in LaTeX simply will enlarge the cell over several rows + # (the following n if n is positive, the former if negative). if 'morerows' in node: self.requirements['multirow'] = r'\usepackage{multirow}' - count = node['morerows'] + 1 + mrows = node['morerows'] + 1 self.active_table.set_rowspan( - self.active_table.get_entry_number()-1,count) - # TODO why does multirow end on % ? needs to be checked for below - self.out.append('\\multirow{%d}{%s}{%%' % - (count,self.active_table.get_column_width())) + self.active_table.get_entry_number(), mrows) + self.out.append('\\multirow{%d}{%s}{' % + (mrows, self.active_table.get_column_width())) self.context.append('}') elif 'morecols' in node: # the vertical bar before column is missing if it is the first # column. the one after always. - if self.active_table.get_entry_number() == 1: + if self.active_table.get_entry_number() == 0: bar1 = self.active_table.get_vertical_bar() else: bar1 = '' - count = node['morecols'] + 1 - self.out.append('\\multicolumn{%d}{%sp{%s}%s}{' % - (count, bar1, + mcols = node['morecols'] + 1 + self.out.append('\\multicolumn{%d}{%s%s%s}{' % + (mcols, bar1, self.active_table.get_multicolumn_width( self.active_table.get_entry_number(), - count), + mcols), self.active_table.get_vertical_bar())) self.context.append('}') else: self.context.append('') - # header / not header - if isinstance(node.parent.parent, nodes.thead): - if self.out[-1].endswith("%"): - self.out.append("\n") - self.out.append('\\textbf{%') - self.context.append('}') - elif self.active_table.is_stub_column(): - if self.out[-1].endswith("%"): - self.out.append("\n") + # bold header/stub-column + if len(node) and (isinstance(node.parent.parent, nodes.thead) + or self.active_table.is_stub_column()): self.out.append('\\textbf{') self.context.append('}') else: self.context.append('') + # if line ends with '{', mask line break to prevent spurious whitespace + if not self.active_table.colwidths_auto and self.out[-1].endswith("{"): + self.out.append("%") + + self.active_table.visit_entry() # increment cell count + def depart_entry(self, node): self.out.append(self.context.pop()) # header / not header self.out.append(self.context.pop()) # multirow/column - # if following row is spanned from above. - if self.active_table.get_rowspan(self.active_table.get_entry_number()): - self.out.append(' & ') - self.active_table.visit_entry() # increment cell count + # insert extra "&"s, if following rows are spanned from above: + self.insert_additional_table_colum_delimiters() def visit_row(self, node): self.active_table.visit_row() @@ -2034,59 +2046,63 @@ class LaTeXTranslator(nodes.NodeVisitor): self.out.extend(self.active_table.depart_row()) def visit_enumerated_list(self, node): - # We create our own enumeration list environment. - # This allows to set the style and starting value - # and unlimited nesting. - enum_style = {'arabic':'arabic', - 'loweralpha':'alph', - 'upperalpha':'Alph', - 'lowerroman':'roman', - 'upperroman':'Roman' } - enum_suffix = '' - if 'suffix' in node: - enum_suffix = node['suffix'] - enum_prefix = '' - if 'prefix' in node: - enum_prefix = node['prefix'] + # enumeration styles: + types = {'': '', + 'arabic':'arabic', + 'loweralpha':'alph', + 'upperalpha':'Alph', + 'lowerroman':'roman', + 'upperroman':'Roman'} + # the 4 default LaTeX enumeration labels: präfix, enumtype, suffix, + labels = [('', 'arabic', '.'), # 1. + ('(', 'alph', ')'), # (a) + ('', 'roman', '.'), # i. + ('', 'Alph', '.')] # A. + + prefix = '' if self.compound_enumerators: - pref = '' - if self.section_prefix_for_enumerators and self.section_level: - for i in range(self.section_level): - pref += '%d.' % self._section_number[i] - pref = pref[:-1] + self.section_enumerator_separator - enum_prefix += pref - for ctype, cname in self._enumeration_counters: - enum_prefix += '\\%s{%s}.' % (ctype, cname) - enum_type = 'arabic' - if 'enumtype' in node: - enum_type = node['enumtype'] - if enum_type in enum_style: - enum_type = enum_style[enum_type] - - counter_name = 'listcnt%d' % len(self._enumeration_counters) - self._enumeration_counters.append((enum_type, counter_name)) - # If we haven't used this counter name before, then create a - # new counter; otherwise, reset & reuse the old counter. - if len(self._enumeration_counters) > self._max_enumeration_counters: - self._max_enumeration_counters = len(self._enumeration_counters) - self.out.append('\\newcounter{%s}\n' % counter_name) + if (self.section_prefix_for_enumerators and self.section_level + and not self._enumeration_counters): + prefix = '.'.join([str(n) for n in + self._section_number[:self.section_level]] + ) + self.section_enumerator_separator + if self._enumeration_counters: + prefix += self._enumeration_counters[-1] + # TODO: use LaTeX default for unspecified label-type? + # (needs change of parser) + prefix += node.get('prefix', '') + enumtype = types[node.get('enumtype' '')] + suffix = node.get('suffix', '') + + enumeration_level = len(self._enumeration_counters)+1 + counter_name = 'enum' + roman.toRoman(enumeration_level).lower() + label = r'%s\%s{%s}%s' % (prefix, enumtype, counter_name, suffix) + self._enumeration_counters.append(label) + + if enumeration_level <= 4: + self.out.append('\\begin{enumerate}\n') + if (prefix, enumtype, suffix + ) != labels[enumeration_level-1]: + self.out.append('\\renewcommand{\\label%s}{%s}\n' % + (counter_name, label)) else: - self.out.append('\\setcounter{%s}{0}\n' % counter_name) - - self.out.append('\\begin{list}{%s\\%s{%s}%s}\n' % - (enum_prefix,enum_type,counter_name,enum_suffix)) - self.out.append('{\n') - self.out.append('\\usecounter{%s}\n' % counter_name) - # set start after usecounter, because it initializes to zero. + 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) if 'start' in node: - self.out.append('\\addtocounter{%s}{%d}\n' % + self.out.append('\\setcounter{%s}{%d}\n' % (counter_name,node['start']-1)) - ## set rightmargin equal to leftmargin - self.out.append('\\setlength{\\rightmargin}{\\leftmargin}\n') - self.out.append('}\n') + # ## set rightmargin equal to leftmargin + # self.out.append('\\setlength{\\rightmargin}{\\leftmargin}\n') + + def depart_enumerated_list(self, node): - self.out.append('\\end{list}\n') + if len(self._enumeration_counters) <= 4: + self.out.append('\\end{enumerate}\n') + else: + self.out.append('\\end{list}\n') self._enumeration_counters.pop() def visit_field(self, node): @@ -2139,7 +2155,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # for "inner alignment" use LaTeX default alignment (similar to HTML) alignment = node.attributes.get('align', 'center') if alignment != 'center': - # The LaTeX "figure" environment always uses the full textwidth, + # 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) @@ -2165,30 +2181,22 @@ class LaTeXTranslator(nodes.NodeVisitor): backref = node['backrefs'][0] except IndexError: backref = node['ids'][0] # no backref, use self-ref instead - if self.settings.figure_footnotes: - self.requirements['~fnt_floats'] = PreambleCmds.footnote_floats - self.out.append('\\begin{figure}[b]') - self.append_hypertargets(node) - if node.get('id') == node.get('name'): # explicite label - self.out += self.ids_to_labels(node) - elif self.docutils_footnotes: + if self.docutils_footnotes: self.fallbacks['footnotes'] = PreambleCmds.footnotes - num,text = node.astext().split(None,1) + num = node[0].astext() if self.settings.footnote_references == 'brackets': num = '[%s]' % num self.out.append('%%\n\\DUfootnotetext{%s}{%s}{%s}{' % (node['ids'][0], backref, self.encode(num))) if node['ids'] == node['names']: self.out += self.ids_to_labels(node) - # mask newline to prevent spurious whitespace: - self.out.append('%') + # mask newline to prevent spurious whitespace if paragraph follows: + if node[1:] and isinstance(node[1], nodes.paragraph): + self.out.append('%') ## else: # TODO: "real" LaTeX \footnote{}s def depart_footnote(self, node): - if self.figure_footnotes: - self.out.append('\\end{figure}\n') - else: - self.out.append('}\n') + self.out.append('}\n') def visit_footnote_reference(self, node): href = '' @@ -2223,12 +2231,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # footnote/citation label def label_delim(self, node, bracket, superscript): if isinstance(node.parent, nodes.footnote): - if not self.figure_footnotes: - raise nodes.SkipNode - if self.settings.footnote_references == 'brackets': - self.out.append(bracket) - else: - self.out.append(superscript) + raise nodes.SkipNode else: assert isinstance(node.parent, nodes.citation) if not self._use_latex_citations: @@ -2306,7 +2309,7 @@ class LaTeXTranslator(nodes.NodeVisitor): 'middle': (r'\raisebox{-0.5\height}{', '}'), 'top': (r'\raisebox{-\height}{', '}'), # block level images: - 'center': (r'\noindent\makebox[\textwidth][c]{', '}'), + 'center': (r'\noindent\makebox[\linewidth][c]{', '}'), 'left': (r'\noindent{', r'\hfill}'), 'right': (r'\noindent{\hfill', '}'),} if 'align' in attrs: @@ -2442,27 +2445,37 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_literal_block(self, node): """Render a literal block.""" # environments and packages to typeset literal blocks - packages = {'listing': r'\usepackage{moreverb}', + packages = {'alltt': r'\usepackage{alltt}', + 'listing': r'\usepackage{moreverb}', 'lstlisting': r'\usepackage{listings}', 'Verbatim': r'\usepackage{fancyvrb}', # 'verbatim': '', 'verbatimtab': r'\usepackage{moreverb}'} + if node.get('ids'): + self.out += ['\n'] + self.ids_to_labels(node) + if not self.active_table.is_open(): # no quote inside tables, to avoid vertical space between # table border and literal block. - # BUG: fails if normal text precedes the literal block. - self.out.append('%\n\\begin{quote}') + # TODO: fails if normal text precedes the literal block. + # check parent node instead? + self.out.append('%\n\\begin{quote}\n') self.context.append('\n\\end{quote}\n') else: self.out.append('\n') self.context.append('\n') - if self.literal_block_env != '' and self.is_plaintext(node): - self.requirements['literal_block'] = packages.get( - self.literal_block_env, '') - self.verbatim = True - self.out.append('\\begin{%s}%s\n' % (self.literal_block_env, - self.literal_block_options)) + + if self.is_plaintext(node): + environment = self.literal_block_env + self.requirements['literal_block'] = packages.get(environment, '') + if environment == 'alltt': + self.alltt = True + else: + self.verbatim = True + self.out.append('\\begin{%s}%s\n' % + (environment, self.literal_block_options)) + self.context.append('\n\\end{%s}' % environment) else: self.literal = True self.insert_newline = True @@ -2472,16 +2485,15 @@ class LaTeXTranslator(nodes.NodeVisitor): self.requirements['color'] = PreambleCmds.color self.fallbacks['code'] = PreambleCmds.highlight_rules self.out.append('{\\ttfamily \\raggedright \\noindent\n') + self.context.append('\n}') def depart_literal_block(self, node): - if self.verbatim: - self.out.append('\n\\end{%s}\n' % self.literal_block_env) - self.verbatim = False - else: - self.out.append('\n}') - self.insert_non_breaking_blanks = False - self.insert_newline = False - self.literal = False + self.insert_non_breaking_blanks = False + self.insert_newline = False + self.literal = False + self.verbatim = False + self.alltt = False + self.out.append(self.context.pop()) self.out.append(self.context.pop()) ## def visit_meta(self, node): @@ -2509,7 +2521,10 @@ class LaTeXTranslator(nodes.NodeVisitor): if node.get('ids'): math_code = '\n'.join([math_code] + self.ids_to_labels(node)) if math_env == '$': - wrapper = u'$%s$' + if self.alltt: + wrapper = u'\(%s\)' + else: + wrapper = u'$%s$' else: wrapper = u'\n'.join(['%%', r'\begin{%s}' % math_env, @@ -2586,8 +2601,10 @@ class LaTeXTranslator(nodes.NodeVisitor): self.depart_docinfo_item(node) def visit_paragraph(self, node): - # insert blank line, if the paragraph is not first in a list item - # nor follows a non-paragraph node in a compound + # insert blank line, unless + # * the paragraph is first in a list item, + # * 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))): @@ -2596,6 +2613,12 @@ class LaTeXTranslator(nodes.NodeVisitor): not isinstance(node.parent[index - 1], nodes.paragraph) and not isinstance(node.parent[index - 1], nodes.compound)): pass + elif self.active_table.colwidths_auto: + if index == 1: # second paragraph + self.warn('LaTeX merges paragraphs in tables ' + 'with auto-sized columns!', base_node=node) + if index > 0: + self.out.append('\n') else: self.out.append('\n') if node.get('ids'): @@ -2606,7 +2629,8 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_paragraph(self, node): if node['classes']: self.depart_inline(node) - self.out.append('\n') + if not self.active_table.colwidths_auto: + self.out.append('\n') def visit_problematic(self, node): self.requirements['color'] = PreambleCmds.color @@ -2797,7 +2821,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if self.active_table.is_open(): self.table_stack.append(self.active_table) # nesting longtable does not work (e.g. 2007-04-18) - self.active_table = Table(self,'tabular',self.settings.table_style) + self.active_table = Table(self,'tabular') # A longtable moves before \paragraph and \subparagraph # section titles if it immediately follows them: if (self.active_table._latex_type == 'longtable' and @@ -2806,9 +2830,11 @@ class LaTeXTranslator(nodes.NodeVisitor): self.d_class.section(self.section_level).find('paragraph') != -1): self.out.append('\\leavevmode') self.active_table.open() - for cls in node['classes']: - self.active_table.set_table_style(cls) - if self.active_table._table_style == 'booktabs': + self.active_table.set_table_style(self.settings.table_style, + node['classes']) + if 'align' in node: + self.active_table.set('align', node['align']) + if self.active_table.borders == 'booktabs': self.requirements['booktabs'] = r'\usepackage{booktabs}' self.push_output_collector([]) @@ -2822,8 +2848,6 @@ class LaTeXTranslator(nodes.NodeVisitor): self.active_table.close() if len(self.table_stack)>0: self.active_table = self.table_stack.pop() - else: - self.active_table.set_table_style(self.settings.table_style) # Insert hyperlabel after (long)table, as # other places (beginning, caption) result in LaTeX errors. if node.get('ids'): @@ -2850,7 +2874,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # BUG write preamble if not yet done (colspecs not []) # for tables without heads. if not self.active_table.get('preamble written'): - self.visit_thead(None) + self.visit_thead(node) self.depart_thead(None) def depart_tbody(self, node): @@ -2882,7 +2906,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_thead(self, node): self._thead_depth += 1 if 1 == self.thead_depth(): - self.out.append('{%s}\n' % self.active_table.get_colspecs()) + self.out.append('{%s}\n' % self.active_table.get_colspecs(node)) self.active_table.set('preamble written',1) self.out.append(self.active_table.get_caption()) self.out.extend(self.active_table.visit_thead()) 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 06eecfd..39df6f9 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/latex2e/xelatex.tex +++ b/docutils/src/main/resources/docutils/docutils/writers/latex2e/xelatex.tex @@ -1,6 +1,7 @@ $head_prefix% generated by Docutils <http://docutils.sourceforge.net/> % rubber: set program xelatex -\usepackage[no-sscript]{xltxtra} % loads fixltx2e, metalogo, xunicode, fontspec +\usepackage{fixltx2e} +\usepackage{fontspec} % \defaultfontfeatures{Scale=MatchLowercase} $requirements %%% Custom LaTeX preamble 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 5670900..3b29e3b 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 7717 2013-08-21 22:01:21Z milde $ +# $Id: __init__.py 7899 2015-06-03 22:02:46Z timehorse $ # Author: Dave Kuhlman <dkuhlman@rexx.com> # Copyright: This module has been placed in the public domain. @@ -58,7 +58,7 @@ try: import pygments.lexers from pygmentsformatter import OdtPygmentsProgFormatter, \ OdtPygmentsLaTeXFormatter -except ImportError, exp: +except (ImportError, SyntaxError), exp: pygments = None # check for the Python Imaging Library @@ -594,9 +594,7 @@ class Writer(writers.Writer): if source is None: continue try: - # encode/decode - destination1 = destination.decode('latin-1').encode('utf-8') - zfile.write(source, destination1) + zfile.write(source, destination) except OSError, e: self.document.reporter.warning( "Can't open file %s." % (source, )) @@ -872,6 +870,8 @@ class ODFTranslator(nodes.GenericNodeVisitor): self.table_styles = None self.in_citation = False + # Keep track of nested styling classes + self.inline_style_count_stack = [] def get_str_stylesheet(self): return self.str_stylesheet @@ -2399,14 +2399,26 @@ class ODFTranslator(nodes.GenericNodeVisitor): def visit_inline(self, node): styles = node.attributes.get('classes', ()) - if len(styles) > 0: - inline_style = styles[0] - el = SubElement(self.current_element, 'text:span', - attrib={'text:style-name': self.rststyle(inline_style)}) + if styles: + el = self.current_element + for inline_style in styles: + el = SubElement(el, 'text:span', + attrib={'text:style-name': + self.rststyle(inline_style)}) + count = len(styles) + else: + # No style was specified so use a default style (old code + # crashed if no style was given) + el = SubElement(self.current_element, 'text:span') + count = 1 + self.set_current_element(el) + self.inline_style_count_stack.append(count) def depart_inline(self, node): - self.set_to_parent() + count = self.inline_style_count_stack.pop() + for x in range(count): + self.set_to_parent() def _calculate_code_block_padding(self, line): count = 0 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 fc2715f..7d6fcaf 100644 --- a/docutils/src/main/resources/docutils/docutils/writers/pep_html/pep.css +++ b/docutils/src/main/resources/docutils/docutils/writers/pep_html/pep.css @@ -1,7 +1,7 @@ /* :Author: David Goodger :Contact: goodger@python.org -:date: $Date: 2006-05-21 22:44:42 +0200 (Son, 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 113fee5..e5ecf1d 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: 7668 $ -# :Date: $Date: 2013-06-04 14:46:30 +0200 (Die, 04. Jun 2013) $ +# :Revision: $Revision: 7852 $ +# :Date: $Date: 2015-03-21 17:07:49 +0100 (Sa, 21. Mär 2015) $ # :Copyright: © 2010 Günter Milde. # :License: Released under the terms of the `2-Clause BSD license`_, in short: # @@ -17,8 +17,9 @@ """ XeLaTeX document tree Writer. -A variant of Docutils' standard 'latex2e' writer producing output -suited for processing with XeLaTeX (http://tug.org/xetex/). +A variant of Docutils' standard 'latex2e' writer producing LaTeX output +suited for processing with the Unicode-aware TeX engines +LuaTeX and XeTeX. """ __docformat__ = 'reStructuredText' @@ -32,9 +33,9 @@ from docutils import frontend, nodes, utils, writers, languages from docutils.writers import latex2e class Writer(latex2e.Writer): - """A writer for Unicode-based LaTeX variants (XeTeX, LuaTeX)""" + """A writer for Unicode-aware LaTeX variants (XeTeX, LuaTeX)""" - supported = ('xetex','xelatex','luatex') + supported = ('lxtex', 'xetex','xelatex','luatex', 'lualatex') """Formats this writer supports.""" default_template = 'xelatex.tex' @@ -54,7 +55,7 @@ class Writer(latex2e.Writer): template=('Template file. Default: "%s".' % default_template, ['--template'], {'default': default_template, 'metavar': '<file>'}), latex_preamble=('Customization by LaTeX code in the preamble. ' - 'Default: select PDF standard fonts (Times, Helvetica, Courier).', + 'Default: select "Linux Libertine" fonts.', ['--latex-preamble'], {'default': default_preamble}), ) -- To stop receiving notification emails like this one, please contact nuiton.org SCM administrator <admin+scm@nuiton.org>.