add sphinx renderer
diff --git a/markdown_it/myst/renderer.py b/markdown_it/myst/doc_renderer.py
similarity index 89%
rename from markdown_it/myst/renderer.py
rename to markdown_it/myst/doc_renderer.py
index 0da8548..0201782 100644
--- a/markdown_it/myst/renderer.py
+++ b/markdown_it/myst/doc_renderer.py
@@ -1,7 +1,10 @@
 """NOTE: this will eventually be moved out of core"""
 from contextlib import contextmanager
+import inspect
 import json
+from os.path import splitext
 from typing import List, Optional
+from urllib.parse import urlparse
 
 import yaml
 
@@ -36,7 +39,7 @@
     return new_document(source_path, settings=settings)
 
 
-class DocRenderer:
+class DocutilsRenderer:
     __output__ = "docutils"
 
     def __init__(
@@ -50,7 +53,7 @@
         self.options = options or {}
         self.rules = {
             k: v
-            for k, v in self.__class__.__dict__.items()
+            for k, v in inspect.getmembers(self, predicate=inspect.ismethod)
             if k.startswith("render_") and k != "render_children"
         }
         self.document = document or make_document()
@@ -93,7 +96,7 @@
         for i, token in enumerate(tokens):
             # skip hidden?
             if f"render_{token.type}" in self.rules:
-                self.rules[f"render_{token.type}"](self, token)
+                self.rules[f"render_{token.type}"](token)
             else:
                 print(f"no render method for: {token.type}")
 
@@ -148,7 +151,7 @@
         for i, token in enumerate(tokens):
             # skip hidden?
             if f"render_{token.type}" in self.rules:
-                self.rules[f"render_{token.type}"](self, token)
+                self.rules[f"render_{token.type}"](token)
             else:
                 print(f"no render method for: {token.type}")
 
@@ -165,7 +168,7 @@
     def render_children(self, token):
         for i, child in enumerate(token.children or []):
             if f"render_{child.type}" in self.rules:
-                self.rules[f"render_{child.type}"](self, child)
+                self.rules[f"render_{child.type}"](child)
             else:
                 print(f"no render method for: {child.type}")
 
@@ -330,10 +333,57 @@
         self.current_node = section
 
     def render_link_open(self, token):
-        # TODO I think this is maybe already handled at this point?
-        # refuri = escape_url(token.target)
-        # TODO identify cross-references
-        refuri = target = token.attrGet("href")
+        if token.markup == "autolink":
+            return self.render_autolink(token)
+
+        ref_node = nodes.reference()
+        self.add_line_and_source_path(ref_node, token)
+        # Check destination is supported for cross-linking and remove extension
+        # TODO escape urls?
+        destination = token.attrGet("href")
+        title = token.attrGet("title")
+        _, ext = splitext(destination)
+        # TODO check for other supported extensions, such as those specified in
+        # the Sphinx conf.py file but how to access this information?
+        # TODO this should probably only remove the extension for local paths,
+        # i.e. not uri's starting with http or other external prefix.
+
+        # if ext.replace('.', '') in self.supported:
+        #     destination = destination.replace(ext, '')
+        ref_node["refuri"] = destination
+        print(token)
+        if title:
+            ref_node["title"] = title
+        next_node = ref_node
+
+        url_check = urlparse(destination)
+        # If there's not a url scheme (e.g. 'https' for 'https:...' links),
+        # or there is a scheme but it's not in the list of known_url_schemes,
+        # then assume it's a cross-reference
+        known_url_schemes = self.config.get("known_url_schemes", None)
+        if known_url_schemes:
+            scheme_known = url_check.scheme in known_url_schemes
+        else:
+            scheme_known = bool(url_check.scheme)
+
+        if not url_check.fragment and not scheme_known:
+            self.handle_cross_reference(token, destination)
+        else:
+            self.current_node.append(next_node)
+            with self.current_node_context(ref_node):
+                self.render_children(token)
+
+    def handle_cross_reference(self, token, destination):
+        # TODO use the docutils error reporting mechanisms, rather than raising
+        if not self.config.get("ignore_missing_refs", False):
+            raise NotImplementedError(
+                "reference not found in current document: {} (lines: {})".format(
+                    destination, token.map
+                )
+            )
+
+    def render_autolink(self, token):
+        refuri = target = escapeHtml(token.attrGet("href"))
         ref_node = nodes.reference(target, target, refuri=refuri)
         self.add_line_and_source_path(ref_node, token)
         self.current_node.append(ref_node)
diff --git a/markdown_it/myst/mocking.py b/markdown_it/myst/mocking.py
index ef6d8d3..af045ed 100644
--- a/markdown_it/myst/mocking.py
+++ b/markdown_it/myst/mocking.py
@@ -111,9 +111,9 @@
         # so that the nested parse does not effect the current renderer,
         # but we use the same env, so that link references, etc
         # are added to the global parse.
-        from .renderer import DocRenderer
+        from .doc_renderer import DocutilsRenderer
 
-        nested_renderer = DocRenderer(
+        nested_renderer = DocutilsRenderer(
             self._renderer.md, document=self.document, current_node=paragraph
         )
         nested_renderer.run_render(tokens, self._renderer.env, ouput_footnotes=False)
diff --git a/markdown_it/myst/sphinx_renderer.py b/markdown_it/myst/sphinx_renderer.py
new file mode 100644
index 0000000..a16da70
--- /dev/null
+++ b/markdown_it/myst/sphinx_renderer.py
@@ -0,0 +1,161 @@
+import copy
+from urllib.parse import unquote
+
+from docutils import nodes
+from docutils.parsers.rst import directives, roles
+
+from .doc_renderer import DocutilsRenderer
+
+
+class SphinxRenderer(DocutilsRenderer):
+    """A mistletoe renderer to populate (in-place) a `docutils.document` AST.
+
+    This is sub-class of `DocutilsRenderer` that handles sphinx cross-referencing.
+    """
+
+    def __init__(self, *args, **kwargs):
+        """Initialise SphinxRenderer
+
+        :param load_sphinx_env: load a basic sphinx environment,
+            when using the renderer as a context manager outside if `sphinx-build`
+        :param sphinx_conf: a dictionary representation of the sphinx `conf.py`
+        :param sphinx_srcdir: a path to a source directory
+          (for example, can be used for `include` statements)
+
+        To use this renderer in a 'standalone' fashion::
+
+            from myst_parser.block_tokens import Document
+
+            with SphinxRenderer(load_sphinx_env=True, sphinx_conf={}) as renderer:
+                renderer.render(Document.read("source text"))
+
+        """
+        self.load_sphinx_env = kwargs.pop("load_sphinx_env", False)
+        self.sphinx_conf = kwargs.pop("sphinx_conf", None)
+        self.sphinx_srcdir = kwargs.pop("sphinx_srcdir", None)
+        super().__init__(*args, **kwargs)
+
+    def handle_cross_reference(self, token, destination):
+        from sphinx import addnodes
+
+        wrap_node = addnodes.pending_xref(
+            reftarget=unquote(destination),
+            reftype="any",
+            refdomain=None,  # Added to enable cross-linking
+            refexplicit=len(token.children) > 0,
+            refwarn=True,
+        )
+        self.add_line_and_source_path(wrap_node, token)
+        title = token.attrGet("title")
+        if title:
+            wrap_node["title"] = title
+        self.current_node.append(wrap_node)
+        text_node = nodes.literal("", "", classes=["xref", "any"])
+        wrap_node.append(text_node)
+        with self.current_node_context(text_node):
+            self.render_children(token)
+
+    def mock_sphinx_env(self, configuration=None, sourcedir=None):
+        """Create a minimimal Sphinx environment;
+        loading sphinx roles, directives, etc.
+        """
+        from sphinx.application import builtin_extensions, Sphinx
+        from sphinx.config import Config
+        from sphinx.environment import BuildEnvironment
+        from sphinx.events import EventManager
+        from sphinx.project import Project
+        from sphinx.registry import SphinxComponentRegistry
+        from sphinx.util.tags import Tags
+
+        class MockSphinx(Sphinx):
+            """Minimal sphinx init to load roles and directives."""
+
+            def __init__(self, confoverrides=None, srcdir=None):
+                self.extensions = {}
+                self.registry = SphinxComponentRegistry()
+                self.html_themes = {}
+                self.events = EventManager(self)
+                self.tags = Tags(None)
+                self.config = Config({}, confoverrides or {})
+                self.config.pre_init_values()
+                self._init_i18n()
+                for extension in builtin_extensions:
+                    self.registry.load_extension(self, extension)
+                # fresh env
+                self.doctreedir = None
+                self.srcdir = srcdir
+                self.confdir = None
+                self.outdir = None
+                self.project = Project(srcdir=srcdir, source_suffix=".md")
+                self.project.docnames = ["mock_docname"]
+                self.env = BuildEnvironment()
+                self.env.setup(self)
+                self.env.temp_data["docname"] = "mock_docname"
+                self.builder = None
+
+                if not confoverrides:
+                    return
+
+                # this code is only required for more complex parsing with extensions
+                for extension in self.config.extensions:
+                    self.setup_extension(extension)
+                buildername = "dummy"
+                self.preload_builder(buildername)
+                self.config.init_values()
+                self.events.emit("config-inited", self.config)
+                import tempfile
+
+                with tempfile.TemporaryDirectory() as tempdir:
+                    # creating a builder attempts to make the doctreedir
+                    self.doctreedir = tempdir
+                    self.builder = self.create_builder(buildername)
+                self.doctreedir = None
+
+        app = MockSphinx(confoverrides=configuration, srcdir=sourcedir)
+        self.document.settings.env = app.env
+        return app
+
+    def __enter__(self):
+        """If `load_sphinx_env=True`, we set up an environment,
+        to parse sphinx roles/directives, outside of a `sphinx-build`.
+
+        This primarily copies the code in `sphinx.util.docutils.docutils_namespace`
+        and `sphinx.util.docutils.sphinx_domains`.
+        """
+        if not self.load_sphinx_env:
+            return super().__enter__()
+
+        # store currently loaded roles/directives, so we can revert on exit
+        self._directives = copy.copy(directives._directives)
+        self._roles = copy.copy(roles._roles)
+        # Monkey-patch directive and role dispatch,
+        # so that sphinx domain-specific markup takes precedence.
+        self._env = self.mock_sphinx_env(
+            configuration=self.sphinx_conf, sourcedir=self.sphinx_srcdir
+        ).env
+        from sphinx.util.docutils import sphinx_domains
+
+        self._sphinx_domains = sphinx_domains(self._env)
+        self._sphinx_domains.enable()
+
+        return super().__enter__()
+
+    def __exit__(self, exception_type, exception_val, traceback):
+        if not self.load_sphinx_env:
+            return super().__exit__(exception_type, exception_val, traceback)
+        # revert loaded roles/directives
+        directives._directives = self._directives
+        roles._roles = self._roles
+        self._directives = None
+        self._roles = None
+        # unregister nodes (see `sphinx.util.docutils.docutils_namespace`)
+        from sphinx.util.docutils import additional_nodes, unregister_node
+
+        for node in list(additional_nodes):
+            unregister_node(node)
+            additional_nodes.discard(node)
+        # revert directive/role function (see `sphinx.util.docutils.sphinx_domains`)
+        self._sphinx_domains.disable()
+        self._sphinx_domains = None
+        self._env = None
+        return super().__exit__(exception_type, exception_val, traceback)
diff --git a/try_running_renderer.py b/try_running_renderer.py
index 6c0a99e..8a92ed2 100644
--- a/try_running_renderer.py
+++ b/try_running_renderer.py
@@ -5,7 +5,7 @@
 from markdown_it.extensions.myst_role import myst_role_plugin
 from markdown_it.extensions.texmath import texmath_plugin
 from markdown_it.extensions.footnote import footnote_plugin
-from markdown_it.myst.renderer import DocRenderer
+from markdown_it.myst.sphinx_renderer import SphinxRenderer
 
 md = (
     MarkdownIt()
@@ -52,7 +52,7 @@
 
 [a][b]
 
-[b]: s
+[b]: s "a"
 
 <div>A</div>
 
@@ -88,11 +88,13 @@
 [^dfg]
 [^dfg]: xyc
 ````
+[dvs]: a
+[dvs]
 """,
     env=env,
 )
 
-doc = DocRenderer(md)
+doc = SphinxRenderer(md)
 doc.run_render(tokens, env)
 print(len(env["foot_refs"]))
 print(doc.document.pformat())