Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 49 additions & 15 deletions src/moin/converters/_tests/test_rst_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ def setup_class(self):
("**Text**", "<page><body><p><strong>Text</strong></p></body></page>"),
("*Text*", "<page><body><p><emphasis>Text</emphasis></p></body></page>"),
("``Text``", "<page><body><p><code>Text</code></p></body></page>"),
( # custom role using a CSS class
".. role:: orange\n\n:orange:`colourful` text",
'<page><body><p><span xhtml:class="orange">colourful</span> text</p></body></page>',
),
( # special custom roles for <del> and <ins>
".. role:: del\n.. role:: ins\n\n" ":del:`deleted` text :ins:`inserted` text",
"<page><body><p><del>deleted</del> text <ins>inserted</ins> text</p></body></page>",
),
("a _`Link`", '<page><body><p>a <span id="link">Link</span></p></body></page>'),
(
"`Text <javascript:alert('xss')>`_",
Expand All @@ -57,9 +65,10 @@ def test_base(self, input, output):
data = [
(
"1. a\n b\n c\n\n2. b\n\n d",
"""<page><body><list item-label-generate="ordered"><list-item><list-item-body><p>a
b
c</p></list-item-body></list-item><list-item><list-item-body><p>b</p><p>d</p></list-item-body></list-item></list></body></page>""",
'<page><body><list item-label-generate="ordered">'
"<list-item><list-item-body><p>a\nb\nc</p></list-item-body></list-item>"
"<list-item><list-item-body><p>b</p><p>d</p></list-item-body></list-item>"
"</list></body></page>",
),
(
"1. a\n2. b\n\nA. c\n\na. A\n\n 1. B\n\n 2. C\n\n",
Expand All @@ -73,12 +82,11 @@ def test_base(self, input, output):
"what\n def\n\nhow\n to",
"<page><body><list><list-item><list-item-label>what</list-item-label><list-item-body><p>def</p></list-item-body></list-item><list-item><list-item-label>how</list-item-label><list-item-body><p>to</p></list-item-body></list-item></list></body></page>",
),
# starting an ordered list with a value other than 1 generates an error
# nested in a block-quote and starting with a value other than 1
(
" 3. A\n #. B",
'<page><body><blockquote><list item-label-generate="ordered"><list-item><list-item-body><p>A</p>'
'<page><body><blockquote><list item-label-generate="ordered" list-start="3"><list-item><list-item-body><p>A</p>'
"</list-item-body></list-item><list-item><list-item-body><p>B</p></list-item-body></list-item></list>"
'<admonition type="error"><p>Enumerated list start value not ordinal-1: "3" (ordinal 3)</p></admonition>'
"</blockquote></body></page>",
),
]
Expand Down Expand Up @@ -353,19 +361,45 @@ def test_table(self, input, output):
self.do(input, output)

data = [
# bibliographic data (visible meta-data)
(
":Author: Test\n:Version: $Revision: 1.17 $\n:Copyright: c\n:Test: t",
"<page><body><table><table-body><table-row><table-cell><strong>Author:</strong></table-cell><table-cell>Test</table-cell></table-row><table-row><table-cell><strong>Version:</strong></table-cell><table-cell>1.17</table-cell></table-row><table-row><table-cell><strong>Copyright:</strong></table-cell><table-cell>c</table-cell></table-row><table-row><table-cell><strong>Test:</strong></table-cell><table-cell><p>t</p></table-cell></table-row></table-body></table></body></page>",
),
(
"""
.. note::
:name: note-id

An admonition of type "note"
""",
'<page><body><span id="note-id" /><admonition type="note"><p>An admonition of type "note"</p></admonition></body></page>',
),
# admonitions (hint, info, warning, error, ...)
(
".. note::\n" " :name: note-id\n\n" ' An admonition of type "note"',
'<page><body><span id="note-id" /><admonition type="note">'
'<p>An admonition of type "note"</p></admonition></body></page>',
),
# use an attention for a generic admonition
(
".. admonition:: Generic Admonition\n\n" " Be alert!",
'<page><body><admonition type="attention">'
'<strong xhtml:class="title">Generic Admonition</strong>'
"<p>Be alert!</p></admonition></body></page>",
),
# Moin uses admonitions also for system messages
(
"Unbalanced *inline markup.",
'<page><body><p>Unbalanced <span id="problematic-1" /><a xhtml:class="red" xlink:href="#system-message-1">*</a>inline markup.</p>'
'<span id="system-message-1" /><admonition type="caution">'
'<p><strong xhtml:class="title">System Message: WARNING/2</strong> (rST input line 1) '
'<span id="system-message-1" /><a xlink:href="#problematic-1">backlink</a></p>'
"<p>Inline emphasis start-string without end-string.</p>"
"</admonition></body></page>",
),
# TODO: this currently fails because the parsing error is not cleared.
# (
# "Sections must not be nested in body elements.\n\n"
# " not allowed\n"
# " -----------\n",
# "<page><body><p>Sections must not be nested in body elements.</p><blockquote>"
# '<admonition type="error"><p><strong xhtml:class="title">System Message: ERROR/3</strong> (rST input line 4)</p>'
# "<p>Unexpected section title.</p>"
# "<blockcode>not allowed\n-----------</blockcode>"
# "</admonition></blockquote></body></page>"
# )
]

@pytest.mark.parametrize("input,output", data)
Expand Down
61 changes: 53 additions & 8 deletions src/moin/converters/rst_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,9 @@ def visit_enumerated_list(self, node):
type = enum_style.get(node["enumtype"], None)
if type:
new_node.set(moin_page.list_style_type, type)
startvalue = node.get("start", 1)
if startvalue > 1:
new_node.set(moin_page.list_start, str(startvalue))
self.open_moin_page_node(new_node, node)

def depart_enumerated_list(self, node):
Expand Down Expand Up @@ -434,10 +437,21 @@ def depart_image(self, node):
self.close_moin_page_node()

def visit_inline(self, node):
pass
classes = node["classes"]
moin_node = moin_page.span
attrib = {}
if "ins" in classes:
moin_node = moin_page.ins
classes.remove("ins")
if "del" in classes:
moin_node = moin_page.del_
classes.remove("del")
if classes:
attrib[html.class_] = " ".join(classes)
self.open_moin_page_node(moin_node(attrib=attrib))

def depart_inline(self, node):
pass
self.close_moin_page_node()

def visit_label(self, node):
if self.status[-1] == "footnote":
Expand Down Expand Up @@ -529,6 +543,7 @@ def visit_paragraph(self, node):
footnote_node = self.footnotes.get(self.footnote_lable, None)
if footnote_node:
# TODO: `node.astext()` ignores all markup!
# "moin" footnotes support inline markup
footnote_node.append(node.astext())
raise nodes.SkipNode
self.open_moin_page_node(moin_page.p(), node)
Expand All @@ -538,10 +553,15 @@ def depart_paragraph(self, node):
self.close_moin_page_node()

def visit_problematic(self, node):
pass
if node.hasattr("refid"):
refuri = f"#{node['refid']}"
attrib = {xlink.href: refuri, html.class_: "red"}
self.open_moin_page_node(moin_page.a(attrib=attrib), node)
else:
self.open_moin_page_node(moin_page.span(attrib={html.class_: "red"}))

def depart_problematic(self, node):
pass
self.close_moin_page_node()

def visit_reference(self, node):
refuri = node.get("refuri", "")
Expand Down Expand Up @@ -584,6 +604,7 @@ def visit_reference(self, node):
return

if not allowed_uri_scheme(refuri):
# TODO: prepend "wiki.local" as in "moin_in"?
self.visit_error(node)
return

Expand Down Expand Up @@ -678,9 +699,25 @@ def depart_superscript(self, node):
self.close_moin_page_node()

def visit_system_message(self, node):
# we have encountered a parsing error, insert an error message
# TODO: also show error level and line number.
self.visit_admonition(node, "error")
# an element reporting a parsing issue (DEBUG, INFO, WARNING, ERROR, or SEVERE)
# TODO: handle node['backrefs'] to <problematic> element.
if node.get("level", 4) < 3:
self.visit_admonition(node, "caution")
else:
self.visit_admonition(node, "error")
self.open_moin_page_node(moin_page.p())
self.open_moin_page_node(moin_page.strong(attrib={html.class_: "title"}))
title = f"{node['type']}/{node['level']}"
self.current_node.append(f"System Message: {title}")
self.close_moin_page_node() # </strong>
if node.hasattr("line"):
self.current_node.append(f" ({node['source']} line {node['line']}) ")
if node.get("backrefs", []):
backrefuri = f"#{node['backrefs'][0]}"
self.open_moin_page_node(moin_page.a(attrib={xlink.href: backrefuri}), node)
self.current_node.append("backlink")
self.close_moin_page_node() # </a>
self.close_moin_page_node() # </p>

def depart_system_message(self, node):
self.depart_admonition(node)
Expand Down Expand Up @@ -823,6 +860,8 @@ class Parser(docutils.parsers.rst.Parser):
Registers a "transform__" for hyperlink references
without matching target__.

Also register the "transforms" that are added by default for a Docutils writer.

__ https://docutils.sourceforge.io/docs/api/transforms.html
__ https://docutils.sourceforge.io/docs/ref/doctree.html#target
"""
Expand All @@ -832,7 +871,13 @@ class Parser(docutils.parsers.rst.Parser):

def get_transforms(self):
"""Add WikiReferences to the registered transforms."""
return super().get_transforms() + [WikiReferences]
moin_parser_transforms = [
WikiReferences,
transforms.universal.StripClassesAndElements,
transforms.universal.Messages,
transforms.universal.FilterMessages,
]
return super().get_transforms() + moin_parser_transforms


class WikiReferences(transforms.Transform):
Expand Down