#! /usr/bin/env python3 # $Id: test_html5_polyglot.py 10135 2025-05-20 15:07:28Z milde $ # Author: reggie dugard # Maintainer: docutils-develop@lists.sourceforge.net # Copyright: This module has been placed in the public domain. """Test HTML5 writer output ("fragment"/"body" part). This is the document body (not HTML ). """ from pathlib import Path import sys import unittest if __name__ == '__main__': # prepend the "docutils root" to the Python library path # so we import the local `docutils` package. sys.path.insert(0, str(Path(__file__).resolve().parents[2])) import docutils import docutils.core from docutils.parsers.rst.directives.images import PIL from docutils.utils.code_analyzer import with_pygments from docutils.writers import html5_polyglot if with_pygments: import pygments if tuple(map(int, pygments.__version__.split('.')[:2])) < (2, 14): # pygments output changed in version 2.14 with_pygments = False # TEST_ROOT is ./test/ from the docutils root TEST_ROOT = Path(__file__).parents[1] DATA_ROOT = TEST_ROOT / 'data' ROOT_PREFIX = (TEST_ROOT / 'functional/input').as_posix() # Pillow/PIL is optional: if PIL: REQUIRES_PIL = '' ONLY_LOCAL = 'Cannot get file path corresponding to https://dummy.png.' # Pillow versions vary in their error output (cf. bugs: #485 and #500) try: PIL.Image.open(Path('dummy.png')) except OSError as err: DUMMY_PNG_NOT_FOUND = str(err) HEIGHT_ATTR = 'height="32" ' WIDTH_ATTR = 'width="32" ' NO_PIL_SYSTEM_MESSAGE = '' else: REQUIRES_PIL = '\n Requires Python Imaging Library.' ONLY_LOCAL = 'Requires Python Imaging Library.' DUMMY_PNG_NOT_FOUND = 'Requires Python Imaging Library.' HEIGHT_ATTR = '' WIDTH_ATTR = '' NO_PIL_SYSTEM_MESSAGE = ( '\n') class Html5WriterPublishPartsTestCase(unittest.TestCase): """Test case for HTML5 writer via the publish_parts() interface.""" maxDiff = None def test_publish(self): for name, (settings_overrides, cases) in totest.items(): for casenum, (case_input, case_expected) in enumerate(cases): with self.subTest(id=f'totest[{name!r}][{casenum}]'): if name == 'syntax_highlight' and not with_pygments: self.skipTest('syntax highlight requires pygments') parts = docutils.core.publish_parts( source=case_input, writer=html5_polyglot.Writer(), settings_overrides={ '_disable_config': True, 'strict_visitor': True, 'stylesheet_path': '', 'section_self_link': True, **settings_overrides, } ) self.assertEqual(case_expected, parts['body']) totest = {} # expected samples contain only the "body" part of the HMTL output totest['standard'] = ({}, [ ["""\ Simple String """, '

Simple String

\n', ], ["""\ Simple String with *markup* """, '

Simple String with markup

\n', ], ["""\ Simple String with an even simpler ``inline literal`` """, '

Simple String with an even simpler inline literal

\n', ], ["""\ A simple `anonymous reference`__ __ http://www.test.com/test_url """, '

A simple anonymous reference

\n', ], ["""\ One paragraph. Two paragraphs. """, """\

One paragraph.

Two paragraphs.

""", ], ["""\ A simple `named reference`_ with stuff in between the reference and the target. .. _`named reference`: http://www.test.com/test_url """, """\

A simple named reference with stuff in between the reference and the target.

""", ], ["""\ .. [CIT2022] A citation. """, """\
[CIT2022]

A citation.

""", ], [f"""\ .. image:: {DATA_ROOT.as_uri()}/circle.svg :loading: embed :width: 50% :height: 30 :align: left """, """\ """], ]) totest['no_title_promotion'] = ({'doctitle_xform': False}, [ ["""\ +++++ Title +++++ Not A Subtitle ============== Some stuff Section ------- Some more stuff Another Section ............... And even more stuff """, """\

Title

Not A Subtitle

Some stuff

Section

Some more stuff

Another Section

And even more stuff

""", ], ["""\ * bullet * list """, """\ """, ], ["""\ .. table:: :align: right :width: 320 +-----+-----+ | 1 | 2 | +-----+-----+ | 3 | 4 | +-----+-----+ """, """\

1

2

3

4

""", ], ["""\ Not a docinfo. :This: .. _target: is :a: :simple: :field: list """, """\

Not a docinfo.

This:

is

a:

simple:

field:

list

""", ], ["""\ Not a docinfo. :This is: a :simple field list with loooong field: names """, """\

Not a docinfo.

