diff --git a/src/moin/converters/_tests/test_rst_in.py b/src/moin/converters/_tests/test_rst_in.py
index a8752bde5..82f28ba16 100644
--- a/src/moin/converters/_tests/test_rst_in.py
+++ b/src/moin/converters/_tests/test_rst_in.py
@@ -218,6 +218,14 @@ def test_field_list(self, input, output):
"Abra example_ arba\n\n.. _example:\n\ntext",
'Abra example arba
text
',
),
+ (
+ "A reference_ with no matching target links to a local Wiki item.",
+ 'A reference with no matching target links to a local Wiki item.
',
+ ),
+ (
+ "`Whitespace is\nnormalized & Case is KEPT.`_",
+ 'Whitespace is\nnormalized & Case is KEPT.
',
+ ),
(
"http://www.python.org/",
'http://www.python.org/
',
diff --git a/src/moin/converters/rst_in.py b/src/moin/converters/rst_in.py
index bfcd1ec5d..82d590466 100644
--- a/src/moin/converters/rst_in.py
+++ b/src/moin/converters/rst_in.py
@@ -18,7 +18,7 @@
import re
import docutils
-from docutils import nodes, utils, writers, core
+from docutils import core, nodes, transforms, utils, writers
from docutils.nodes import reference, literal_block
from docutils.parsers.rst import directives, roles
@@ -31,6 +31,7 @@
from moin.utils.iri import Iri
from moin.utils.tree import html, moin_page, xlink, xinclude
from moin.utils.mime import Type, type_moin_document
+from moin.wikiutil import normalize_pagename
from . import default_registry
from ._util import allowed_uri_scheme, decode_data, normalize_split_text
@@ -781,7 +782,54 @@ def walkabout(node, visitor):
return stop
+class Parser(docutils.parsers.rst.Parser):
+ """reStructuredText parser for the MoinMoin wiki.
+
+ Registers a "transform__" for hyperlink references
+ without matching target__.
+
+ __ https://docutils.sourceforge.io/docs/api/transforms.html
+ """
+
+ config_section = "MoinMoin parser"
+ config_section_dependencies = ("parsers", "restructuredtext parser")
+
+ def get_transforms(self):
+ """Add WikiReferences to the registered transforms."""
+ return super().get_transforms() + [WikiReferences]
+
+
+class WikiReferences(transforms.Transform):
+ """Resolve references without matching target as local wiki references.
+
+ Set the "refuri" attribute to refer to a local wiki item.
+ The value is derived from the node's text content with
+ `moin.wikiutil.normalize_pagename()`.
+
+ Cf. https://docutils.sourceforge.io/docs/api/transforms.html#docinfo.
+ """
+
+ default_priority = 775
+ # Apply between `InternalTargets` (660) and `DanglingReferences` (850)
+
+ def apply(self) -> None:
+ for node in self.document.findall(nodes.reference):
+ # Skip resolved references, unresolvable references, and references with matching target:
+ if node.resolved or "refname" not in node or self.document.nameids.get(node["refname"]):
+ continue
+ # Get the name from the link text (the "refname" attribute is lowercased).
+ wikiname = normalize_pagename(node.astext(), None) # second arg is ignored
+ # Skip references whose "refname" attribute differs from the wikiname (exept for case):
+ if normalize_pagename(node["refname"], None) != wikiname.lower():
+ continue
+ # Resolve the reference:
+ node["refuri"] = wikiname
+ del node["refname"]
+ node.resolved = True
+
+
class Writer(writers.Writer):
+ # Ignored! In moin 2.0, the conversion does not use a Writer component.
supported = ("moin-x-document",)
config_section = "MoinMoin writer"
@@ -924,7 +972,7 @@ def __call__(self, data, contenttype=None, arguments=None):
while True:
input = "\n".join(input)
try:
- docutils_tree = core.publish_doctree(source=input)
+ docutils_tree = core.publish_doctree(source=input, source_path="rST input", parser=Parser())
except utils.SystemMessage as inst:
string_numb = re.match(
re.compile(r":([0-9]*):\s*\(.*?\)\s*(.*)", re.X | re.U | re.M | re.S), str(inst)