This is:

a

simple field list with loooong field:

names

""", ], ["""\ Not a docinfo. .. class:: field-indent-200 :This: is a :simple: field list with custom indent. """, """\

Not a docinfo.

This:

is a

simple:

field list with custom indent.

""", ], ["""\ Not a docinfo. .. class:: field-indent-200uf :This: is a :simple: field list without custom indent, because the unit "uf" is invalid. """, """\

Not a docinfo.

This:

is a

simple:

field list without custom indent, because the unit "uf" is invalid.

""", ], ["""\ .. figure:: dummy.png :figname: fig:dummy The figure's caption. A legend. The legend's second paragraph. """, """\
dummy.png

The figure's caption.

A legend.

The legend's second paragraph.

""", ], ["""\ .. figure:: dummy.png The figure's caption, no legend. """, """\
dummy.png

The figure's caption, no legend.

""", ], ["""\ .. figure:: dummy.png .. A legend without caption. """, """\
dummy.png

A legend without caption.

""", ], ["""\ .. figure:: dummy.png No caption nor legend. """, """\
dummy.png

No caption nor legend.

""", ], [f"""\ .. include:: {DATA_ROOT}/multiple-term-definition.xml :parser: xml """, """\
New in Docutils 0.22

A definition list item may contain several terms with optional classifier(s).

However, there is currently no corresponding reStructuredText syntax.

term 2a
term 2b

definition 2

term 3aclassifier 3aclassifier 3aa
term 3bclassifier 3b

definition 3

""", ], ]) totest['lazy_loading'] = ({'image_loading': 'lazy', 'report_level': 4}, [ ["""\ Lazy loading by default, overridden by :loading: option ("cannot embed" warning ignored). .. image:: dummy.png .. image:: dummy.png :loading: link .. figure:: dummy.png .. figure:: dummy.png :loading: embed """, """\

Lazy loading by default, overridden by :loading: option ("cannot embed" warning ignored).

dummy.png dummy.png
dummy.png
dummy.png
""", ], ]) totest['root_prefix'] = ({'root_prefix': ROOT_PREFIX, 'image_loading': 'embed', 'warning_stream': '', }, [ ["""\ .. image:: /data/blue%20square.png :scale: 100% .. figure:: /data/blue%20square.png """, f'/data/blue%20square.png\n{NO_PIL_SYSTEM_MESSAGE}' '
\n' '/data/blue%20square.png\n' '
\n', ], ]) totest['no_backlinks'] = ({'footnote_backlinks': False}, [ ["""\ Two footnotes [#f1]_ [#f2]_ and two citations [once]_ [twice]_. The latter are referenced a second time [#f2]_ [twice]_. .. [#f1] referenced once .. [#f2] referenced twice .. [once] citation referenced once .. [twice] citation referenced twice """, """\

Two footnotes [1] [2] and two citations [once] [twice].

The latter are referenced a second time [2] [twice].

[once]

citation referenced once

[twice]

citation referenced twice

""", ], ]) totest['syntax_highlight'] = ({'syntax_highlight': 'short', }, [ ["""\ .. code:: shell cat <cat <<EOF Hello World EOF """, ], ["""\ .. role:: shell(code) :language: shell :shell:`cat <cat <<EOF Hello World EOF

""", ], ]) totest['system_messages'] = ({'math_output': 'mathml', 'warning_stream': '', }, [ ["""\ .. image:: https://dummy.png :loading: embed """, """\ https://dummy.png """, ], [f"""\ .. image:: {DATA_ROOT.as_uri()}/circle-broken.svg :loading: embed """, f"""\ """ ], [r"""Broken :math:`\sin \my`. """, """\

Broken \\sin \\my.

"""], ]) totest['system_messages-PIL'] = ({'math_output': 'mathml', 'warning_stream': '', }, [ ["""\ .. image:: dummy.png :scale: 100% :loading: embed """, f"""\ dummy.png """, ], ["""\ .. image:: dummy.mp4 :scale: 100% """, f"""\ """, ], ["""\ .. image:: https://dummy.png :scale: 100% :loading: embed """, f"""\ https://dummy.png """, ], ]) totest['no_system_messages'] = ({'math_output': 'mathml', 'report_level': 4, 'warning_stream': '', }, [ ["""\ .. image:: dummy.png :scale: 100% :loading: embed .. image:: dummy.mp4 :scale: 100% """, """\ dummy.png """, ], [f"""\ .. image:: {DATA_ROOT.as_uri()}/circle-broken.svg :loading: embed """, """\ """], [r'Broken :math:`\sin \my`.', '

Broken \\sin \\my.

\n' ], ]) if __name__ == '__main__': unittest.main()