summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:23:02 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-29 04:23:02 +0000
commit943e3dc057eca53e68ddec51529bd6a1279ebd8e (patch)
tree61fb7bac619a56dfbcdcbdb7b0d4d6535fc36fe9
parentInitial commit. (diff)
downloadmyst-parser-943e3dc057eca53e68ddec51529bd6a1279ebd8e.tar.xz
myst-parser-943e3dc057eca53e68ddec51529bd6a1279ebd8e.zip
Adding upstream version 0.18.1.upstream/0.18.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rwxr-xr-x.github/workflows/docutils_setup.py56
-rw-r--r--.github/workflows/tests.yml152
-rw-r--r--.gitignore135
-rw-r--r--.pre-commit-config.yaml59
-rw-r--r--.readthedocs.yml14
-rw-r--r--CHANGELOG.md728
-rw-r--r--LICENSE21
-rw-r--r--README.md64
-rw-r--r--codecov.yml10
-rw-r--r--docs/.gitignore2
-rw-r--r--docs/Makefile27
-rw-r--r--docs/_static/custom.css24
-rw-r--r--docs/_static/logo-square.svg1
-rw-r--r--docs/_static/logo-wide.svg1
-rw-r--r--docs/api/reference.rst96
-rw-r--r--docs/conf.py169
-rw-r--r--docs/configuration.md106
-rw-r--r--docs/develop/_changelog.md4
-rw-r--r--docs/develop/architecture.md27
-rw-r--r--docs/develop/background.md82
-rw-r--r--docs/develop/contributing.md85
-rw-r--r--docs/develop/index.md15
-rw-r--r--docs/develop/test_infrastructure.md53
-rw-r--r--docs/docutils.md82
-rw-r--r--docs/faq/index.md267
-rw-r--r--docs/faq/snippets/include-md.md1
-rw-r--r--docs/faq/snippets/include-rst.rst1
-rw-r--r--docs/index.md160
-rw-r--r--docs/intro.md250
-rw-r--r--docs/syntax/example.txt1
-rw-r--r--docs/syntax/img/fun-fish.pngbin0 -> 92086 bytes
-rw-r--r--docs/syntax/optional.md1002
-rw-r--r--docs/syntax/reference.md261
-rw-r--r--docs/syntax/roles-and-directives.md421
-rw-r--r--docs/syntax/syntax.md491
-rw-r--r--example-include.md2
-rw-r--r--myst_parser/__init__.py10
-rw-r--r--myst_parser/_compat.py11
-rw-r--r--myst_parser/_docs.py198
-rw-r--r--myst_parser/cli.py42
-rw-r--r--myst_parser/config/__init__.py1
-rw-r--r--myst_parser/config/dc_validators.py161
-rw-r--r--myst_parser/config/main.py409
-rw-r--r--myst_parser/docutils_.py6
-rw-r--r--myst_parser/mdit_to_docutils/__init__.py1
-rw-r--r--myst_parser/mdit_to_docutils/base.py1483
-rw-r--r--myst_parser/mdit_to_docutils/html_to_nodes.py139
-rw-r--r--myst_parser/mdit_to_docutils/sphinx_.py245
-rw-r--r--myst_parser/mdit_to_docutils/utils.py36
-rw-r--r--myst_parser/mocking.py514
-rw-r--r--myst_parser/parsers/__init__.py1
-rw-r--r--myst_parser/parsers/directives.py190
-rw-r--r--myst_parser/parsers/docutils_.py275
-rw-r--r--myst_parser/parsers/mdit.py123
-rw-r--r--myst_parser/parsers/parse_html.py440
-rw-r--r--myst_parser/parsers/sphinx_.py69
-rw-r--r--myst_parser/py.typed1
-rw-r--r--myst_parser/sphinx_.py6
-rw-r--r--myst_parser/sphinx_ext/__init__.py1
-rw-r--r--myst_parser/sphinx_ext/directives.py136
-rw-r--r--myst_parser/sphinx_ext/main.py60
-rw-r--r--myst_parser/sphinx_ext/mathjax.py118
-rw-r--r--myst_parser/sphinx_ext/myst_refs.py282
-rw-r--r--pyproject.toml110
-rw-r--r--tests/test_cli.py15
-rw-r--r--tests/test_commonmark/commonmark.json5194
-rwxr-xr-xtests/test_commonmark/spec.sh26
-rw-r--r--tests/test_commonmark/test_commonmark.py44
-rw-r--r--tests/test_docutils.py116
-rw-r--r--tests/test_html/html_ast.md124
-rw-r--r--tests/test_html/html_round_trip.md87
-rw-r--r--tests/test_html/html_to_nodes.md199
-rw-r--r--tests/test_html/test_html_to_nodes.py35
-rw-r--r--tests/test_html/test_parse_html.py41
-rw-r--r--tests/test_renderers/fixtures/amsmath.md52
-rw-r--r--tests/test_renderers/fixtures/containers.md31
-rw-r--r--tests/test_renderers/fixtures/definition_lists.md56
-rw-r--r--tests/test_renderers/fixtures/directive_options.md159
-rw-r--r--tests/test_renderers/fixtures/directive_parsing.txt141
-rw-r--r--tests/test_renderers/fixtures/docutil_directives.md436
-rw-r--r--tests/test_renderers/fixtures/docutil_roles.md131
-rw-r--r--tests/test_renderers/fixtures/docutil_syntax_elements.md794
-rw-r--r--tests/test_renderers/fixtures/docutil_syntax_extensions.txt139
-rw-r--r--tests/test_renderers/fixtures/dollarmath.md75
-rw-r--r--tests/test_renderers/fixtures/eval_rst.md23
-rw-r--r--tests/test_renderers/fixtures/mock_include.md62
-rw-r--r--tests/test_renderers/fixtures/mock_include_errors.md24
-rw-r--r--tests/test_renderers/fixtures/myst-config.txt176
-rw-r--r--tests/test_renderers/fixtures/reporter_warnings.md180
-rw-r--r--tests/test_renderers/fixtures/sphinx_directives.md441
-rw-r--r--tests/test_renderers/fixtures/sphinx_roles.md632
-rw-r--r--tests/test_renderers/fixtures/sphinx_syntax_elements.md800
-rw-r--r--tests/test_renderers/fixtures/tables.md148
-rw-r--r--tests/test_renderers/test_error_reporting.py22
-rw-r--r--tests/test_renderers/test_fixtures_docutils.py107
-rw-r--r--tests/test_renderers/test_fixtures_sphinx.py119
-rw-r--r--tests/test_renderers/test_include_directive.py52
-rw-r--r--tests/test_renderers/test_myst_config.py38
-rw-r--r--tests/test_renderers/test_myst_refs.py38
-rw-r--r--tests/test_renderers/test_myst_refs/doc.xml5
-rw-r--r--tests/test_renderers/test_myst_refs/doc_nested.xml6
-rw-r--r--tests/test_renderers/test_myst_refs/doc_with_extension.xml5
-rw-r--r--tests/test_renderers/test_myst_refs/duplicate.xml9
-rw-r--r--tests/test_renderers/test_myst_refs/missing.xml3
-rw-r--r--tests/test_renderers/test_myst_refs/null.xml1
-rw-r--r--tests/test_renderers/test_myst_refs/ref.xml9
-rw-r--r--tests/test_renderers/test_myst_refs/ref_colon.xml9
-rw-r--r--tests/test_renderers/test_myst_refs/ref_nested.xml10
-rw-r--r--tests/test_renderers/test_parse_directives.py46
-rw-r--r--tests/test_sphinx/conftest.py127
-rw-r--r--tests/test_sphinx/sourcedirs/.gitignore1
-rw-r--r--tests/test_sphinx/sourcedirs/basic/conf.py2
-rw-r--r--tests/test_sphinx/sourcedirs/basic/content.md104
-rw-r--r--tests/test_sphinx/sourcedirs/basic/example.jpgbin0 -> 57841 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/basic/index.md3
-rw-r--r--tests/test_sphinx/sourcedirs/basic/orphan.md5
-rw-r--r--tests/test_sphinx/sourcedirs/commonmark_only/conf.py3
-rw-r--r--tests/test_sphinx/sourcedirs/commonmark_only/index.md7
-rw-r--r--tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py20
-rw-r--r--tests/test_sphinx/sourcedirs/extended_syntaxes/fun-fish.pngbin0 -> 92086 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/extended_syntaxes/index.md67
-rw-r--r--tests/test_sphinx/sourcedirs/fieldlist/conf.py4
-rw-r--r--tests/test_sphinx/sourcedirs/fieldlist/index.md19
-rw-r--r--tests/test_sphinx/sourcedirs/footnotes/conf.py2
-rw-r--r--tests/test_sphinx/sourcedirs/footnotes/footnote_md.md29
-rw-r--r--tests/test_sphinx/sourcedirs/footnotes/footnote_rst.rst26
-rw-r--r--tests/test_sphinx/sourcedirs/footnotes/index.md4
-rw-r--r--tests/test_sphinx/sourcedirs/gettext/conf.py4
-rw-r--r--tests/test_sphinx/sourcedirs/gettext/fr/LC_MESSAGES/index.po127
-rw-r--r--tests/test_sphinx/sourcedirs/gettext/fun-fish.pngbin0 -> 92086 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/gettext/index.md63
-rw-r--r--tests/test_sphinx/sourcedirs/gettext/poisson-amusant.pngbin0 -> 92086 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/heading_slug_func/conf.py6
-rw-r--r--tests/test_sphinx/sourcedirs/heading_slug_func/index.md3
-rw-r--r--tests/test_sphinx/sourcedirs/include_from_rst/conf.py2
-rw-r--r--tests/test_sphinx/sourcedirs/include_from_rst/include.md5
-rw-r--r--tests/test_sphinx/sourcedirs/include_from_rst/index.rst5
-rw-r--r--tests/test_sphinx/sourcedirs/includes/conf.py2
-rw-r--r--tests/test_sphinx/sourcedirs/includes/example1.jpgbin0 -> 57841 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/includes/include1.inc.md12
-rw-r--r--tests/test_sphinx/sourcedirs/includes/include_code.py2
-rw-r--r--tests/test_sphinx/sourcedirs/includes/include_literal.txt6
-rw-r--r--tests/test_sphinx/sourcedirs/includes/index.md31
-rw-r--r--tests/test_sphinx/sourcedirs/includes/subfolder/example2.jpgbin0 -> 57841 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/includes/subfolder/include2.inc.md21
-rw-r--r--tests/test_sphinx/sourcedirs/mathjax/conf.py12
-rw-r--r--tests/test_sphinx/sourcedirs/mathjax/index.md5
-rw-r--r--tests/test_sphinx/sourcedirs/references/conf.py3
-rw-r--r--tests/test_sphinx/sourcedirs/references/file_link.txt0
-rw-r--r--tests/test_sphinx/sourcedirs/references/index.md54
-rw-r--r--tests/test_sphinx/sourcedirs/references/other.md3
-rw-r--r--tests/test_sphinx/sourcedirs/references/subfolder/file_link2.txt0
-rw-r--r--tests/test_sphinx/sourcedirs/references/subfolder/other2.md3
-rw-r--r--tests/test_sphinx/sourcedirs/references_singlehtml/conf.py6
-rw-r--r--tests/test_sphinx/sourcedirs/references_singlehtml/index.md15
-rw-r--r--tests/test_sphinx/sourcedirs/references_singlehtml/other/index.md6
-rw-r--r--tests/test_sphinx/sourcedirs/references_singlehtml/other/other.md9
-rw-r--r--tests/test_sphinx/sourcedirs/references_singlehtml/other/other2.md1
-rw-r--r--tests/test_sphinx/sourcedirs/substitutions/conf.py4
-rw-r--r--tests/test_sphinx/sourcedirs/substitutions/fun-fish.pngbin0 -> 92086 bytes
-rw-r--r--tests/test_sphinx/sourcedirs/substitutions/index.md56
-rw-r--r--tests/test_sphinx/sourcedirs/substitutions/other.md3
-rw-r--r--tests/test_sphinx/sourcedirs/texi_table/conf.py2
-rw-r--r--tests/test_sphinx/sourcedirs/texi_table/index.md3
-rw-r--r--tests/test_sphinx/test_sphinx_builds.py565
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_basic.resolved.xml154
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_basic.sphinx4.html252
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_basic.sphinx5.html252
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_basic.xml155
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_commonmark_only.html28
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_commonmark_only.xml10
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html166
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml90
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx4.html131
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx5.html149
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.xml82
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx4.html147
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx5.html213
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_footnotes.xml62
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_gettext.pot81
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_gettext_additional_targets.pot129
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_gettext_html.resolved.xml93
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx4.html162
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx5.html162
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_gettext_html.xml93
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.html22
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml7
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml7
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_include_from_rst.xml10
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_includes.html135
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_includes.xml113
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_references.html195
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml115
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_references.xml95
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html111
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml20
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml18
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_substitutions.html82
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_substitutions.other.xml6
-rw-r--r--tests/test_sphinx/test_sphinx_builds/test_substitutions.xml55
-rw-r--r--tox.ini63
201 files changed, 25925 insertions, 0 deletions
diff --git a/.github/workflows/docutils_setup.py b/.github/workflows/docutils_setup.py
new file mode 100755
index 0000000..9d74522
--- /dev/null
+++ b/.github/workflows/docutils_setup.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+"""Script to convert package setup to myst-docutils."""
+import sys
+
+import tomlkit
+
+
+def modify_toml(content: str) -> str:
+ """Modify `pyproject.toml`."""
+ doc = tomlkit.parse(content)
+
+ # change name of package
+ doc["project"]["name"] = "myst-docutils"
+
+ # move dependency on docutils and sphinx to extra
+ dependencies = []
+ sphinx_extra = []
+ for dep in doc["project"]["dependencies"]:
+ if dep.startswith("docutils") or dep.startswith("sphinx"):
+ sphinx_extra.append(dep)
+ else:
+ dependencies.append(dep)
+ doc["project"]["dependencies"] = dependencies
+ doc["project"]["optional-dependencies"]["sphinx"] = sphinx_extra
+
+ return tomlkit.dumps(doc)
+
+
+def modify_readme(content: str) -> str:
+ """Modify README.md."""
+ content = content.replace("myst-parser", "myst-docutils")
+ content = content.replace(
+ "# MyST-Parser",
+ "# MyST-Parser\n\nNote: myst-docutils is identical to myst-parser, "
+ "but without installation requirements on sphinx",
+ )
+ content = content.replace("myst-docutils.readthedocs", "myst-parser.readthedocs")
+ content = content.replace(
+ "readthedocs.org/projects/myst-docutils", "readthedocs.org/projects/myst-parser"
+ )
+ return content
+
+
+if __name__ == "__main__":
+ project_path = sys.argv[1]
+ readme_path = sys.argv[2]
+ with open(project_path) as f:
+ content = f.read()
+ content = modify_toml(content)
+ with open(project_path, "w") as f:
+ f.write(content)
+ with open(readme_path) as f:
+ content = f.read()
+ content = modify_readme(content)
+ with open(readme_path, "w") as f:
+ f.write(content)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..c05356e
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,152 @@
+name: continuous-integration
+
+on:
+ push:
+ branches: [master]
+ tags:
+ - "v[0-9]+.[0-9]+.[0-9]+*"
+ pull_request:
+
+jobs:
+ pre-commit:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.8"
+ - uses: pre-commit/action@v2.0.0
+
+ tests:
+
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.7", "3.8", "3.9", "3.10"]
+ sphinx: [">=5,<6"]
+ os: [ubuntu-latest]
+ include:
+ - os: ubuntu-latest
+ python-version: "3.8"
+ sphinx: ">=4,<5"
+ - os: windows-latest
+ python-version: "3.8"
+ sphinx: ">=4,<5"
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v1
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -e .[linkify,testing]
+ pip install --upgrade-strategy "only-if-needed" "sphinx${{ matrix.sphinx }}"
+ - name: Run pytest
+ run: |
+ pytest --cov=myst_parser --cov-report=xml --cov-report=term-missing
+ coverage xml
+ - name: Upload to Codecov
+ if: github.repository == 'executablebooks/MyST-Parser' && matrix.python-version == 3.8
+ uses: codecov/codecov-action@v1
+ with:
+ name: myst-parser-pytests
+ flags: pytests
+ file: ./coverage.xml
+ fail_ci_if_error: true
+
+ check-myst-docutils:
+
+ name: Check myst-docutils install
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ docutils-version: ["0.17", "0.18", "0.19"]
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout@v2
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.8"
+ - name: Install setup
+ run: |
+ python -m pip install --upgrade pip
+ pip install tomlkit
+ - name: Modify setup
+ run: python .github/workflows/docutils_setup.py pyproject.toml README.md
+ - name: Install dependencies
+ run: |
+ pip install .
+ pip install pytest~=6.2 pytest-param-files~=0.3.3 pygments docutils==${{ matrix.docutils-version }}
+ - name: ensure sphinx is not installed
+ run: |
+ python -c "\
+ try:
+ import sphinx
+ except ImportError:
+ pass
+ else:
+ raise AssertionError()"
+ - name: Run pytest for docutils-only tests
+ run: pytest tests/test_docutils.py tests/test_renderers/test_fixtures_docutils.py tests/test_renderers/test_include_directive.py
+ - name: Run docutils CLI
+ run: echo "test" | myst-docutils-html
+
+ publish:
+
+ name: Publish myst-parser to PyPi
+ needs: [pre-commit, tests, check-myst-docutils]
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source
+ uses: actions/checkout@v2
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.8"
+ - name: install flit
+ run: |
+ pip install flit~=3.4
+ - name: Build and publish
+ run: |
+ flit publish
+ env:
+ FLIT_USERNAME: __token__
+ FLIT_PASSWORD: ${{ secrets.PYPI_KEY }}
+
+ publish-docutils:
+
+ name: Publish myst-docutils to PyPi
+ needs: [publish]
+ if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout source
+ uses: actions/checkout@v2
+ - name: Set up Python 3.8
+ uses: actions/setup-python@v1
+ with:
+ python-version: "3.8"
+ - name: install flit and tomlkit
+ run: |
+ pip install flit~=3.4 tomlkit
+ - name: Modify setup
+ run: python .github/workflows/docutils_setup.py pyproject.toml README.md
+ - name: Build and publish
+ run: |
+ flit publish
+ env:
+ FLIT_USERNAME: __token__
+ FLIT_PASSWORD: ${{ secrets.PYPI_KEY_DOCUTILS }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dc4ce70
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,135 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+!tests/**/*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+_archive/
+
+.vscode/
+.DS_Store
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..aee3233
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,59 @@
+# Install pre-commit hooks via
+# pre-commit install
+
+exclude: >
+ (?x)^(
+ \.vscode/settings\.json|
+ tests/test_commonmark/commonmark\.json|
+ .*\.xml|
+ tests/.*/.*\.md
+ )$
+
+repos:
+
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.3.0
+ hooks:
+ - id: check-json
+ - id: check-yaml
+ - id: end-of-file-fixer
+ - id: trailing-whitespace
+
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v2.38.2
+ hooks:
+ - id: pyupgrade
+ args: [--py37-plus]
+
+ - repo: https://github.com/PyCQA/isort
+ rev: 5.10.1
+ hooks:
+ - id: isort
+
+ - repo: https://github.com/psf/black
+ rev: 22.8.0
+ hooks:
+ - id: black
+
+ - repo: https://github.com/PyCQA/flake8
+ rev: 5.0.4
+ hooks:
+ - id: flake8
+ additional_dependencies:
+ - flake8-comprehensions
+ - flake8-bugbear
+ # - flake8-self~=0.2.2
+
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v0.971
+ hooks:
+ - id: mypy
+ args: [--config-file=pyproject.toml]
+ additional_dependencies:
+ - sphinx~=5.0
+ - markdown-it-py>=1.0.0,<3.0.0
+ - mdit-py-plugins~=0.3.1
+ files: >
+ (?x)^(
+ myst_parser/.*py|
+ )$
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..43b8526
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,14 @@
+version: 2
+
+python:
+ version: "3"
+ install:
+ - method: pip
+ path: .
+ extra_requirements:
+ - linkify
+ - rtd
+
+sphinx:
+ builder: html
+ fail_on_warning: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..0fe72f4
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,728 @@
+# Changelog
+
+## 0.18.1 - 2022-27-09
+
+Full Changelog: [v0.18.0...v0.18.1](https://github.com/executablebooks/MyST-Parser/compare/v0.18.0...v0.18.1)
+
+- ⬆️ UPGRADE: docutils 0.19 support (#611)
+- ✨ NEW: Add `attrs_image` (experimental) extension (#620)
+ - e.g. `![image](image.png){#id .class width=100px}`
+ - See: [Optional syntax section](docs/syntax/optional.md)
+ - **Important**: This is an experimental extension, and may change in future releases
+
+## 0.18.0 - 2022-06-07
+
+Full Changelog: [v0.17.2...v0.18.0](https://github.com/executablebooks/MyST-Parser/compare/v0.17.2...v0.18.0)
+
+This release adds support for Sphinx v5 (dropping v3), restructures the code base into modules, and also restructures the documentation, to make it easier for developers/users to follow.
+
+It also introduces **document-level configuration** *via* the Markdown top-matter, under the `myst` key.
+See the [Local configuration](docs/configuration.md) section for more information.
+
+### Breaking changes
+
+This should not be breaking, for general users of the sphinx extension (with `sphinx>3`),
+but will be for anyone directly using the Python API, mainly just requiring changes in import module paths.
+
+The `to_docutils`, `to_html`, `to_tokens` (from `myst_parser/main.py`) and `mock_sphinx_env`/`parse` (from `myst_parser.sphinx_renderer.py`) functions have been removed, since these were primarily for internal testing.
+Instead, for single page builds, users should use the docutils parser API/CLI (see [](docs/docutils.md)),
+and for testing, functionality has been moved to <https://github.com/chrisjsewell/sphinx-pytest>.
+
+The top-level `html_meta` and `substitutions` top-matter keys have also been deprecated (i.e. they will still work but will emit a warning), as they now form part of the `myst` config, e.g.
+
+```yaml
+---
+html_meta:
+ "description lang=en": "metadata description"
+substitutions:
+ key1: I'm a **substitution**
+---
+```
+
+is replaced by:
+
+```yaml
+---
+myst:
+ html_meta:
+ "description lang=en": "metadata description"
+ substitutions:
+ key1: I'm a **substitution**
+---
+```
+
+### Key PRs
+
+- ♻️📚 Restructure code base and documentation (#566)
+- ⬆️ Drop Sphinx 3 and add Sphinx 5 support (#579)
+- 🐛 FIX: `parse_directive_text` when body followed by options (#580)
+- 🐛 FIX: floor table column widths to integers (#568), thanks to @Jean-Abou-Samra!
+
+## 0.17.2 - 2022-04-17
+
+Full Changelog: [v0.17.1...v0.17.2](https://github.com/executablebooks/MyST-Parser/compare/v0.17.1...v0.17.2)
+
+- ♻️ REFACTOR: Replace `attrs` by `dataclasses` for configuration (#557)
+
+## 0.17.1 - 2022-04-15
+
+Full Changelog: [v0.17.0...v0.17.1](https://github.com/executablebooks/MyST-Parser/compare/v0.17.0...v0.17.1)
+
+- 🐛 FIX: Heading anchor resolution for parallel builds (#525)
+- 🔧 MAINTAIN: Move packaging from setuptools to flit (#553)
+- 🔧 MAINTAIN: Directly specify attrs dependency (#555)
+
+## 0.17.0 - 2022-02-11
+
+This release contains a number of breaking improvements.
+
+Full Changelog: [v0.16.1...v0.17.0](https://github.com/executablebooks/MyST-Parser/compare/v0.16.1...v0.17.0)
+
+### ‼️ Markdown link resolution improvements
+
+**WARNING: This is a breaking change for links that rely on auto-generated anchor links**. You should now [manually enable auto-generated anchor links](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html?highlight=anchor#auto-generated-header-anchors) if you see errors like `WARNING reference target not found`.
+
+Markdown links are of the format `[text](link)`.
+MyST-Parser looks to smartly resolve such links, by identifying if they are:
+
+1. A link to an external resource, e.g. `[text](http://example.com)`
+2. A link to another source document, e.g. `[text](file.md)`
+ - If `header-anchors` are enabled, anchor links are also supported, e.g. `[text](file.md#anchor)`
+3. A link to an internal sphinx cross-reference, e.g. `[text](my-reference)`
+
+an additional situation is now supported:
+
+4. A link to a source file, which is not a document, e.g. `[text](file.js)`. This behaves similarly to the sphinx `download` role.
+
+In addition, configuration to more finely tune this behaviour has been added.
+
+- `myst_all_links_external=True`, will make all links be treated as (1)
+- `myst_url_schemes=("http", "https")`, sets what URL schemes are treated as (1)
+- `myst_ref_domains=("std", "py")`, sets what Sphinx reference domains are checked, when handling (3)
+
+See [Markdown Links and Referencing](docs/syntax/syntax.md#markdown-links-and-referencing) for more information.
+
+### ‼️ Dollarmath is now disabled by default
+
+**WARNING: This is a breaking change for dollar math**. You should now manually enable dollar math (see below).
+
+The default configuration is now `myst_enable_extensions=()`, instead of `myst_enable_extensions=("dollarmath",)`.
+If you are using math enclosed in `$` or `$$` in your documents, you should enable `dollarmath` explicitly.
+
+See [Dollar delimited math](docs/syntax/optional.md#math-shortcuts) for more information.
+
+### ⬆️ Drop Python 3.6 support
+
+MyST-Parser now supports, and is tested against, Python 3.7 to 3.10.
+
+### ✨ Add the `strikethrough` extension and `myst_gfm_only` configuration
+
+The `strikethrough` extension allows text within `~~` delimiters to have a strike-through (horizontal line) placed over it.
+For example, `~~strikethrough with *emphasis*~~` renders as: ~~strikethrough with *emphasis*~~.
+
+**Important**: This extension is currently only supported for HTML output.
+
+See [Strikethrough](docs/syntax/optional.md#strikethrough) for more information.
+
+The `myst_gfm_only=True` configuration sets up specific configuration, to enable compliance only with [GitHub-flavored Markdown](https://github.github.com/gfm/), including enabling the `strikethrough`, `tasklist` and `linkify` extensions, but disabling support for roles and directives.
+
+### ✨ Add `myst_title_to_header` configuration
+
+Setting `myst_title_to_header=True`, allows for a `title` key in the frontmatter to be used as the document title.
+for example:
+
+```md
+---
+title: My Title with *emphasis*
+---
+```
+
+would be equivalent to:
+
+```md
+# My Title with *emphasis*
+```
+
+See [Front matter](docs/syntax/syntax.md#front-matter) for more information.
+
+### 👌 Internal improvements
+
+👌 IMPROVE: Convert nested headings to rubrics.
+Headings within directives are not directly supported by sphinx, since they break the structure of the document. Previously myst-parser would emit a `myst.nested_header` warning, but still generate the heading, leading to unexpected outcomes.
+Now the warning is still emitted, but also the heading is rendered as a [rubric](https://docutils.sourceforge.io/docs/ref/rst/directives.html#rubric) non-structural heading (i.e. it will not show in the ToC).
+
+Other internal improvements primarily focused in improving support for the for "docutils-only" use, introduced in `v0.16`:
+
+- ♻️ REFACTOR: `default_parser` -> `create_md_parser` in [#474](https://github.com/executablebooks/MyST-Parser/pull/474)
+- 👌 IMPROVE: Add `bullet` attribute to `bullet_list` node in [#465](https://github.com/executablebooks/MyST-Parser/pull/465)
+- 👌 IMPROVE: Use correct renderer for `state.inline_text` in [#466](https://github.com/executablebooks/MyST-Parser/pull/466)
+- 👌 IMPROVE: Docutils parser settings in [#476](https://github.com/executablebooks/MyST-Parser/pull/476)
+- 🐛 FIX: front-matter rendering with docutils in [#477](https://github.com/executablebooks/MyST-Parser/pull/477)
+- 👌 IMPROVE: Code block highlighting in [#478](https://github.com/executablebooks/MyST-Parser/pull/478)
+- 👌 IMPROVE: `note_refname` for docutils internal links in [#481](https://github.com/executablebooks/MyST-Parser/pull/481)
+- 🐛 FIX: Ordered list starting number in [#483](https://github.com/executablebooks/MyST-Parser/pull/483)
+- 👌 IMPROVE: Propagate enumerated list suffix in [#484](https://github.com/executablebooks/MyST-Parser/pull/484)
+- 👌 IMPROVE: `DocutilsRenderer.create_highlighted_code_block` in [#488](https://github.com/executablebooks/MyST-Parser/pull/488)
+- 🐛 FIX: Source line reporting for nested parsing in [#490](https://github.com/executablebooks/MyST-Parser/pull/490)
+- 🔧 MAINTAIN: Implement `MockInliner.parse` in [#504](https://github.com/executablebooks/MyST-Parser/pull/504)
+
+## 0.16.1 - 2021-12-16
+
+✨ NEW: Add `myst_linkify_fuzzy_links` option.
+When using the [`linkify` extension](docs/syntax/optional.md#linkify), this option can be used to disable matching of links that do not contain a schema (such as `http://`).
+
+## 0.16.0 - 2021-12-06
+
+This release contains a number of exciting improvements:
+
+### Upgrade of Markdown parser
+
+`markdown-it-py` has been upgraded to [v2.0.0](https://github.com/executablebooks/markdown-it-py/releases/tag/v2.0.0).
+This upgrade brings full compliance with the [CommonMark v0.30 specification](https://spec.commonmark.org/0.30/).
+
+Additionally, `mdit-py-plugins` has been upgraded to [v0.3.0](https://github.com/executablebooks/mdit-py-plugins/releases/tag/v0.3.0).
+This improves the parsing of the MyST target syntax, to allow for spaces and additional special characters in the target name,
+for example this is now valid:
+
+```md
+(a bc |@<>*./_-+:)=
+
+# Header
+```
+
+Also MyST role syntax now supports unlimited length in the role name and new lines in the content.
+For example, this is now valid:
+
+```md
+{abc}`xy
+new line`
+```
+
+### Improvements for Docutils-only use
+
+MyST now allows for Docutils-only use (outside of Sphinx), that allows for MyST configuration options to be set via the `docutils.conf` file, or on the command line.
+
+On installing MyST-Parser, the following CLI-commands are made available:
+
+- `myst-docutils-html`: converts MyST to HTML
+- `myst-docutils-html5`: converts MyST to HTML5
+- `myst-docutils-latex`: converts MyST to LaTeX
+- `myst-docutils-xml`: converts MyST to docutils-native XML
+- `myst-docutils-pseudoxml`: converts MyST to pseudo-XML (to visualise the AST structure)
+
+You can also install the [myst-docutils](https://pypi.org/project/myst-docutils/) package from `pip`,
+which includes no direct install requirements on docutils or sphinx.
+
+See [MyST with Docutils](docs/docutils.md) for more information.
+
+Thanks to help from [@cpitclaudel](https://github.com/cpitclaudel)!
+
+### Include MyST files in RST files
+
+With `docutils>=0.17`, the `include` directive has a `parser` option.
+This can be used with myst-parser to include MyST files in RST files.
+
+```md
+Parse using the docutils only parser:
+
+.. include:: include.md
+ :parser: myst_parser.docutils_
+
+Parse using the sphinx parser:
+
+.. include:: include.md
+ :parser: myst_parser.sphinx_
+```
+
+### Addition of the `fieldlist` syntax extension
+
+Field lists are mappings from field names to field bodies, based on the [reStructureText syntax](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists):
+
+```rst
+:name only:
+:name: body
+:name:
+ Multiple
+
+ Paragraphs
+```
+
+This should eventually allow for MyST Markdown docstrings! (see <https://github.com/executablebooks/MyST-Parser/issues/228>)
+
+See [Field Lists syntax](docs/syntax/optional.md#field-lists) for more information.
+
+### Improvements to table rendering
+
+Tables with no body are now allowed, for example:
+
+```md
+| abc | def |
+| --- | --- |
+```
+
+Also cell alignment HTML classes have now been changed to: `text-left`, `text-center`, or `text-right`, for example:
+
+```md
+| left | center | right |
+| :--- | :----: | ----: |
+| a | b | c |
+```
+
+is converted to:
+
+```html
+<table class="colwidths-auto">
+ <thead>
+ <tr>
+ <th class="text-left head"><p>left</p></th>
+ <th class="text-center head"><p>center</p></th>
+ <th class="text-right head"><p>right</p></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td class="text-left"><p>a</p></td>
+ <td class="text-center"><p>b</p></td>
+ <td class="text-right"><p>c</p></td>
+ </tr>
+ </tbody>
+</table>
+```
+
+These classes should be supported by most sphinx HTML themes.
+
+See [Tables syntax](docs/syntax/syntax.md#tables) for more information.
+
+### Pull Requests
+
+- 🐛 FIX: Add mandatory attributes on `enumerated_list` by @cpitclaudel in [#418](https://github.com/executablebooks/MyST-Parser/pull/418)
+- 📚 DOCS: Add reference to MySTyc in landing page by @astrojuanlu in [#413](https://github.com/executablebooks/MyST-Parser/pull/413)
+- ⬆️ UPGRADE: markdown-it-py v2, mdit-py-plugins v0.3 by @chrisjsewell in [#449](https://github.com/executablebooks/MyST-Parser/pull/449)
+- 👌 IMPROVE: Table rendering by @chrisjsewell in [#450](https://github.com/executablebooks/MyST-Parser/pull/450)
+- 🐛 FIX: Ensure parent files are re-built if `include` file changes by @chrisjsewell in [#451](https://github.com/executablebooks/MyST-Parser/pull/451)
+- 🐛 FIX: Convert empty directive option to `None` by @chrisjsewell in [#452](https://github.com/executablebooks/MyST-Parser/pull/452)
+- 👌 IMPROVE: Add `\\` for hard-breaks in latex by @chrisjsewell in [#453](https://github.com/executablebooks/MyST-Parser/pull/453)
+- 🔧 MAINTAIN: Remove empty "sphinx" extra by @hukkin in [#350](https://github.com/executablebooks/MyST-Parser/pull/350)
+- ✨ NEW: Add `fieldlist` extension by @chrisjsewell in [#455](https://github.com/executablebooks/MyST-Parser/pull/455)
+- ✨ NEW: Add Docutils MyST config and CLI by @cpitclaudel in [#426](https://github.com/executablebooks/MyST-Parser/pull/426)
+- 🔧 MAINTAIN: Add publishing job for `myst-docutils` by @chrisjsewell in [#456](https://github.com/executablebooks/MyST-Parser/pull/456)
+- 🧪 TESTS: Add for `gettext_additional_targets` by @jpmckinney in [#459](https://github.com/executablebooks/MyST-Parser/pull/459)
+
+### New Contributors
+
+- @cpitclaudel made their first contribution in [#418](https://github.com/executablebooks/MyST-Parser/pull/418)
+- @astrojuanlu made their first contribution in [#413](https://github.com/executablebooks/MyST-Parser/pull/413)
+
+**Full Changelog**: <https://github.com/executablebooks/MyST-Parser/compare/v0.15.2...v0.16.0>
+
+## 0.15.2 - 2021-08-26
+
+This is mainly a maintenance release that fixes some incompatibilities with `sphinx<3.1`, improvements for compatibility
+with `docutils=0.17`, and improvements to robustness.
+
+## 0.15.1 - 2021-06-18
+
+👌 IMPROVE: MathJax compatibility with `nbsphinx`
+
+`nbsphinx` also overrides the MathJax configuration.
+For compatibility, `output_area` is added to the list of default processed classes, and the override warning is allowed to be suppressed with `suppress_warnings = ["myst.mathjax"]`.
+
+## 0.15.0 - 2021-06-13
+
+### Upgraded to `sphinx` v4 ⬆️
+
+A principe change in this release is to updates the requirements of myst-parser from `sphinx>=2,<4` to `sphinx>=3,<5`.
+
+### Changed MathJax handling ♻️
+
+Instead of removing all `$` processing for the whole project,
+during MyST document parsing, the top-level section is now given the classes `tex2jax_ignore` and `mathjax_ignore` (turning off default MathJax processing of all HTML elements)
+and MathJax is then configured to process elements with the `tex2jax_process|mathjax_process|math` classes.
+
+See [the math syntax guide](docs/syntax/optional.md#math-shortcuts) for further information.
+
+### Set URL scheme defaults ‼️
+
+The `myst_url_schemes` default is now: `("http", "https", "mailto", "ftp")`.
+This means that only these URL will be considered as external (e.g. `[](https://example.com)`),
+and references like `[](prefix:main)` will be considered as internal references.
+Set `myst_url_schemes = None`, to revert to the previous default.
+
+### Added `myst_heading_slug_func` option 👌
+
+Use this option to specify a custom function to auto-generate heading anchors (see [Auto-generated header anchors](docs/syntax/optional.md#auto-generated-header-anchors)).
+
+Thanks to [@jpmckinney](https://github.com/jpmckinney)!
+
+## 0.14.0 - 2021-05-04
+
+### Upgrade to `markdown-it-py` v1.0 ⬆️
+
+This release updates the code-base to fully support the [markdown-it-py](https://markdown-it-py.readthedocs.io) `v1.0.0` release.
+In particular for users, this update alters the parsing of tables to be consistent with the [Github Flavoured Markdown (GFM) specification](https://github.github.com/gfm/#tables-extension-).
+
+### New Features ✨
+
+- **Task lists** utilise the [markdown-it-py tasklists plugin](markdown_it:md/plugins), and are applied to Markdown list items starting with `[ ]` or `[x]`.
+
+ ```markdown
+ - [ ] An item that needs doing
+ - [x] An item that is complete
+ ```
+
+ Add "tasklist" to the `myst_enable_extensions` configuration to enable.
+
+ See [the optional syntax guide](docs/syntax/optional.md#task-lists) for further information.
+
+- The **`sub-ref`** role has been added for use identical to ReST's `|name|` syntax.
+
+ This allows one to access Sphinx's built-in `|today|`, `|release|` and `|version|` substitutions, and also introduces two new substitutions: `wordcount-words` and `wordcount-minutes`, computed by the markdown-it-py [`wordcount_plugin`](https://github.com/executablebooks/mdit-py-plugins/pull/20).
+
+ ```markdown
+ > {sub-ref}`today` | {sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
+ ```
+
+ See [the roles syntax guide](docs/syntax/syntax.md) for further information.
+
+- The **`dmath_double_inline`** configuration option allows display math (i.e. `$$`) within an inline context.
+ See [the math syntax guide](docs/syntax/optional.md#math-shortcuts) for further information.
+
+### Remove v0.13 deprecations ‼️
+
+The deprecations made to extension configurations and colon fences in `0.13.0` (see below) have now been removed:
+
+- Configuration variables: `myst_admonition_enable`, `myst_figure_enable`, `myst_dmath_enable`, `myst_amsmath_enable`, `myst_deflist_enable`, `myst_html_img_enable`
+- `:::{admonition,class}` -> `:::{admonition}\n:class: class`
+- `:::{figure}` -> `:::{figure-md}`
+
+### Fix extraction of nested footnotes 🐛
+
+Previously footnote definitions in block elements like lists would crash the parsing:
+
+```markdown
+- [^e]: footnote definition in a block element
+```
+
+These are now correctly extracted.
+
+## 0.13.7 - 2021-04-25
+
+👌 IMPROVE: Add warning for nested headers:
+
+Nested headers are not supported within most elements (this is a limitation of the docutils/sphinx document structure), and can lead to unexpected outcomes.
+For example in admonitions:
+
+````markdown
+```{note}
+# Unsupported Header
+```
+````
+
+A warning (of type `myst.nested_header`) is now emitted when this occurs.
+
+🔧 MAINTAIN: Python 3.9 is now officially supported.
+
+## 0.13.6 - 2021-04-10
+
+🐛 FIX: docutils `v0.17` compatibility
+
+## 0.13.5 - 2021-02-15
+
+- ⬆️ UPGRADE: required markdown-it-py to `v0.6.2`:
+ In particular, this fixes missing source line mappings for table rows and their children
+- 👌 IMPROVE: Store `rawtext` in AST nodes:
+ We now ensure that the raw text is propagated from the Markdown tokens to the Sphinx AST.
+ In particular, this is required by the `gettext` builder, to generate translation POT templates.
+ Thanks to [@jpmckinney](https://github.com/jpmckinney)!
+- ✨ NEW: Add warning types `myst.subtype`:
+ All parsing warnings are assigned a type/subtype, and also the messages are appended with them.
+ These warning types can be suppressed with the sphinx `suppress_warnings` config option.
+ See [How-to suppress warnings](howto/warnings) for more information.
+
+## 0.13.3 - 2021-01-20
+
+Minor fixes:
+
+- 🐛 FIX: front-matter parsing for bibliographic keys
+- 🐛 FIX: directive/role name translations
+- 👌 IMPROVE: Add warning for multiple footnote definitions
+
+## 0.13.2 - 2021-01-20
+
+✨ NEW: Add `html_admonition` extension
+
+: By adding `"html_admonition"` to `myst_enable_extensions`, you can enable parsing of `<div class="admonition">` HTML blocks to sphinx admonitions.
+: This is helpful when you care about viewing the "source" Markdown, such as in Jupyter Notebooks.
+: For example:
+ ```html
+ <div class="admonition note" name="html-admonition">
+ <p class="title">This is the **title**</p>
+ This is the *content*
+ </div>
+ ```
+: See [the optional syntax guide](docs/syntax/optional.md) for further information.
+
+👌 IMPROVE: Footnotes
+
+: If the label is an integer, then it will always use this integer for the rendered label (i.e. they are manually numbered).
+: Add `myst_footnote_transition` configuration, to turn on/off transition line.
+: Add `footnotes` class to transition `<hr>` in HTML.
+: See [the syntax guide](docs/syntax/syntax.md) for further information.
+
+👌 IMPROVE: `substitution` extension logic
+
+: Parse inline substitutions without block rules, unless the substitution starts with a directive.
+
+🐛 FIX: Render front-matter as `field_list`
+
+: To improve use by sphinx extensions).
+
+👌 IMPROVE: Code quality
+
+: Add isort and mypy type checking to code base.
+
+(thanks to contributors @akhmerov, @tfiers)
+
+## 0.13.1 - 2020-12-31
+
+👌 Directives can now be used for inline substitutions, e.g.
+
+```md
+---
+substitutions:
+ key: |
+ ```{image} img/fun-fish.png
+ :alt: fishy
+ :height: 20px
+ ```
+---
+
+An inline image: {{ key }}
+```
+
+## 0.13.0 - 2020-12-18
+
+This release makes some major updates to the optional syntaxes.
+For full details see [Optional MyST Syntaxes](docs/syntax/optional.md).
+
+### 🗑 Deprecations
+
+`myst_enable_extensions = ["dollarmath", ...]` now replaces and deprecates individual enable configuration variables: `admonition_enable` -> `"colon_fence"`, `figure_enable` -> `"colon_fence"`, `dmath_enable` -> `"dollarmath"`, `amsmath` -> `"colon_fence"`, `deflist_enable` -> `"deflist"`, `html_img_enable` -> `"html_image"`.
+
+The `colon_fence` extension (replacing `admonition_enable`) now works exactly the same as normal ```` ``` ```` code fences, but using `:::` delimiters. This is helpful for directives that contain Markdown text, for example:
+
+```md
+:::{admonition} The title
+:class: note
+
+This note contains *Markdown*
+:::
+```
+
+### ✨ New
+
+The `smartquotes` extension will automatically convert standard quotations to their opening/closing variants:
+
+- `'single quotes'`: ‘single quotes’
+- `"double quotes"`: “double quotes”
+
+The `linkify` extension will automatically identify “bare” web URLs, like `www.example.com`, and add hyperlinks; www.example.com.
+This extension requires that [linkify-it-py](https://github.com/tsutsu3/linkify-it-py) is installed.
+
+The `replacements` extension will automatically convert some common typographic texts, such as `+-` -> `±`.
+
+The `substitution` extension allows you to specify "substitution definitions" in either the `conf.py` (as `myst_substitutions`) and/or individual file's front-matter (front-matter takes precedence), which will then replace substitution references. For example:
+
+```md
+---
+substitutions:
+ key1: definition
+---
+{{ key1 }}
+```
+
+The substitutions are assessed as [jinja2 expressions](http://jinja.palletsprojects.com/) and includes the [Sphinx Environment](https://www.sphinx-doc.org/en/master/extdev/envapi.html) as `env`, so you can do powerful thinks like:
+
+```
+{{ [key1, env.docname] | join('/') }}
+```
+
+The `figure-md` directive has been added (replacing `enable_figure`), which parses a "Markdown friendly" figure (used with the `colon_fence` extension):
+
+```md
+:::{figure-md} fig-target
+:class: myclass
+
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+
+This is a caption in **Markdown**
+:::
+```
+
+### 👌 Improvements
+
+Using the `html_image` extension, HTML images are now processed for both blocks and (now) inline.
+
+So you can correctly do, for example:
+
+```md
+I’m an inline image: <img src="img/fun-fish.png" height="20px">
+
+| table column |
+| ----------------------------------------- |
+| <img src="img/fun-fish.png" width="20px"> |
+```
+
+## 0.12.10 - 2020-09-21
+
+🐛 FIX: allow dates to be parsed in frontmatter.
+: This fixes a bug that would raise errors at parse time if non-string date objects were in front-matter YAML. See [#253](https://github.com/executablebooks/MyST-Parser/pull/253)
+
+## 0.12.9 - 2020-09-08
+
+✨ NEW: Auto-generate heading anchors.
+: This utilises `markdown-it-py`'s `anchors-plugin`, to generate unique anchor "slugs" for each header (up to a certain level),
+ and allows them to be referenced *via* a relative path, e.g. `[](./file.md#header-anchor)`, or in the same document, e.g. `[](#header-anchor)`.
+
+ Slugs are generated in the GitHub style ([see here](https://github.com/Flet/github-slugger)); lower-case text, removing punctuation, replacing spaces with `-`, enforce uniqueness *via* suffix enumeration `-1`.
+
+ It is enabled in your `conf.py` *via* `myst_heading_anchors = 2` (sets maximum heading level).
+
+ See [the documentation here](docs/syntax/optional.md#auto-generated-header-anchors).
+
+🐛 FIX: doc reference resolution for singlehtml/latex.
+: These reference resolutions are passed to the "missing-reference" event, and require the `node["refdoc"]` attribute to be available, which was missing for `[text](./path/to/file.md)` type references.
+
+## 0.12.7 - 2020-08-31
+
+✨ NEW: Want to include your README.md in the documentation?
+: See [including a file from outside the docs folder](howto/include-readme).
+
+(👌 added `relative-docs` option in 0.12.8)
+
+## 0.12.5 - 2020-08-28
+
+✨ NEW: Add Markdown figure syntax
+: Setting `myst_figure_enable = True` in your sphinx `conf.py`, combines the above two extended syntaxes,
+ to create a fully Markdown compliant version of the `figure` directive.
+ See [Markdown Figures](docs/syntax/optional.md#markdown-figures) for details.
+
+(👌 formatting of caption improved in 0.12.6)
+
+## 0.12.4 - 2020-08-27
+
+👌 IMPROVE: the mathjax extension is now only overridden if strictly necessary (to support dollar and ams math), and the override is more precise, to mitigate any unwanted side-effects
+
+## 0.12.3 - 2020-08-26
+
+✨ NEW: Add definition lists.
+: This addition, enabled by `myst_deflist_enable = True`, allows for "Pandoc style" definition lists to be parsed and rendered, e.g.
+
+```md
+Term 1
+: Definition
+```
+
+See the [Definition Lists documentation](https://myst-parser.readthedocs.io/en/latest/syntax/optional.html#definition-lists) for further details.
+
+👌 IMPROVE: mathjax_config override.
+: Only `mathjax_config["tex2jax"]` will now be overridden, in order to not interfere with other user configurations, such as adding TeX macros.
+ The configuration name has also changed from `myst_override_mathjax` to `myst_update_mathjax`.
+ See [Mathjax and math parsing](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#mathjax-and-math-parsing) for further details.
+
+## 0.12.2 - 2020-08-25
+
+✨ NEW: Add the `eval-rst` directive
+
+: This directive parses its contents as ReStructuredText, which integrates back into the rest of the document, e.g. for cross-referencing. See [this documentation](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#how-directives-parse-content) for further explanation.
+
+ In particular, this addition solves some outstanding user requests:
+
+ - How-to [include rST files into a Markdown file](https://myst-parser.readthedocs.io/en/latest/using/howto.html#include-rst-files-into-a-markdown-file)
+ - How-to [Use sphinx.ext.autodoc in Markdown files](https://myst-parser.readthedocs.io/en/latest/using/howto.html#use-sphinx-ext-autodoc-in-markdown-files)
+
+ Thanks to [@stephenroller](https://github.com/stephenroller) for the contribution 🎉
+
+## 0.12.1 - 2020-08-19
+
+✨ NEW: Add `myst_commonmark_only` config option, for restricting the parser to strict CommonMark (no extensions).
+
+## 0.12.0 - 2020-08-19
+
+### ‼️ BREAKING
+
+If you are using math in your documents, be sure to read the updated [Math syntax guide](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#math-shortcuts)!
+In particular, the Mathjax configuration is now overridden, such that LaTeX environments will only be rendered if `myst_amsmath_enable=True` is set.
+
+The `myst_math_delimiters` option has also been removed (please open an issue if you would like brackets math parsing to be re-implemented).
+
+In addition the `myst_html_img` option name has been changed to `myst_html_img_enable`.
+
+Some underlying code has also been refactored, to centralise handling of configuration options (see [commit 98573b9](https://github.com/executablebooks/MyST-Parser/commit/98573b9c6e3602ab31d627b5266ae5c1ba2c9e5f)).
+
+### Improved 👌
+
+More configuration options for math parsing (see [MyST configuration options](https://myst-parser.readthedocs.io/en/latest/using/intro.html#myst-configuration-options)).
+
+## 0.11.2 - 2020-07-13
+
+### Added ✨
+
+- `<img src="file.png" width="200px">` tag parsing to sphinx representation, see [the image syntax guide](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#images)
+
+### Improved 👌
+
+- `[title](link)` syntax now works with intersphinx references.
+ Recognised URI schemas can also be configured, see the [configuration options](https://myst-parser.readthedocs.io/en/latest/using/intro.html#myst-configuration-options)
+
+## 0.11.1 - 2020-07-12
+
+### Fix
+
+- Correctly pin required minimum markdown-it-py version
+
+## 0.11.0 - 2020-07-12
+
+### Added ✨
+
+* Special admonition directive syntax (optional):
+
+ ```md
+ :::{note}
+ This text is **standard** _Markdown_
+ :::
+ ```
+
+ See [the syntax guide section](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#admonition-directives-special-syntax-optional) for details.
+
+* Direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations (optional).
+ See [the syntax guide section](https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#direct-latex-math-optional) for details.
+
+### Breaking ‼️
+
+* Sphinx configuration options are now set as separate variables, rather than a single dict.
+ See [MyST configuration options](https://myst-parser.readthedocs.io/en/latest/using/intro.html#myst-configuration-options) for details.
+
+## 0.10.0 - 2020-07-08
+
+([full changelog](https://github.com/executablebooks/MyST-Parser/compare/v0.9.1...aaed58808af485c29bbbf73c5aac10697bfa08b9))
+
+### Improved 👌
+
+* Support Sphinx version 3 [#197](https://github.com/executablebooks/MyST-Parser/pull/197) ([@chrisjsewell](https://github.com/chrisjsewell))
+* Update Trove Classifiers [#192](https://github.com/executablebooks/MyST-Parser/pull/192) ([@chrisjsewell](https://github.com/chrisjsewell))
+* Add functionality to use docutils specialized role [#189](https://github.com/executablebooks/MyST-Parser/pull/189) ([@chrisjsewell](https://github.com/chrisjsewell))
+
+### Contributors to this release
+
+([GitHub contributors page for this release](https://github.com/executablebooks/MyST-Parser/graphs/contributors?from=2020-07-20&to=2020-08-07&type=c))
+
+[@AakashGfude](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3AAakashGfude+updated%3A2020-07-20..2020-08-07&type=Issues) | [@asmeurer](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aasmeurer+updated%3A2020-07-20..2020-08-07&type=Issues) | [@choldgraf](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acholdgraf+updated%3A2020-07-20..2020-08-07&type=Issues) | [@chrisjsewell](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Achrisjsewell+updated%3A2020-07-20..2020-08-07&type=Issues) | [@codecov](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acodecov+updated%3A2020-07-20..2020-08-07&type=Issues) | [@webknjaz](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Awebknjaz+updated%3A2020-07-20..2020-08-07&type=Issues) | [@welcome](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Awelcome+updated%3A2020-07-20..2020-08-07&type=Issues)
+
+## Past Releases
+
+### Contributors
+
+([GitHub contributors page for these releases](https://github.com/executablebooks/MyST-Parser/graphs/contributors?from=2020-01-01&to=2020-07-20&type=c))
+
+[@akhmerov](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aakhmerov+updated%3A2020-01-01..2020-07-20&type=Issues) | [@asmeurer](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aasmeurer+updated%3A2020-01-01..2020-07-20&type=Issues) | [@certik](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acertik+updated%3A2020-01-01..2020-07-20&type=Issues) | [@choldgraf](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acholdgraf+updated%3A2020-01-01..2020-07-20&type=Issues) | [@chrisjsewell](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Achrisjsewell+updated%3A2020-01-01..2020-07-20&type=Issues) | [@codecov](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Acodecov+updated%3A2020-01-01..2020-07-20&type=Issues) | [@dhermes](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Adhermes+updated%3A2020-01-01..2020-07-20&type=Issues) | [@filippo82](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Afilippo82+updated%3A2020-01-01..2020-07-20&type=Issues) | [@jlperla](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Ajlperla+updated%3A2020-01-01..2020-07-20&type=Issues) | [@jstac](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Ajstac+updated%3A2020-01-01..2020-07-20&type=Issues) | [@martinagvilas](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Amartinagvilas+updated%3A2020-01-01..2020-07-20&type=Issues) | [@mlncn](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Amlncn+updated%3A2020-01-01..2020-07-20&type=Issues) | [@mmcky](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Ammcky+updated%3A2020-01-01..2020-07-20&type=Issues) | [@moorepants](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Amoorepants+updated%3A2020-01-01..2020-07-20&type=Issues) | [@najuzilu](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Anajuzilu+updated%3A2020-01-01..2020-07-20&type=Issues) | [@nathancarter](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Anathancarter+updated%3A2020-01-01..2020-07-20&type=Issues) | [@pauleveritt](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Apauleveritt+updated%3A2020-01-01..2020-07-20&type=Issues) | [@phaustin](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Aphaustin+updated%3A2020-01-01..2020-07-20&type=Issues) | [@rossbar](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Arossbar+updated%3A2020-01-01..2020-07-20&type=Issues) | [@rowanc1](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Arowanc1+updated%3A2020-01-01..2020-07-20&type=Issues) | [@sbliven](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Asbliven+updated%3A2020-01-01..2020-07-20&type=Issues) | [@webknjaz](https://github.com/search?q=repo%3Aexecutablebooks%2FMyST-Parser+involves%3Awebknjaz+updated%3A2020-01-01..2020-07-20&type=Issues)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..582ddf5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 ExecutableBookProject
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..874bada
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+# MyST-Parser
+
+[![Github-CI][github-ci]][github-link]
+[![Coverage Status][codecov-badge]][codecov-link]
+[![Documentation Status][rtd-badge]][rtd-link]
+[![Code style: black][black-badge]][black-link]
+[![PyPI][pypi-badge]][pypi-link]
+[![Conda][conda-badge]][conda-link]
+[![PyPI - Downloads][install-badge]][install-link]
+
+
+**MyST is a rich and extensible flavor of Markdown meant for technical documentation and publishing**.
+
+MyST is a flavor of markdown that is designed for simplicity, flexibility, and extensibility.
+This repository serves as the reference implementation of MyST Markdown, as well as a collection of tools to support working with MyST in Python and Sphinx.
+It contains an extended [CommonMark](https://commonmark.org)-compliant parser using [`markdown-it-py`](https://markdown-it-py.readthedocs.io/), as well as a [Sphinx](https://www.sphinx-doc.org) extension that allows you to write MyST Markdown in Sphinx.
+
+[**See the MyST Parser documentation for more information**](https://myst-parser.readthedocs.io/en/latest/).
+
+## Installation
+
+To install the MyST parser, run the following in a
+[Conda environment](https://docs.conda.io) (recommended):
+
+```bash
+conda install -c conda-forge myst-parser
+```
+
+or
+
+```bash
+pip install myst-parser
+```
+
+Or for package development:
+
+```bash
+git clone https://github.com/executablebooks/MyST-Parser
+cd MyST-Parser
+git checkout master
+pip install -e .[code_style,testing,rtd]
+```
+
+To use the MyST parser in Sphinx, simply add: `extensions = ["myst_parser"]` to your `conf.py`.
+
+## Contributing
+
+We welcome all contributions!
+See the [Contributing Guide](https://myst-parser.readthedocs.io/en/latest/develop/index.html) for more details.
+
+[github-ci]: https://github.com/executablebooks/MyST-Parser/workflows/continuous-integration/badge.svg?branch=master
+[github-link]: https://github.com/executablebooks/MyST-Parser
+[codecov-badge]: https://codecov.io/gh/executablebooks/MyST-Parser/branch/master/graph/badge.svg
+[codecov-link]: https://codecov.io/gh/executablebooks/MyST-Parser
+[rtd-badge]: https://readthedocs.org/projects/myst-parser/badge/?version=latest
+[rtd-link]: https://myst-parser.readthedocs.io/en/latest/?badge=latest
+[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
+[pypi-badge]: https://img.shields.io/pypi/v/myst-parser.svg
+[pypi-link]: https://pypi.org/project/myst-parser
+[conda-badge]: https://anaconda.org/conda-forge/myst-parser/badges/version.svg
+[conda-link]: https://anaconda.org/conda-forge/myst-parser
+[black-link]: https://github.com/ambv/black
+[install-badge]: https://img.shields.io/pypi/dw/myst-parser?label=pypi%20installs
+[install-link]: https://pypistats.org/packages/myst-parser
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..c3a2dc7
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,10 @@
+coverage:
+ status:
+ project:
+ default:
+ target: 90%
+ threshold: 0.5%
+ patch:
+ default:
+ target: 75%
+ threshold: 0.5%
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..22f62c8
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+_build/
+_api/
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..914a4f5
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,27 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+# raise warnings to errors
+html-strict:
+ @$(SPHINXBUILD) -b html -nW --keep-going "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O)
+
+clean:
+ rm -r $(BUILDDIR)
diff --git a/docs/_static/custom.css b/docs/_static/custom.css
new file mode 100644
index 0000000..f126fba
--- /dev/null
+++ b/docs/_static/custom.css
@@ -0,0 +1,24 @@
+/** Add a counter before subsections **/
+h1 {
+ counter-reset: subsection;
+ text-decoration: underline;
+}
+h2 {
+ counter-reset: subsubsection;
+}
+h2::before {
+ counter-increment: subsection;
+ content: counter(subsection) ". ";
+}
+h3::before {
+ counter-increment: subsubsection;
+ content: counter(subsection) "." counter(subsubsection) ". ";
+}
+
+/** No icon for admonitions with no-icon class */
+.admonition > .admonition-title, div.admonition.no-icon > .admonition-title::before {
+ content: "";
+}
+.admonition > .admonition-title, div.admonition.no-icon > .admonition-title {
+ padding-left: .6rem;
+}
diff --git a/docs/_static/logo-square.svg b/docs/_static/logo-square.svg
new file mode 100644
index 0000000..a283cba
--- /dev/null
+++ b/docs/_static/logo-square.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 81.78 73.9"><defs><style>.cls-1{fill:#616161;}.cls-2{fill:#f37726;}</style></defs><title>Artboard 4</title><path class="cls-1" d="M14.66,41.7V38.09l4.67-.79V4.44l-4.67-.8V0h12.2L40.3,31.66h.17L53.47,0H66.05V3.64l-4.67.8V37.3l4.67.79V41.7h-15V38.09L56,37.3V7.7l-.15,0L42.3,40.22H38.55l-14-32.49-.14,0,.18,17.43V37.3l5.05.79V41.7Z"/><path class="cls-2" d="M37.88,73.9q0-8.82-10.13-8.82H19.33q-7.83,0-12.53-3.79T0,50.25l4.8-1.63q2.7,8.46,13.78,8.75h9.17q9.63,0,13.13,7.53Q44.3,57.37,54,57.37h8.45q11.76,0,14.55-8.72l4.78,1.6Q79.71,57.61,75,61.33T62.63,65.08H53.88q-10,0-10,8.82"/></svg>
diff --git a/docs/_static/logo-wide.svg b/docs/_static/logo-wide.svg
new file mode 100644
index 0000000..4dbdfef
--- /dev/null
+++ b/docs/_static/logo-wide.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 215.62 110"><defs><style>.cls-1{fill:#616161;}.cls-2{fill:#f37726;}</style></defs><title>Artboard 1</title><path class="cls-1" d="M27,63.43V59.74l4.78-.83V25.32L27,24.49V20.77H39.47L53.2,53.15h.19l13.3-32.38H79.55v3.72l-4.79.83V58.91l4.79.83v3.69H64.22V59.74l5-.83V28.65l-.14,0L55.26,61.9H51.42L37.09,28.68l-.14,0,.21,17.84V58.91l5.15.83v3.69ZM91.35,76.22a13.26,13.26,0,0,1-1.75-.15c-.72-.11-1.28-.21-1.67-.31l.59-4.54a12.21,12.21,0,0,0,1.36.1c.59,0,1,0,1.22,0a3.69,3.69,0,0,0,3-1.4,15.13,15.13,0,0,0,2.15-3.83l1.38-3.34L86.41,35.91,83,35.44V31.72H96.3v3.72l-3.83.6,6.17,15.84,1.5,4.14h.18l7.32-20-4-.6V31.72h13v3.72l-3.28.47L100.88,68.29a19,19,0,0,1-2.13,4.09,9.13,9.13,0,0,1-3,2.81A8.6,8.6,0,0,1,91.35,76.22ZM135,64a24.79,24.79,0,0,1-7.5-1.14,24.21,24.21,0,0,1-6.87-3.52V50.27h4.48l1,6.7a17,17,0,0,0,4.13,1.83,17.26,17.26,0,0,0,4.81.64,12.24,12.24,0,0,0,5-.89,7.05,7.05,0,0,0,3.11-2.47,6.84,6.84,0,0,0,.16-7.15,7.79,7.79,0,0,0-3.1-2.63,24.72,24.72,0,0,0-5.87-2,27.61,27.61,0,0,1-7.42-2.74,12.83,12.83,0,0,1-4.61-4.15,10.17,10.17,0,0,1-1.57-5.61,10.43,10.43,0,0,1,1.77-6,12.13,12.13,0,0,1,4.94-4.16,16.82,16.82,0,0,1,7.39-1.53,21.34,21.34,0,0,1,7.92,1.36,19.44,19.44,0,0,1,5.67,3.3v8.51h-4.48l-1-6.09a11.47,11.47,0,0,0-3.29-1.75,14.48,14.48,0,0,0-4.86-.7,10.17,10.17,0,0,0-4.33.86,7,7,0,0,0-2.94,2.42,6.34,6.34,0,0,0-1,3.65,5.57,5.57,0,0,0,.95,3.24,7.85,7.85,0,0,0,3.09,2.42,31.77,31.77,0,0,0,5.77,1.94Q143,41,146.44,44.26a10.61,10.61,0,0,1,3.49,8.06,10.35,10.35,0,0,1-1.86,6.09,12.28,12.28,0,0,1-5.21,4.14A19.32,19.32,0,0,1,135,64Zm30.47-.61V59.74l4.79-.83V25.13h-9.68l-.84,5.92h-4.34V20.77h35.45V31.05h-4.28l-.88-5.92H176V58.91l4.78.83v3.69Z"/><path class="cls-2" d="M24,83q-8.25-2.33-11.75-7.4A21.21,21.21,0,0,1,8.8,63.3V54.63q0-4.74-2.16-7.38T0,44.61V38.38q4.49,0,6.64-2.6T8.8,28.39v-8.7a21.14,21.14,0,0,1,3.48-12.3Q15.77,2.34,24,0l1.69,5a10.7,10.7,0,0,0-6.63,5.32,20.45,20.45,0,0,0-2,9.42v8.7a16.58,16.58,0,0,1-1.79,7.84A12.45,12.45,0,0,1,10,41.52a12.5,12.5,0,0,1,5.36,5.34,16.49,16.49,0,0,1,1.79,7.77V63.3a20.15,20.15,0,0,0,2,9.37A10.85,10.85,0,0,0,25.72,78Z"/><path class="cls-2" d="M192,82.46l-1.67-4.93a10.72,10.72,0,0,0,6.45-5.23,19.44,19.44,0,0,0,2-9.22V54.53a15.61,15.61,0,0,1,1.88-7.78,12.22,12.22,0,0,1,5.78-5.14,11.72,11.72,0,0,1-5.78-5,15.83,15.83,0,0,1-1.88-7.87V20.13a19.74,19.74,0,0,0-2-9.27,10.52,10.52,0,0,0-6.45-5.23L192,.75Q200,3,203.48,8a20.81,20.81,0,0,1,3.43,12.11v8.58q0,4.7,2.13,7.26t6.58,2.56v6.13q-4.45,0-6.58,2.61t-2.13,7.26v8.55a20.8,20.8,0,0,1-3.43,12.08Q200,80.16,192,82.46Z"/><path class="cls-1" d="M12.46,105.84l-4.19-11H8.19c.08.88.12,1.91.12,3.1v7.87H7V93.47H9.14l3.92,10.2h.08l3.94-10.2h2.16v12.37H17.8v-8c0-.91,0-1.91.11-3h-.06l-4.24,11Zm15.66,0-.29-1.31h-.06a3.92,3.92,0,0,1-1.39,1.17,4.13,4.13,0,0,1-1.72.32,3.1,3.1,0,0,1-2.15-.71,2.62,2.62,0,0,1-.79-2c0-1.87,1.5-2.86,4.49-3l1.58,0V99.7a2.31,2.31,0,0,0-.47-1.59,1.91,1.91,0,0,0-1.5-.53,6.2,6.2,0,0,0-2.63.7l-.42-1.06a6.86,6.86,0,0,1,3.13-.8,3.57,3.57,0,0,1,2.47.74,3.07,3.07,0,0,1,.79,2.36v6.32Zm-3.18-1a2.87,2.87,0,0,0,2.07-.72,2.66,2.66,0,0,0,.75-2v-.84l-1.41.06a5.25,5.25,0,0,0-2.42.53,1.57,1.57,0,0,0-.74,1.43,1.47,1.47,0,0,0,.46,1.17A2,2,0,0,0,24.94,104.86Zm11.34-8.45a6.08,6.08,0,0,1,1.11.09l-.18,1.31a4.66,4.66,0,0,0-1-.14,2.52,2.52,0,0,0-1.94.92,3.35,3.35,0,0,0-.79,2.28v5H32.05V96.58h1.16l.17,1.7h.06a4.1,4.1,0,0,1,1.23-1.39A2.89,2.89,0,0,1,36.28,96.41Zm4.23,4.68a14.94,14.94,0,0,1,1.11-1.34l3-3.17h1.66l-3.75,3.94,4,5.32h-1.7l-3.26-4.37-1.07.9v3.47H39.12V92.67h1.39v7c0,.31,0,.79-.06,1.43ZM52.14,106a4.29,4.29,0,0,1-3.25-1.25,4.89,4.89,0,0,1-1.19-3.49,5.33,5.33,0,0,1,1.11-3.55,3.68,3.68,0,0,1,3-1.32,3.5,3.5,0,0,1,2.75,1.14,4.37,4.37,0,0,1,1,3v.89H49.16a3.7,3.7,0,0,0,.82,2.47,2.87,2.87,0,0,0,2.21.84,7.64,7.64,0,0,0,3-.62v1.25a7.61,7.61,0,0,1-1.43.46A7.15,7.15,0,0,1,52.14,106Zm-.37-8.44a2.32,2.32,0,0,0-1.8.73,3.34,3.34,0,0,0-.77,2h4.85a3.05,3.05,0,0,0-.6-2A2.07,2.07,0,0,0,51.77,97.58Zm12.48,7h-.08a3.27,3.27,0,0,1-2.9,1.43,3.48,3.48,0,0,1-2.83-1.24,5.46,5.46,0,0,1-1-3.55,5.55,5.55,0,0,1,1-3.56,3.44,3.44,0,0,1,2.83-1.26,3.36,3.36,0,0,1,2.89,1.36h.11l0-.66,0-.66V92.67h1.41v13.17H64.44Zm-2.81.25a2.54,2.54,0,0,0,2.09-.78,4,4,0,0,0,.64-2.53v-.3a4.63,4.63,0,0,0-.65-2.81,2.51,2.51,0,0,0-2.1-.84,2.14,2.14,0,0,0-1.89,1,4.76,4.76,0,0,0-.65,2.7,4.64,4.64,0,0,0,.65,2.69A2.23,2.23,0,0,0,61.44,104.84Zm8.52,1h-1.4V92.67H70Zm1.51-9.26H73l2,5.28a18.81,18.81,0,0,1,.83,2.61h.08q.11-.42.45-1.47t2.3-6.42h1.52l-4,10.54a5.11,5.11,0,0,1-1.39,2.22,2.91,2.91,0,0,1-1.94.66,5.93,5.93,0,0,1-1.26-.14v-1.13a4,4,0,0,0,1,.11,2.13,2.13,0,0,0,2.06-1.62l.52-1.33Zm21.91,6a3.07,3.07,0,0,1-1.19,2.56A5.2,5.2,0,0,1,89,106a8,8,0,0,1-3.39-.57v-1.39a9,9,0,0,0,1.66.5,8.35,8.35,0,0,0,1.78.19,3.46,3.46,0,0,0,2.15-.55,1.78,1.78,0,0,0,.74-1.51,1.86,1.86,0,0,0-.27-1,2.19,2.19,0,0,0-.86-.77,13.3,13.3,0,0,0-1.82-.78,5.73,5.73,0,0,1-2.47-1.47,3.22,3.22,0,0,1-.74-2.2,2.75,2.75,0,0,1,1.07-2.28,4.46,4.46,0,0,1,2.84-.84,8.54,8.54,0,0,1,3.41.67l-.46,1.25a7.62,7.62,0,0,0-3-.64,2.77,2.77,0,0,0-1.78.5,1.59,1.59,0,0,0-.64,1.36,2.13,2.13,0,0,0,.23,1.06,2.25,2.25,0,0,0,.8.75A10.53,10.53,0,0,0,90,99a6.8,6.8,0,0,1,2.68,1.48A3,3,0,0,1,93.38,102.55Zm5.3,2.31a5.55,5.55,0,0,0,.72,0,5.43,5.43,0,0,0,.55-.12v1.08a2.37,2.37,0,0,1-.67.17,4.81,4.81,0,0,1-.8.08c-1.79,0-2.69-1-2.69-2.85V97.66H94.47V97l1.32-.57.6-2h.81v2.16h2.69v1.08H97.2v5.46a1.93,1.93,0,0,0,.39,1.29A1.4,1.4,0,0,0,98.68,104.86ZM106,96.41a6.23,6.23,0,0,1,1.11.09L107,97.81a4.66,4.66,0,0,0-1-.14,2.5,2.5,0,0,0-1.93.92,3.31,3.31,0,0,0-.8,2.28v5H101.8V96.58H103l.17,1.7h.06a4.12,4.12,0,0,1,1.24-1.39A2.87,2.87,0,0,1,106,96.41Zm4.16.17v6a2.44,2.44,0,0,0,.52,1.69,2.06,2.06,0,0,0,1.61.56,2.61,2.61,0,0,0,2.12-.79,4,4,0,0,0,.68-2.61V96.58h1.4v9.26h-1.15l-.21-1.25h-.08a2.79,2.79,0,0,1-1.18,1.07,4.09,4.09,0,0,1-1.75.36,3.55,3.55,0,0,1-2.53-.8,3.41,3.41,0,0,1-.85-2.58V96.58Zm13,9.44a4,4,0,0,1-3.11-1.24,5.09,5.09,0,0,1-1.1-3.51,5.25,5.25,0,0,1,1.12-3.6,4,4,0,0,1,3.19-1.26,6.1,6.1,0,0,1,1.34.14,4,4,0,0,1,1,.34l-.42,1.19a6.2,6.2,0,0,0-1-.31,4.12,4.12,0,0,0-1-.13q-2.84,0-2.83,3.61a4.28,4.28,0,0,0,.69,2.62,2.4,2.4,0,0,0,2,.91,6.24,6.24,0,0,0,2.38-.5v1.25A5.09,5.09,0,0,1,123.21,106Zm7.55-1.16a5.55,5.55,0,0,0,.72,0,5.43,5.43,0,0,0,.55-.12v1.08a2.36,2.36,0,0,1-.68.17,4.7,4.7,0,0,1-.79.08c-1.79,0-2.69-1-2.69-2.85V97.66h-1.33V97l1.33-.57.59-2h.82v2.16H132v1.08h-2.68v5.46a1.93,1.93,0,0,0,.39,1.29A1.39,1.39,0,0,0,130.76,104.86Zm4.45-8.28v6a2.44,2.44,0,0,0,.51,1.69,2.07,2.07,0,0,0,1.61.56,2.64,2.64,0,0,0,2.13-.79,4.09,4.09,0,0,0,.67-2.61V96.58h1.4v9.26h-1.15l-.21-1.25h-.07a2.81,2.81,0,0,1-1.19,1.07,4.05,4.05,0,0,1-1.75.36,3.55,3.55,0,0,1-2.53-.8,3.45,3.45,0,0,1-.85-2.58V96.58Zm13.54-.17a6.23,6.23,0,0,1,1.11.09l-.19,1.31a4.58,4.58,0,0,0-1-.14,2.5,2.5,0,0,0-1.93.92,3.31,3.31,0,0,0-.8,2.28v5h-1.41V96.58h1.16l.17,1.7h.06a4.12,4.12,0,0,1,1.24-1.39A2.87,2.87,0,0,1,148.75,96.41Zm6.76,9.61a4.27,4.27,0,0,1-3.25-1.25,4.89,4.89,0,0,1-1.19-3.49,5.33,5.33,0,0,1,1.11-3.55,3.68,3.68,0,0,1,3-1.32,3.5,3.5,0,0,1,2.75,1.14,4.37,4.37,0,0,1,1,3v.89h-6.39a3.75,3.75,0,0,0,.83,2.47,2.87,2.87,0,0,0,2.21.84,7.59,7.59,0,0,0,3-.62v1.25a7.53,7.53,0,0,1-1.42.46A7.28,7.28,0,0,1,155.51,106Zm-.38-8.44a2.29,2.29,0,0,0-1.79.73,3.27,3.27,0,0,0-.77,2h4.84a3.05,3.05,0,0,0-.59-2A2.08,2.08,0,0,0,155.13,97.58Zm12.49,7h-.08a3.27,3.27,0,0,1-2.9,1.43,3.48,3.48,0,0,1-2.83-1.24,5.46,5.46,0,0,1-1-3.55,5.55,5.55,0,0,1,1-3.56,3.44,3.44,0,0,1,2.83-1.26,3.36,3.36,0,0,1,2.89,1.36h.11l0-.66-.05-.66V92.67H169v13.17h-1.14Zm-2.81.25a2.55,2.55,0,0,0,2.09-.78,4,4,0,0,0,.64-2.53v-.3a4.7,4.7,0,0,0-.65-2.81,2.51,2.51,0,0,0-2.1-.84,2.14,2.14,0,0,0-1.89,1,4.69,4.69,0,0,0-.65,2.7,4.57,4.57,0,0,0,.65,2.69A2.21,2.21,0,0,0,164.81,104.84Zm15.65,1H179V94.75H175.1V93.47h9.27v1.28h-3.91Zm9.48.18a4.27,4.27,0,0,1-3.25-1.25,4.85,4.85,0,0,1-1.19-3.49,5.33,5.33,0,0,1,1.11-3.55,3.68,3.68,0,0,1,3-1.32,3.51,3.51,0,0,1,2.75,1.14,4.42,4.42,0,0,1,1,3v.89H187a3.7,3.7,0,0,0,.83,2.47,2.86,2.86,0,0,0,2.2.84,7.64,7.64,0,0,0,3-.62v1.25a7.53,7.53,0,0,1-1.42.46A7.28,7.28,0,0,1,189.94,106Zm-.38-8.44a2.32,2.32,0,0,0-1.8.73,3.33,3.33,0,0,0-.76,2h4.84a3.11,3.11,0,0,0-.59-2A2.09,2.09,0,0,0,189.56,97.58Zm8.41,3.51-3.22-4.51h1.6l2.45,3.54,2.44-3.54h1.57l-3.21,4.51,3.39,4.75h-1.6l-2.59-3.75-2.63,3.75h-1.59Zm9.84,3.77a5.55,5.55,0,0,0,.72,0,5.43,5.43,0,0,0,.55-.12v1.08a2.37,2.37,0,0,1-.67.17,4.81,4.81,0,0,1-.8.08c-1.79,0-2.69-1-2.69-2.85V97.66H203.6V97l1.32-.57.6-2h.81v2.16H209v1.08h-2.69v5.46a1.93,1.93,0,0,0,.39,1.29A1.4,1.4,0,0,0,207.81,104.86Z"/></svg>
diff --git a/docs/api/reference.rst b/docs/api/reference.rst
new file mode 100644
index 0000000..9ea5898
--- /dev/null
+++ b/docs/api/reference.rst
@@ -0,0 +1,96 @@
+.. _api/main:
+
+==========
+Python API
+==========
+
+Source text parsers
+-------------------
+
+.. _api/docutils_parser:
+
+Docutils
+........
+
+.. autoclass:: myst_parser.docutils_.Parser
+ :members: parse
+ :undoc-members:
+ :member-order: bysource
+ :show-inheritance:
+
+.. _api/sphinx_parser:
+
+Sphinx
+......
+
+.. autoclass:: myst_parser.parsers.sphinx_.MystParser
+ :members: supported, parse
+ :undoc-members:
+ :member-order: bysource
+ :show-inheritance:
+ :exclude-members: __init__
+
+.. _api/renderers:
+
+Markdown-it to docutils
+-----------------------
+
+These renderers take the markdown-it parsed token stream and convert it to
+the docutils AST. The sphinx renderer is a subclass of the docutils one,
+with some additional methods only available *via* sphinx e.g. multi-document cross-referencing.
+
+
+Docutils
+........
+
+.. autoclass:: myst_parser.mdit_to_docutils.base.DocutilsRenderer
+ :special-members: __output__, __init__
+ :members: render, nested_render_text, add_line_and_source_path, current_node_context
+ :undoc-members:
+ :member-order: bysource
+ :show-inheritance:
+
+
+Sphinx
+......
+
+.. autoclass:: myst_parser.mdit_to_docutils.sphinx_.SphinxRenderer
+ :special-members: __output__
+ :members: render_internal_link, render_math_block_label
+ :undoc-members:
+ :member-order: alphabetical
+ :show-inheritance:
+
+.. _api/directive:
+
+Directive and role processing
+-----------------------------
+
+This module processes the content of a directive:
+
+.. automodule:: myst_parser.parsers.directives
+ :members:
+
+These classes are parsed to sphinx roles and directives,
+to mimic the original docutls rST specific parser elements,
+but instead run nested parsing with the markdown parser.
+
+.. autoclass:: myst_parser.mocking.MockInliner
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autoclass:: myst_parser.mocking.MockState
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autoclass:: myst_parser.mocking.MockStateMachine
+ :members:
+ :undoc-members:
+ :show-inheritance:
+
+.. autoclass:: myst_parser.mocking.MockIncludeDirective
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..199a32a
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,169 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+from datetime import date
+
+from sphinx.application import Sphinx
+
+from myst_parser import __version__
+
+# -- Project information -----------------------------------------------------
+
+project = "MyST Parser"
+copyright = f"{date.today().year}, Executable Book Project"
+author = "Executable Book Project"
+version = __version__
+
+master_doc = "index"
+language = "en"
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "myst_parser",
+ "sphinx.ext.autodoc",
+ "sphinx.ext.intersphinx",
+ "sphinx.ext.viewcode",
+ "sphinx_design",
+ "sphinxext.rediraffe",
+ "sphinxcontrib.mermaid",
+ "sphinxext.opengraph",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_book_theme"
+html_logo = "_static/logo-wide.svg"
+html_favicon = "_static/logo-square.svg"
+html_title = ""
+html_theme_options = {
+ "home_page_in_toc": True,
+ "github_url": "https://github.com/executablebooks/MyST-Parser",
+ "repository_url": "https://github.com/executablebooks/MyST-Parser",
+ "repository_branch": "master",
+ "path_to_docs": "docs",
+ "use_repository_button": True,
+ "use_edit_page_button": True,
+}
+# OpenGraph metadata
+ogp_site_url = "https://myst-parser.readthedocs.io/en/latest"
+# This is the image that GitHub stores for our social media previews
+ogp_image = "https://repository-images.githubusercontent.com/240151150/316bc480-cc23-11eb-96fc-4ab2f981a65d" # noqa: E501
+ogp_custom_meta_tags = [
+ '<meta name="twitter:card" content="summary_large_image">',
+]
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
+
+myst_enable_extensions = [
+ "dollarmath",
+ "amsmath",
+ "deflist",
+ "fieldlist",
+ "html_admonition",
+ "html_image",
+ "colon_fence",
+ "smartquotes",
+ "replacements",
+ "linkify",
+ "strikethrough",
+ "substitution",
+ "tasklist",
+ "attrs_image",
+]
+myst_number_code_blocks = ["typescript"]
+myst_heading_anchors = 2
+myst_footnote_transition = True
+myst_dmath_double_inline = True
+
+rediraffe_redirects = {
+ "using/intro.md": "sphinx/intro.md",
+ "sphinx/intro.md": "intro.md",
+ "using/use_api.md": "api/index.md",
+ "api/index.md": "api/reference.rst",
+ "using/syntax.md": "syntax/syntax.md",
+ "using/syntax-optional.md": "syntax/optional.md",
+ "using/reference.md": "syntax/reference.md",
+ "sphinx/reference.md": "configuration.md",
+ "sphinx/index.md": "faq/index.md",
+ "sphinx/use.md": "faq/index.md",
+ "sphinx/faq.md": "faq/index.md",
+ "explain/index.md": "develop/background.md",
+}
+
+suppress_warnings = ["myst.strikethrough"]
+
+
+intersphinx_mapping = {
+ "python": ("https://docs.python.org/3.7", None),
+ "sphinx": ("https://www.sphinx-doc.org/en/master", None),
+ "markdown_it": ("https://markdown-it-py.readthedocs.io/en/latest", None),
+}
+
+# autodoc_default_options = {
+# "show-inheritance": True,
+# "special-members": "__init__, __enter__, __exit__",
+# "members": True,
+# # 'exclude-members': '',
+# "undoc-members": True,
+# # 'inherited-members': True
+# }
+autodoc_member_order = "bysource"
+nitpicky = True
+nitpick_ignore = [
+ ("py:class", "docutils.nodes.document"),
+ ("py:class", "docutils.nodes.docinfo"),
+ ("py:class", "docutils.nodes.Element"),
+ ("py:class", "docutils.nodes.Node"),
+ ("py:class", "docutils.nodes.field_list"),
+ ("py:class", "docutils.nodes.problematic"),
+ ("py:class", "docutils.nodes.pending"),
+ ("py:class", "docutils.nodes.system_message"),
+ ("py:class", "docutils.statemachine.StringList"),
+ ("py:class", "docutils.parsers.rst.directives.misc.Include"),
+ ("py:class", "docutils.parsers.rst.Parser"),
+ ("py:class", "docutils.utils.Reporter"),
+ ("py:class", "nodes.Element"),
+ ("py:class", "nodes.Node"),
+ ("py:class", "nodes.system_message"),
+ ("py:class", "Directive"),
+ ("py:class", "Include"),
+ ("py:class", "StringList"),
+ ("py:class", "DocutilsRenderer"),
+ ("py:class", "MockStateMachine"),
+]
+
+
+def setup(app: Sphinx):
+ """Add functions to the Sphinx setup."""
+ from myst_parser._docs import (
+ DirectiveDoc,
+ DocutilsCliHelpDirective,
+ MystConfigDirective,
+ )
+
+ app.add_css_file("custom.css")
+ app.add_directive("myst-config", MystConfigDirective)
+ app.add_directive("docutils-cli-help", DocutilsCliHelpDirective)
+ app.add_directive("doc-directive", DirectiveDoc)
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..a87ce0b
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,106 @@
+(sphinx/config-options)=
+# Configuration
+
+MyST parsing can be configured at both the global and individual document level,
+with the most specific configuration taking precedence.
+
+## Global configuration
+
+Overriding the default configuration at the global level is achieved by specifying variables in the Sphinx `conf.py` file.
+All `myst_parser` global configuration variables are prefixed with `myst_`, e.g.
+
+```python
+myst_enable_extensions = ["deflist"]
+```
+
+:::{seealso}
+Configuration in Docutils, in the [](docutils.md) section.
+:::
+
+```{myst-config}
+:sphinx:
+:scope: global
+```
+
+### Extensions
+
+Configuration specific to syntax extensions:
+
+```{myst-config}
+:sphinx:
+:extensions:
+:scope: global
+```
+
+## Local configuration
+
+```{versionadded} 0.18
+```
+
+The following configuration variables are available at the document level.
+These can be set in the document [front matter](syntax/frontmatter), under the `myst` key, e.g.
+
+```yaml
+---
+myst:
+ enable_extensions: ["deflist"]
+---
+```
+
+```{myst-config}
+:sphinx:
+:scope: local
+```
+
+### Extensions
+
+Configuration specific to syntax extensions:
+
+```{myst-config}
+:sphinx:
+:extensions:
+:scope: local
+```
+
+## List of syntax extensions
+
+Full details in the [](syntax/extensions) section.
+
+amsmath
+: enable direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations
+
+colon_fence
+: Enable code fences using `:::` delimiters, [see here](syntax/colon_fence) for details
+
+deflist
+: Enable definition lists, [see here](syntax/definition-lists) for details
+
+dollarmath
+: Enable parsing of dollar `$` and `$$` encapsulated math
+
+fieldlist
+: Enable field lists, [see here](syntax/fieldlists) for details
+
+html_admonition
+: Convert `<div class="admonition">` elements to sphinx admonition nodes, see the [HTML admonition syntax](syntax/html-admonition) for details
+
+html_image
+: Convert HTML `<img>` elements to sphinx image nodes, [see here](syntax/images) for details
+
+linkify
+: Automatically identify "bare" web URLs and add hyperlinks
+
+replacements
+: Automatically convert some common typographic texts
+
+smartquotes
+: Automatically convert standard quotations to their opening/closing variants
+
+strikethrough
+: Enable strikethrough syntax, [see here](syntax/strikethrough) for details
+
+substitution
+: Substitute keys, [see here](syntax/substitutions) for details
+
+tasklist
+: Add check-boxes to the start of list items, [see here](syntax/tasklists) for details
diff --git a/docs/develop/_changelog.md b/docs/develop/_changelog.md
new file mode 100644
index 0000000..d24e7ac
--- /dev/null
+++ b/docs/develop/_changelog.md
@@ -0,0 +1,4 @@
+```{include} ../../CHANGELOG.md
+:relative-docs: docs/
+:relative-images:
+```
diff --git a/docs/develop/architecture.md b/docs/develop/architecture.md
new file mode 100644
index 0000000..f00cdae
--- /dev/null
+++ b/docs/develop/architecture.md
@@ -0,0 +1,27 @@
+# The MyST implementation architecture
+
+This page describes implementation details to help you understand the structure
+of the project.
+
+## A Renderer for markdown-it tokens
+
+At a high level, the MyST parser is an extension of th project. Markdown-It-Py
+is a well-structured Python parser for CommonMark text. It also defines an extension
+point to include more syntax in parsed files. The MyST parser uses this extension
+point to define its own syntax options (e.g., for Sphinx roles and directives).
+
+The result of this parser is a markdown-it token stream.
+
+## A docutils renderer
+
+The MyST parser also defines a docutils renderer for the markdown-it token stream.
+This allows us to convert parsed elements of a MyST markdown file into docutils.
+
+## A Sphinx parser
+
+Finally, the MyST parser provides a parser for Sphinx, the documentation generation
+system. This parser does the following:
+
+* Parse markdown files with the markdown-it parser, including MyST specific plugins
+* Convert these files into docutils objects using the MyST docutils renderer
+* Provide these to Sphinx in order to use in building your site.
diff --git a/docs/develop/background.md b/docs/develop/background.md
new file mode 100644
index 0000000..4be19e0
--- /dev/null
+++ b/docs/develop/background.md
@@ -0,0 +1,82 @@
+# Background
+
+These sections discuss high-level questions about the MyST ecosystem, and explain a few decisions made in the project.
+
+## Why did we create MyST markdown?
+
+While markdown is ubiquitous, it is not powerful enough for writing modern,
+fully-featured documentation. Some flavors of markdown support features needed for this,
+but there is no community standard around various syntactic choices for these features.
+
+Sphinx is a documentation generation framework written in Python. It heavily-utilizes
+reStructuredText syntax, which is another markup language for writing documents. In
+particular, Sphinx defines two extension points that are extremely useful:
+**{ref}`in-line roles<sphinx:rst-roles-alt>`** and **{ref}`block-level directives <sphinx:rst-directives>`**.
+
+**This project is an attempt at combining the simplicity and readability of Markdown
+with the power and flexibility of reStructuredText and the Sphinx platform.** It
+starts with the [CommonMark markdown specification][commonmark], and selectively adds a few extra
+syntax pieces to utilize the most powerful parts of reStructuredText.
+
+```{note}
+The CommonMark community has been discussing an "official" extension syntax for many
+years now (for example, see
+[this seven-year-old thread about directives](https://talk.commonmark.org/t/generic-directives-plugins-syntax/444) as well as
+[this more recent converstaion](https://talk.commonmark.org/t/support-for-extension-token/2771),
+and [this comment listing several more threads on this topic](https://talk.commonmark.org/t/extension-terminology-and-rules/1233)).
+
+We have chosen a "roles and directives" syntax that seems reasonable and follows other
+common conventions in Markdown flavors. However, if the CommonMark community ever
+decides on an "official" extension syntax, we will likely utilize this syntax for
+MyST.
+```
+
+## The relationship between MyST, reStructuredText, and Sphinx
+
+MyST markdown provides a markdown equivalent of the reStructuredText syntax,
+meaning that you can do anything in MyST that you can do with reStructuredText.
+
+The Sphinx documentation engine supports a number of different input types. By default,
+Sphinx reads **reStructuredText** (`.rst`) files. Sphinx uses a **parser** to parse input files
+into its own internal document model (which is provided by a core Python project,
+[docutils](https://docutils.sourceforge.io/)).
+
+Developers can *extend Sphinx* to support other kinds of input files. Any content file
+can be read into the Sphinx document structure, provided that somebody writes a
+**parser** for that file. Once a content file has been parsed into Sphinx, it behaves
+nearly the same way as any other content file, regardless of the language in which it
+was written.
+
+The MyST-parser is a Sphinx parser for the MyST markdown language.
+When you use it, Sphinx will know how to parse content files that contain MyST markdown (by default, Sphinx will assume any files ending in `.md` are written in MyST markdown). Once a document has been parsed into Sphinx, it behaves the same way regardless of whether it has been written in rST or MyST markdown.
+
+```
+myst markdown (.md) ------> myst parser ---+
+ |
+ +-->Sphinx document (docutils)
+ |
+reStructuredText (.rst) --> rst parser ----+
+```
+
+For example, here's how you'd write a `toctree` directive in MyST markdown:
+
+````
+```{toctree}
+My page name <page1>
+page2
+```
+````
+
+and here's the same in rST:
+
+```
+.. toctree::
+
+ My page name <page1>
+ page2
+```
+
+They will both behave the same in Sphinx.
+
+
+[commonmark]: https://commonmark.org/
diff --git a/docs/develop/contributing.md b/docs/develop/contributing.md
new file mode 100644
index 0000000..7f57052
--- /dev/null
+++ b/docs/develop/contributing.md
@@ -0,0 +1,85 @@
+# Contributing
+
+[![Github-CI][github-ci]][github-link]
+[![Coverage Status][codecov-badge]][codecov-link]
+[![Documentation Status][rtd-badge]][rtd-link]
+[![Code style: black][black-badge]][black-link]
+
+We welcome all contributions!
+See the [EBP Contributing Guide](https://executablebooks.org/en/latest/contributing.html) for general details, and below for guidance specific to MyST-Parser.
+
+## Install for development
+
+To install `myst-parser` for development, take the following steps:
+
+```bash
+git clone https://github.com/executablebooks/MyST-Parser
+cd MyST-Parser
+git checkout master
+pip install -e .[code_style,testing,rtd]
+```
+
+## Code Style
+
+Code style is tested using [flake8](http://flake8.pycqa.org),
+with the configuration set in `.flake8`,
+and code formatted with [black](https://github.com/ambv/black).
+
+Installing with `myst-parser[code_style]` makes the [pre-commit](https://pre-commit.com/)
+package available, which will ensure this style is met before commits are submitted, by reformatting the code
+and testing for lint errors.
+It can be setup by:
+
+```shell
+>> cd MyST-Parser
+>> pre-commit install
+```
+
+Optionally you can run `black` and `flake8` separately:
+
+```shell
+>> black .
+>> flake8 .
+```
+
+Editors like VS Code also have automatic code reformat utilities, which can adhere to this standard.
+
+All functions and class methods should be annotated with types and include a docstring. The preferred docstring format is outlined in `MyST-Parser/docstring.fmt.mustache` and can be used automatically with the
+[autodocstring](https://marketplace.visualstudio.com/items?itemName=njpwerner.autodocstring) VS Code extension.
+
+## Testing
+
+For code tests, myst-parser uses [pytest](https://docs.pytest.org):
+
+```shell
+>> cd MyST-Parser
+>> pytest
+```
+
+You can also use [tox](https://tox.readthedocs.io), to run the tests in multiple isolated environments (see the `tox.ini` file for available test environments):
+
+```shell
+>> cd MyST-Parser
+>> tox
+```
+
+For documentation build tests:
+
+```shell
+>> cd MyST-Parser/docs
+>> make clean
+>> make html-strict
+```
+
+```{seealso}
+{ref}`develop/testing`
+```
+
+[github-ci]: https://github.com/executablebooks/MyST-Parser/workflows/continuous-integration/badge.svg?branch=master
+[github-link]: https://github.com/executablebooks/MyST-Parser
+[codecov-badge]: https://codecov.io/gh/executablebooks/MyST-Parser/branch/master/graph/badge.svg
+[codecov-link]: https://codecov.io/gh/executablebooks/MyST-Parser
+[rtd-badge]: https://readthedocs.org/projects/myst-parser/badge/?version=latest
+[rtd-link]: https://myst-parser.readthedocs.io/en/latest/?badge=latest
+[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
+[black-link]: https://github.com/ambv/black
diff --git a/docs/develop/index.md b/docs/develop/index.md
new file mode 100644
index 0000000..f3a3f97
--- /dev/null
+++ b/docs/develop/index.md
@@ -0,0 +1,15 @@
+# Contribute
+
+This section covers documentation relevant to developing and maintaining the MyST
+codebase, and some guidelines for how you can contribute.
+
+```{toctree}
+contributing.md
+architecture.md
+test_infrastructure.md
+```
+
+## Code of Conduct
+
+The MyST-parser project follows the
+[Executable Book Project code of conduct](https://github.com/executablebooks/.github/blob/master/CODE_OF_CONDUCT.md).
diff --git a/docs/develop/test_infrastructure.md b/docs/develop/test_infrastructure.md
new file mode 100644
index 0000000..ebef011
--- /dev/null
+++ b/docs/develop/test_infrastructure.md
@@ -0,0 +1,53 @@
+(develop/testing)=
+
+# Testing Infrastructure
+
+Where possible, additions to the code should be carried out in a
+[test-driven development](https://en.wikipedia.org/wiki/Test-driven_development)
+manner:
+
+> **Write failing tests that the code should pass, then write code to pass the tests**.
+
+The tests are run using [pytest](https://docs.pytest.org)/[GitHub Actions](https://github.com/features/actions) for unit tests, and [readthedocs](https://readthedocs.org/) for documentation build tests.
+
+The tests are ordered in a hierarchical fashion:
+
+1. In `tests/test_commonmark` the [CommonMark](https://github.com/commonmark/CommonMark.git) test set is run to check that the parser is complying with the CommonMark specification.
+2. In `tests/test_renderers` are tests that check that the Markdown AST is being correctly converted to the docutils/sphinx AST. This includes testing that roles and directives are correctly parsed and run.
+3. In `tests/test_sphinx` are tests that check that minimal sphinx project builds are running correctly, to convert MyST markdown files to HTML.
+4. In `.circleci` the package documentation (written in MyST format) is built and tested for build errors/warnings.
+
+## Test tools
+
+[**pytest-regressions**](https://pytest-regressions.readthedocs.io) is a pytest plugin
+that is used in the test suite, to maintain tests that generate lots of data.
+In particular, they are used in the syntax testing to generate tests for AST trees
+which may change in the future due to changes/additions to the data captured by the parser.
+For example, after writing:
+
+```python
+def test_example_dict(data_regression):
+ data_regression.check({
+ "key1": "value1",
+ "key2": "value2",
+ "more": "data...",
+ })
+def test_example_str(file_regression):
+ file_regression.check("a very long string...")
+```
+
+Running the following will initially fail,
+but will also generate a file (per test) of expected output:
+
+```console
+$ pytest -k test_example
+```
+
+Subsequent times the tests are run, the tests output will now be validated against these stored files.
+
+After a change to the syntax parser, all failing tests can then be 'regenerated' with the new
+expected output, by running:
+
+```console
+$ pytest --force-regen
+```
diff --git a/docs/docutils.md b/docs/docutils.md
new file mode 100644
index 0000000..b4d0480
--- /dev/null
+++ b/docs/docutils.md
@@ -0,0 +1,82 @@
+(myst-docutils)=
+
+# Single Page Builds
+
+```{versionadded} 0.16.0
+```
+
+Sphinx, and thus MyST-Parser, is built on top of the [Docutils](https://docutils.sourceforge.io/docs/) package.
+MyST-Parser offers a renderer, parser and CLI-interface for working directly with Docutils, independent of Sphinx, as described below.
+
+:::{note}
+Since these tools are independent of Sphinx, this means they cannot parse any Sphinx or Sphinx extensions specific roles or directives.
+:::
+
+On installing MyST-Parser, the following CLI-commands are made available:
+
+- `myst-docutils-html`: converts MyST to HTML
+- `myst-docutils-html5`: converts MyST to HTML5
+- `myst-docutils-latex`: converts MyST to LaTeX
+- `myst-docutils-xml`: converts MyST to docutils-native XML
+- `myst-docutils-pseudoxml`: converts MyST to pseudo-XML (to visualise the AST structure)
+
+Each command can be piped stdin or take a file path as an argument:
+
+```console
+$ myst-docutils-html --help
+$ echo "Hello World" | myst-docutils-html
+$ myst-docutils-html hello-world.md
+```
+
+The commands are based on the [Docutils Front-End Tools](https://docutils.sourceforge.io/docs/user/tools.html), and so follow the same argument and options structure, included many of the MyST specific options detailed in [](sphinx/config-options).
+
+:::{dropdown} Shared Docutils CLI Options
+```{docutils-cli-help}
+```
+:::
+
+The CLI commands can also utilise the [`docutils.conf` configuration file](https://docutils.sourceforge.io/docs/user/config.html) to configure the behaviour of the CLI commands. For example:
+
+```
+# These entries affect all processing:
+[general]
+myst-enable-extensions: deflist,linkify
+myst-footnote-transition: no
+
+# These entries affect specific HTML output:
+[html writers]
+embed-stylesheet: no
+
+[html5 writer]
+stylesheet-dirs: path/to/html5_polyglot/
+stylesheet-path: minimal.css, responsive.css
+```
+
+You can also use the {py:class}`myst_parser.docutils_.Parser` class programmatically with the [Docutils publisher API](https://docutils.sourceforge.io/docs/api/publisher.html):
+
+```python
+from docutils.core import publish_string
+from myst_parser.docutils_ import Parser
+
+source = "hallo world\n: Definition"
+output = publish_string(
+ source=source,
+ writer_name="html5",
+ settings_overrides={
+ "myst_enable_extensions": ["deflist"],
+ "embed_stylesheet": False,
+ },
+ parser=Parser(),
+)
+```
+
+Finally, you can include MyST Markdown files within a RestructuredText file, using the [`include` directive](https://docutils.sourceforge.io/docs/ref/rst/directives.html#include):
+
+```rst
+.. include:: include.md
+ :parser: myst_parser.docutils_
+```
+
+```{important}
+The `parser` option requires `docutils>=0.17`
+```
diff --git a/docs/faq/index.md b/docs/faq/index.md
new file mode 100644
index 0000000..e4d4581
--- /dev/null
+++ b/docs/faq/index.md
@@ -0,0 +1,267 @@
+(myst-sphinx)=
+
+# FAQ
+
+## How-tos
+
+These sections describe some common scenarios and use-cases for writing MyST with Sphinx.
+
+(howto/include-rst)=
+### Include rST files into a Markdown file
+
+As explained in [this section](syntax/directives/parsing), all MyST directives will parse their content as Markdown.
+Therefore, using the conventional `include` directive, will parse the file contents as Markdown:
+
+````md
+```{include} snippets/include-md.md
+```
+````
+
+```{include} snippets/include-md.md
+```
+
+To include rST, we must first "wrap" the directive in the [eval-rst directive](syntax/directives/parsing):
+
+````md
+```{eval-rst}
+.. include:: snippets/include-rst.rst
+```
+````
+
+```{eval-rst}
+.. include:: snippets/include-rst.rst
+```
+
+(howto/include-md)=
+### Include Markdown files into an rST file
+
+To include a MyST file within a ReStructuredText file, we can use the `parser` option of the `include` directive:
+
+```rst
+.. include:: include.md
+ :parser: myst_parser.sphinx_
+```
+
+```{important}
+The `parser` option requires `docutils>=0.17`
+```
+
+### Use MyST in Jupyter Notebooks
+
+The [MyST-NB](https://myst-nb.readthedocs.io) tool provides a Sphinx extension for parsing **Jupyter Notebooks written with MyST Markdown**. It includes features like automatically executing notebooks during documentation builds, storing notebook cell outputs in order to insert them elsewhere in your documentation, and more. See the [MyST-NB documentation](https://myst-nb.readthedocs.io) for more information.
+
+(howto/include-readme)=
+### Include a file from outside the docs folder (like README.md)
+
+You can include a file, including one from outside the project using e.g.:
+
+````md
+```{include} ../README.md
+```
+````
+
+**However**, including a file will not usually resolve local links correctly, like `![](my-image.png)`, since it treats the text as if it originated from the "including file".
+
+As of myst-parser version 0.12.7, a new, experimental feature has been added to resolve such links.
+You can now use for example:
+
+````md
+Source:
+```{literalinclude} ../../example.md
+:language: md
+```
+Included:
+```{include} ../../example.md
+:relative-docs: docs/
+:relative-images:
+```
+````
+
+Source:
+
+```{literalinclude} ../../example-include.md
+:language: md
+```
+
+Included:
+
+```{include} ../../example-include.md
+:relative-docs: docs/
+:relative-images:
+```
+
+The include here attempts to re-write local links, to reference them from the correct location!
+The `relative-docs` must be given the prefix of any links to re-write, to distinguish them from sphinx cross-references.
+
+:::{important}
+The current functionality only works for Markdown style images and links.
+
+If you encounter any issues with this feature, please don't hesitate to report it.
+:::
+
+(howto/autodoc)=
+### Use `sphinx.ext.autodoc` in Markdown files
+
+The [Sphinx extension `autodoc`](sphinx:sphinx.ext.autodoc), which pulls in code documentation from docstrings, is currently hard-coded to parse reStructuredText.
+It is therefore incompatible with MyST's Markdown parser.
+However, the special [`eval-rst` directive](syntax/directives/parsing) can be used to "wrap" `autodoc` directives:
+
+````md
+```{eval-rst}
+.. autoclass:: myst_parser.mocking.MockRSTParser
+ :show-inheritance:
+ :members: parse
+```
+````
+
+```{eval-rst}
+.. autoclass:: myst_parser.mocking.MockRSTParser
+ :show-inheritance:
+ :members: parse
+```
+
+As with other objects in MyST, this can then be referenced:
+
+- Using the role `` {py:class}`myst_parser.mocking.MockRSTParser` ``: {py:class}`myst_parser.mocking.MockRSTParser`
+- Using the Markdown syntax `[MockRSTParser](myst_parser.mocking.MockRSTParser)`: [MockRSTParser](myst_parser.mocking.MockRSTParser)
+
+```{warning}
+This expects docstrings to be written in reStructuredText.
+We hope to support Markdown in the future, see [GitHub issue #228](https://github.com/executablebooks/MyST-Parser/issues/228).
+```
+
+(howto/autosectionlabel)=
+### Automatically create targets for section headers
+
+:::{important}
+
+New in `v0.13.0` ✨, myst-parser now provides a separate implementation of `autosectionlabel`, which implements GitHub Markdown style bookmark anchors, like `[](file.md#header-anchor)`.
+
+See the [](syntax/header-anchors) section of extended syntaxes.
+
+:::
+
+If you'd like to *automatically* generate targets for each of your section headers,
+check out the [`autosectionlabel`](https://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html)
+sphinx feature. You can activate it in your Sphinx site by adding the following to your
+`conf.py` file:
+
+```python
+extensions = [
+ 'sphinx.ext.autosectionlabel',
+]
+
+# Prefix document path to section labels, to use:
+# `path/to/file:heading` instead of just `heading`
+autosectionlabel_prefix_document = True
+```
+
+So, if you have a page at `myfolder/mypage.md` (relative to your documentation root)
+with the following structure:
+
+```md
+# Title
+
+## My Subtitle
+```
+
+Then the `autosectionlabel` feature will allow you to reference the section headers
+like so:
+
+```md
+{ref}`path/to/file_1:My Subtitle`
+```
+
+(howto/warnings)=
+### Suppress warnings
+
+In general, if your build logs any warnings, you should either fix them or [raise an Issue](https://github.com/executablebooks/MyST-Parser/issues/new/choose) if you think the warning is erroneous.
+However, in some circumstances if you wish to suppress the warning you can use the [`suppress_warnings`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-suppress_warnings) configuration option.
+All myst-parser warnings are prepended by their type, e.g. to suppress:
+
+```md
+# Title
+### Subtitle
+```
+
+```
+WARNING: Non-consecutive header level increase; H1 to H3 [myst.header]
+```
+
+Add to your `conf.py`:
+
+```python
+suppress_warnings = ["myst.header"]
+```
+
+
+### Sphinx-specific page front matter
+
+Sphinx intercepts front matter and stores them within the global environment
+(as discussed [in the deflists documentation](https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html)).
+There are certain front-matter keys (or their translations) that are also recognised specifically by docutils and parsed to inline Markdown:
+
+- `author`
+- `authors`
+- `organization`
+- `address`
+- `contact`
+- `version`
+- `revision`
+- `status`
+- `date`
+- `copyright`
+- `dedication`
+- `abstract`
+
+A classic use-case is to specify 'orphan' documents, that are not specified in any toctrees.
+For example, inserting the following syntax at the top of a page will cause Sphinx to treat it as an orphan page:
+
+```md
+---
+orphan: true
+---
+
+This is an orphan document, not specified in any toctrees.
+```
+
+### Migrate pre-existing rST into MyST
+
+If you've already got some reStructuredText files that you'd like to convert into MyST Markdown, try the [`rst-to-myst`](https://github.com/executablebooks/rst-to-myst) tool, which allows you to convert single rST files to MyST markdown documents.
+
+## Disable Markdown syntax for the parser
+
+If you'd like to either enable or disable custom markdown syntax, use `myst_disable_syntax`.
+Anything in this list will no longer be parsed by the MyST parser.
+
+For example, to disable the `emphasis` in-line syntax, use this configuration:
+
+```python
+myst_disable_syntax = ["emphasis"]
+```
+
+emphasis syntax will now be disabled. For example, the following will be rendered
+*without* any italics:
+
+```md
+*emphasis is now disabled*
+```
+
+For a list of all the syntax elements you can disable, see the [markdown-it parser guide](markdown_it:using).
+
+## Common errors and questions
+
+These are common issues and gotchas that people may experience when using the MyST Sphinx extension.
+
+### What markup language should I use inside directives?
+
+If you need to parse content *inside* of another block of content (for example, the
+content inside a **note directive**), note that the MyST parser will be used for this
+nested parsing as well.
+
+### Why doesn't my role/directive recognize markdown link syntax?
+
+There are some roles/directives that _hard-code_ syntax into
+their behavior. For example, many roles allow you to supply titles for links like so:
+`` {role}`My title <myref>` ``. While this looks like reStructuredText, the role may
+be explicitly expecting the `My title <myref>` structure, and so MyST will behave the same way.
diff --git a/docs/faq/snippets/include-md.md b/docs/faq/snippets/include-md.md
new file mode 100644
index 0000000..296421a
--- /dev/null
+++ b/docs/faq/snippets/include-md.md
@@ -0,0 +1 @@
+Hallo I'm from a Markdown file, [with a reference](howto/autodoc).
diff --git a/docs/faq/snippets/include-rst.rst b/docs/faq/snippets/include-rst.rst
new file mode 100644
index 0000000..04ec485
--- /dev/null
+++ b/docs/faq/snippets/include-rst.rst
@@ -0,0 +1 @@
+Hallo I'm from an rST file, :ref:`with a reference <howto/autodoc>`.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..36c0cd0
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,160 @@
+---
+sd_hide_title: true
+---
+
+# Overview
+
+::::{grid}
+:reverse:
+:gutter: 3 4 4 4
+:margin: 1 2 1 2
+
+:::{grid-item}
+:columns: 12 4 4 4
+
+```{image} _static/logo-square.svg
+:width: 200px
+:class: sd-m-auto
+```
+
+:::
+
+:::{grid-item}
+:columns: 12 8 8 8
+:child-align: justify
+:class: sd-fs-5
+
+```{rubric} MyST - Markedly Structured Text - Parser
+```
+
+A Sphinx and Docutils extension to parse MyST,
+a rich and extensible flavour of Markdown for authoring technical and scientific documentation.
+
+```{button-ref} intro
+:ref-type: doc
+:color: primary
+:class: sd-rounded-pill
+
+Get Started
+```
+
+:::
+
+::::
+
+---
+
+::::{grid} 1 2 2 3
+:gutter: 1 1 1 2
+
+:::{grid-item-card} {octicon}`markdown;1.5em;sd-mr-1` CommonMark-plus
+:link: syntax/core
+:link-type: ref
+
+MyST extends the CommonMark syntax specification, to support technical authoring features such as tables and footnotes.
+
++++
+[Learn more »](syntax/core)
+:::
+
+:::{grid-item-card} {octicon}`plug;1.5em;sd-mr-1` Sphinx compatible
+:link: roles-directives
+:link-type: ref
+
+Use the MyST role and directive syntax to harness the full capability of Sphinx, such as admonitions and figures, and all existing Sphinx extensions.
+
++++
+[Learn more »](roles-directives)
+:::
+
+:::{grid-item-card} {octicon}`tools;1.5em;sd-mr-1` Highly configurable
+:link: configuration
+:link-type: doc
+
+MyST-parser can be configured at both the global and individual document level,
+to modify parsing behaviour and access extended syntax features.
+
++++
+[Learn more »](configuration)
+:::
+
+::::
+
+---
+
+```{rubric} Additional resources
+```
+
+[MyST-Markdown VS Code extension](https://marketplace.visualstudio.com/items?itemName=ExecutableBookProject.myst-highlight)
+: For MyST extended syntax highlighting and authoring tools.
+
+[Convert existing ReStructuredText files to Markdown][rst-to-myst]
+: Use the [rst-to-myst] CLI or [the MySTyc interactive web interface](https://astrojuanlu.github.io/mystyc/).
+
+[MyST-NB](https://myst-nb.readthedocs.io)
+: A Sphinx and Docutils extension for compiling Jupyter Notebooks into high quality documentation formats, built on top of the MyST-Parser.
+
+[Jupyter Book](https://jupyterbook.org)
+: An open source project for building beautiful, publication-quality books and documents from computational material, built on top of the MyST-Parser and MyST-NB.
+
+[The Jupyter Book gallery](https://executablebooks.org/en/latest/gallery.html)
+: Examples of documents built with MyST.
+
+[Javascript MyST parser][mystjs]
+: The [mystjs] Javascript parser, allows you to parse MyST in websites.
+
+[markdown-it-py]
+: A CommonMark-compliant and extensible Markdown parser, used by MyST-Parser to parse source text to tokens.
+
+```{rubric} Acknowledgements
+```
+
+The MyST markdown language and MyST parser are both supported by the open community,
+[The Executable Book Project](https://executablebooks.org).
+
+```{toctree}
+:hidden:
+intro.md
+```
+
+```{toctree}
+:hidden:
+:caption: Guides
+
+syntax/syntax
+syntax/optional
+syntax/roles-and-directives.md
+configuration.md
+docutils.md
+faq/index.md
+develop/index.md
+```
+
+```{toctree}
+:hidden:
+:caption: Reference
+
+develop/_changelog.md
+syntax/reference
+develop/background.md
+api/reference.rst
+```
+
+[commonmark]: https://commonmark.org/
+[github-ci]: https://github.com/executablebooks/MyST-Parser/workflows/continuous-integration/badge.svg?branch=master
+[github-link]: https://github.com/executablebooks/MyST-Parser
+[codecov-badge]: https://codecov.io/gh/executablebooks/MyST-Parser/branch/master/graph/badge.svg
+[codecov-link]: https://codecov.io/gh/executablebooks/MyST-Parser
+[rtd-badge]: https://readthedocs.org/projects/myst-parser/badge/?version=latest
+[rtd-link]: https://myst-parser.readthedocs.io/en/latest/?badge=latest
+[black-badge]: https://img.shields.io/badge/code%20style-black-000000.svg
+[pypi-badge]: https://img.shields.io/pypi/v/myst-parser.svg
+[pypi-link]: https://pypi.org/project/myst-parser
+[conda-badge]: https://anaconda.org/conda-forge/myst-parser/badges/version.svg
+[conda-link]: https://anaconda.org/conda-forge/myst-parser
+[black-link]: https://github.com/ambv/black
+[github-badge]: https://img.shields.io/github/stars/executablebooks/myst-parser?label=github
+[markdown-it-py]: https://markdown-it-py.readthedocs.io/
+[markdown-it]: https://markdown-it.github.io/
+[rst-to-myst]: https://rst-to-myst.readthedocs.io
+[mystjs]: https://github.com/executablebooks/mystjs
diff --git a/docs/intro.md b/docs/intro.md
new file mode 100644
index 0000000..dc65728
--- /dev/null
+++ b/docs/intro.md
@@ -0,0 +1,250 @@
+(intro/get-started)=
+# Get Started
+
+This page describes how to get started with the MyST parser, with a focus on enabling it in the Sphinx documentation engine.
+
+## Installation
+
+[![PyPI][pypi-badge]][pypi-link]
+[![Conda][conda-badge]][conda-link]
+
+To install use [pip](https://pip.pypa.io):
+
+```bash
+pip install myst-parser
+```
+
+or [Conda](https://docs.conda.io):
+
+```bash
+conda install -c conda-forge myst-parser
+```
+
+[pypi-badge]: https://img.shields.io/pypi/v/myst-parser.svg
+[pypi-link]: https://pypi.org/project/myst-parser
+[conda-badge]: https://anaconda.org/conda-forge/myst-parser/badges/version.svg
+[conda-link]: https://anaconda.org/conda-forge/myst-parser
+
+(intro/sphinx)=
+## Enable MyST in Sphinx
+
+To get started with Sphinx, see their [Quickstart Guide](https://www.sphinx-doc.org/en/master/usage/quickstart.html).
+
+To use the MyST parser in Sphinx, simply add the following to your `conf.py` file:
+
+```python
+extensions = ["myst_parser"]
+```
+
+This will activate the MyST Parser extension, causing all documents with the `.md` extension to be parsed as MyST.
+
+:::{tip}
+To parse single documents, see the [](docutils.md) section
+:::
+
+(intro/writing)=
+## Write a CommonMark document
+
+MyST is an extension of [CommonMark Markdown](https://commonmark.org/),
+that includes [additional syntax](../syntax/syntax.md) for technical authoring,
+which integrates with Docutils and Sphinx.
+
+To start off, create an empty file called `myfile.md` and give it a markdown title and text.
+
+```md
+# My nifty title
+
+Some **text**!
+```
+
+To parse to HTML, try the CLI:
+
+```html
+$ myst-docutils-html5 --stylesheet= myfile.md
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta charset="utf-8"/>
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
+<title>My nifty title</title>
+
+</head>
+<body>
+<main id="my-nifty-title">
+<h1 class="title">My nifty title</h1>
+
+<p>Some <strong>text</strong>!</p>
+</main>
+</body>
+</html>
+```
+
+To include this document within a Sphinx project,
+include `myfile.md` in a [`toctree` directive](sphinx:toctree-directive) on an index page.
+
+## Extend CommonMark with roles and directives
+
+MyST allows any Sphinx role or directive to be used in a document.
+These are extensions points allowing for richer features, such as admonitions and figures.
+
+For example, add an `admonition` directive and `sup` role to your Markdown page, like so:
+
+````md
+# My nifty title
+
+Some **text**!
+
+```{admonition} Here's my title
+:class: tip
+
+Here's my admonition content.{sup}`1`
+```
+````
+
+Then convert to HTML:
+
+```html
+$ myst-docutils-html5 --stylesheet= myfile.md
+...
+<div class="admonition tip">
+<p class="admonition-title">Here's my title</p>
+<p>Here's my admonition content.<sup>1</sup></p>
+</div>
+...
+```
+
+:::{seealso}
+The full [](syntax/roles-and-directives.md) section
+:::
+
+(intro/reference)=
+## Cross-referencing
+
+MyST-Parser offers powerful cross-referencing features, to link to documents, headers, figures and more.
+
+For example, to add a section *reference target*, and reference it:
+
+```md
+(header-label)=
+# A header
+
+[My reference](header-label)
+```
+
+```html
+$ myst-docutils-html5 --stylesheet= myfile.md
+...
+<span id="header-label"></span>
+<h1 class="title">A header</h1>
+
+<p><a class="reference internal" href="#header-label">My reference</a></p>
+...
+```
+
+:::{seealso}
+The [](syntax/referencing) section,\
+and the [ReadTheDocs cross-referencing](https://docs.readthedocs.io/en/stable/guides/cross-referencing-with-sphinx.html) documentation
+:::
+
+## Configuring MyST-Parser
+
+The [](configuration.md) section contains a complete list of configuration options for the MyST-Parser.
+
+These can be applied globally, e.g. in the sphinx `conf.py`:
+
+```python
+myst_enable_extensions = [
+ "colon_fence",
+]
+```
+
+Or they can be applied to specific documents, at the top of the document:
+
+```yaml
+---
+myst:
+ enable_extensions: ["colon_fence"]
+---
+```
+
+## Extending Sphinx
+
+The other way to extend MyST in Sphinx is to install Sphinx extensions that define new roles, directives, etc.
+
+For example, let's install the `sphinxcontrib.mermaid` extension,
+which will allow us to generate [Mermaid diagrams](https://mermaid-js.github.io/mermaid/#/) with MyST.
+
+First, install `sphinxcontrib.mermaid`:
+
+```shell
+pip install sphinxcontrib-mermaid
+```
+
+Next, add it to your list of extensions in `conf.py`:
+
+```python
+extensions = [
+ "myst_parser",
+ "sphinxcontrib.mermaid",
+]
+```
+
+Now, add a **mermaid directive** to your markdown file.
+For example:
+
+````md
+# My nifty title
+
+Some **text**!
+
+```{admonition} Here's my title
+:class: warning
+
+Here's my admonition content
+```
+
+(section-two)=
+## Here's another section
+
+And some more content.
+
+% This comment won't make it into the outputs!
+And here's {ref}`a reference to this section <section-two>`.
+I can also reference the section {ref}`section-two` without specifying my title.
+
+:::{note}
+And here's a note with a colon fence!
+:::
+
+And finally, here's a cool mermaid diagram!
+
+```{mermaid}
+sequenceDiagram
+ participant Alice
+ participant Bob
+ Alice->John: Hello John, how are you?
+ loop Healthcheck
+ John->John: Fight against hypochondria
+ end
+ Note right of John: Rational thoughts <br/>prevail...
+ John-->Alice: Great!
+ John->Bob: How about you?
+ Bob-->John: Jolly good!
+```
+````
+
+When you build your documentation, you should see something like this:
+
+```{mermaid}
+sequenceDiagram
+ participant Alice
+ participant Bob
+ Alice->John: Hello John, how are you?
+ loop Healthcheck
+ John->John: Fight against hypochondria
+ end
+ Note right of John: Rational thoughts <br/>prevail...
+ John-->Alice: Great!
+ John->Bob: How about you?
+ Bob-->John: Jolly good!
+```
diff --git a/docs/syntax/example.txt b/docs/syntax/example.txt
new file mode 100644
index 0000000..e84df8e
--- /dev/null
+++ b/docs/syntax/example.txt
@@ -0,0 +1 @@
+Hallo!
diff --git a/docs/syntax/img/fun-fish.png b/docs/syntax/img/fun-fish.png
new file mode 100644
index 0000000..c9a4997
--- /dev/null
+++ b/docs/syntax/img/fun-fish.png
Binary files differ
diff --git a/docs/syntax/optional.md b/docs/syntax/optional.md
new file mode 100644
index 0000000..c5ee44e
--- /dev/null
+++ b/docs/syntax/optional.md
@@ -0,0 +1,1002 @@
+---
+myst:
+ substitutions:
+ key1: I'm a **substitution**
+ key2: |
+ ```{note}
+ {{ key1 }}
+ ```
+ key3a: <img src="img/fun-fish.png" alt="fishy" width="200px">
+ key3: |
+ ```{image} img/fun-fish.png
+ :alt: fishy
+ :width: 200px
+ ```
+ key4: example
+---
+
+(syntax/extensions)=
+
+# Syntax Extensions
+
+MyST-Parser is highly configurable, utilising the inherent "plugability" of the [markdown-it-py](markdown_it:index) parser.
+The following syntaxes are optional (disabled by default) and can be enabled *via* the sphinx `conf.py` (see also [](sphinx/config-options)).
+Their goal is generally to add more *Markdown friendly* syntaxes; often enabling and rendering [markdown-it-py plugins](markdown_it:md/plugins) that extend the [CommonMark specification](https://commonmark.org/).
+
+To enable all the syntaxes explained below:
+
+```python
+myst_enable_extensions = [
+ "amsmath",
+ "colon_fence",
+ "deflist",
+ "dollarmath",
+ "fieldlist",
+ "html_admonition",
+ "html_image",
+ "linkify",
+ "replacements",
+ "smartquotes",
+ "strikethrough",
+ "substitution",
+ "tasklist",
+]
+```
+
+:::{important}
+`myst_enable_extensions` replaces previous configuration options:
+`admonition_enable`, `figure_enable`, `dmath_enable`, `amsmath_enable`, `deflist_enable`, `html_img_enable`
+:::
+
+(syntax/typography)=
+
+## Typography
+
+Adding `"smartquotes"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)) will automatically convert standard quotations to their opening/closing variants:
+
+- `'single quotes'`: 'single quotes'
+- `"double quotes"`: "double quotes"
+
+Adding `"replacements"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)) will automatically convert some common typographic texts
+
+text | converted
+----- | ----------
+``(c)``, ``(C)`` | (c)
+``(tm)``, ``(TM)`` | (tm)
+``(r)``, ``(R)`` | (r)
+``(p)``, ``(P)`` | (p)
+``+-`` | +-
+``...`` | ...
+``?....`` | ?....
+``!....`` | !....
+``????????`` | ????????
+``!!!!!`` | !!!!!
+``,,,`` | ,,,
+``--`` | --
+``---`` | ---
+
+(syntax/strikethrough)=
+
+## Strikethrough
+
+```{versionadded} 0.17.0
+```
+
+The `strikethrough` extension allows text within `~~` delimiters to have a strikethrough (horizontal line) placed over it.
+For example, `~~strikethrough with *emphasis*~~` renders as: ~~strikethrough with *emphasis*~~.
+
+:::{warning}
+This extension is currently only supported for HTML output,
+and you will need to suppress the `myst.strikethrough` warning
+(see [](howto/warnings))
+:::
+
+(syntax/math)=
+## Math shortcuts
+
+Math is parsed by adding to the `myst_enable_extensions` list option, in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html) one or both of:
+
+- `"dollarmath"` for parsing of dollar `$` and `$$` encapsulated math.
+- `"amsmath"` for direct parsing of [amsmath LaTeX environments](https://ctan.org/pkg/amsmath).
+
+These options enable their respective Markdown parser plugins, as detailed in the [markdown-it plugin guide](markdown_it:md/plugins).
+
+:::{important}
+`myst_dmath_enable=True` and `myst_amsmath_enable=True` are deprecated, and replaced by `myst_enable_extensions = ["dollarmath", "amsmath"]`
+:::
+
+### Dollar delimited math
+
+Enabling `dollarmath` will parse the following syntax:
+
+- Inline math: `$...$`
+- Display (block) math: `$$...$$`
+
+Additionally if `myst_dmath_allow_labels=True` is set (the default):
+
+- Display (block) math with equation label: `$$...$$ (1)`
+
+For example, `$x_{hey}=it+is^{math}$` renders as $x_{hey}=it+is^{math}$.
+This is equivalent to writing:
+
+```md
+{math}`x_{hey}=it+is^{math}`
+```
+
+:::{admonition} Escaping Dollars
+:class: tip
+
+Math can be escaped (negated) by adding a `\` before the first symbol, e.g. `\$a$` renders as \$a\$.
+Escaping can also be used inside math, e.g. `$a=\$3$` renders as $a=\$3$.
+
+Conversely `\\` will negate the escaping, so `\\$a$` renders as \\$a$.
+:::
+
+Block-level math can be specified with `$$` signs that wrap the math block you'd like to parse.
+For example:
+
+```latex
+$$
+ \begin{eqnarray}
+ y & = & ax^2 + bx + c \\
+ f(x) & = & x^2 + 2xy + y^2
+ \end{eqnarray}
+$$
+```
+
+becomes
+
+$$
+ \begin{eqnarray}
+ y & = & ax^2 + bx + c \\
+ f(x) & = & x^2 + 2xy + y^2
+ \end{eqnarray}
+$$
+
+This is equivalent to the following directive:
+
+````md
+```{math}
+ \begin{eqnarray}
+ y & = & ax^2 + bx + c \\
+ f(x) & = & x^2 + 2xy + y^2
+ \end{eqnarray}
+```
+````
+
+You can also add labels to block equations:
+
+```latex
+$$
+e = mc^2
+$$ (eqn:best)
+
+This is the best equation {eq}`eqn:best`
+```
+
+$$
+e = mc^2
+$$ (eqn:best)
+
+This is the best equation {eq}`eqn:best`
+
+There are a few other options available to control dollar math parsing:
+
+`myst_dmath_allow_space=False`, will cause inline math to only be parsed if there are no initial / final spaces, e.g. `$a$` but not `$ a$` or `$a $`.
+
+`myst_dmath_allow_digits=False`, will cause inline math to only be parsed if there are no initial / final digits, e.g. `$a$` but not `1$a$` or `$a$2`.
+
+These options can both be useful if you also wish to use `$` as a unit of currency.
+
+```{versionadded} 0.14.0
+`myst_dmath_double_inline` option
+```
+
+To allow display math (i.e. `$$`) within an inline context, set `myst_dmath_double_inline = True` (`False` by default).
+This allows for example:
+
+```latex
+Hence, for $\alpha \in (0, 1)$,
+$$
+ \mathbb P (\alpha \bar{X} \ge \mu) \le \alpha;
+$$
+i.e., $[\alpha \bar{X}, \infty)$ is a lower 1-sided $1-\alpha$ confidence bound for $\mu$.
+```
+
+Hence, for $\alpha \in (0, 1)$,
+$$
+ \mathbb P (\alpha \bar{X} \ge \mu) \le \alpha;
+$$
+i.e., $[\alpha \bar{X}, \infty)$ is a lower 1-sided $1-\alpha$ confidence bound for $\mu$.
+
+### Math in other block elements
+
+Math will also work when nested in other block elements, like lists or quotes:
+
+```md
+- A list
+- $$ a = 1 $$
+
+> A block quote
+> $$ a = 1 $$
+```
+
+- A list
+- $$ a = 1 $$
+
+> A block quote
+> $$ a = 1 $$
+
+### Direct LaTeX Math
+
+Want to use [amsmath](https://ctan.org/pkg/amsmath) LaTeX directly, with no dollars?
+See [the extended syntax option](syntax/amsmath).
+
+(syntax/mathjax)=
+### Mathjax and math parsing
+
+When building HTML using the [sphinx.ext.mathjax](https://www.sphinx-doc.org/en/master/usage/extensions/math.html#module-sphinx.ext.mathjax) extension (enabled by default),
+If `dollarmath` is enabled, Myst-Parser injects the `tex2jax_ignore` (MathJax v2) and `mathjax_ignore` (MathJax v3) classes in to the top-level section of each MyST document, and adds the following default MathJax configuration:
+
+MathJax version 2 (see [the tex2jax preprocessor](https://docs.mathjax.org/en/v2.7-latest/options/preprocessors/tex2jax.html#configure-tex2jax):
+
+```javascript
+MathJax.Hub.Config({"tex2jax": {"processClass": "tex2jax_process|mathjax_process|math|output_area"}})
+```
+
+MathJax version 3 (see [the document options](https://docs.mathjax.org/en/latest/options/document.html?highlight=ignoreHtmlClass#the-configuration-block)):
+
+```javascript
+window.MathJax = {"options": {"processHtmlClass": "tex2jax_process|mathjax_process|math|output_area"}}
+```
+
+This ensurea that MathJax processes only math, identified by the `dollarmath` and `amsmath` extensions, or specified in `math` directives.
+
+To change this behaviour, set a custom regex, for identifying HTML classes to process, like `myst_mathjax_classes="math|myclass"`, or set `myst_update_mathjax=False` to inhibit this override and process all HTML elements.
+
+(syntax/linkify)=
+## Linkify
+
+Adding `"linkify"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)) will automatically identify "bare" web URLs and add hyperlinks:
+
+`www.example.com` -> www.example.com
+
+To only match URLs that start with schema, such as `http://example.com`, set `myst_linkify_fuzzy_links=False`.
+
+:::{important}
+This extension requires that [linkify-it-py](https://github.com/tsutsu3/linkify-it-py) is installed.
+Either directly; `pip install linkify-it-py` or *via* `pip install myst-parser[linkify]`.
+:::
+
+(syntax/substitutions)=
+
+## Substitutions (with Jinja2)
+
+Adding `"substitution"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)) will allow you to add substitutions, added in either the `conf.py` using `myst_substitutions`:
+
+```python
+myst_substitutions = {
+ "key1": "I'm a **substitution**"
+}
+```
+
+or at the top of the file, in the front-matter section (see [this section](syntax/frontmatter)):
+
+````yaml
+---
+myst:
+ substitutions:
+ key1: "I'm a **substitution**"
+ key2: |
+ ```{note}
+ {{ key1 }}
+ ```
+ key3: |
+ ```{image} img/fun-fish.png
+ :alt: fishy
+ :width: 200px
+ ```
+ key4: example
+---
+````
+
+:::{important}
+Keys in the front-matter will override ones in the `conf.py`.
+:::
+
+You can use these substitutions inline or as blocks, and you can even nest substitutions in other substitutions (but circular references are prohibited):
+
+::::{tab-set}
+:::{tab-item} Markdown Input
+
+```md
+Inline: {{ key1 }}
+
+Block level:
+
+{{ key2 }}
+
+| col1 | col2 |
+| -------- | -------- |
+| {{key2}} | {{key3}} |
+
+```
+
+:::
+
+:::{tab-item} Rendered Output
+Inline: {{ key1 }}
+
+Block level:
+
+{{ key2 }}
+
+| col1 | col2 |
+| -------- | -------- |
+| {{key2}} | {{key3}} |
+
+:::
+::::
+
+:::{important}
+
+Substitutions will only be assessed where you would normally use Markdown, e.g. not in code blocks:
+
+````
+```
+{{ key1 }}
+```
+````
+
+```
+{{ key1 }}
+```
+
+One should also be wary of using unsuitable directives for inline substitutions.
+This may lead to unexpected outcomes.
+
+:::
+
+Substitution references are assessed as [Jinja2 expressions](http://jinja.palletsprojects.com) which can use [filters](https://jinja.palletsprojects.com/en/2.11.x/templates/#list-of-builtin-filters), and also contains the [Sphinx Environment](https://www.sphinx-doc.org/en/master/extdev/envapi.html) in the context (as `env`).
+Therefore you can do things like:
+
+```md
+- version: {{ env.config.version }}
+- docname: {{ env.docname | upper }}
+- {{ "a" + "b" }}
+```
+
+- version: {{ env.config.version }}
+- docname: {{ env.docname | upper }}
+- {{ "a" + "b" }}
+
+You can also change the delimiter if necessary, for example setting in the `conf.py`:
+
+```python
+myst_sub_delimiters = ["|", "|"]
+```
+
+Will parse: `|| "a" + "b" ||`.
+This should be changed with care though, so as not to affect other syntaxes.
+
+The exact logic for handling substitutions is:
+
+1. Combine global substitutions (specified in `conf.py`) with front-matter substitutions, to create a variable context (front-matter takes priority)
+2. Add the sphinx `env` to the variable context
+3. Create the string content to render using Jinja2 (passing it the variable context)
+4. If the substitution is inline and not a directive, render ignoring block syntaxes (like lists or block-quotes), otherwise render with all syntax rules.
+
+### Substitutions and URLs
+
+Substitutions cannot be directly used in URLs, such as `[a link](https://{{key4}}.com)` or `<https://{{key4}}.com>`.
+However, since Jinja2 substitutions allow for Python methods to be used, you can use string formatting or replacements:
+
+```md
+{{ '[a link](https://{}.com)'.format(key4) }}
+
+{{ '<https://myst-parser.readthedocs.io/en/latest/REPLACE.html>'.replace('REPLACE', env.docname) }}
+```
+
+{{ '[a link](https://{}.com)'.format(key4) }}
+
+{{ '<https://myst-parser.readthedocs.io/en/latest/REPLACE.html>'.replace('REPLACE', env.docname) }}
+
+(syntax/colon_fence)=
+
+## Code fences using colons
+
+By adding `"colon_fence"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+you can also use `:::` delimiters to denote code fences, instead of ```` ``` ````.
+
+Using colons instead of back-ticks has the benefit of allowing the content to be rendered correctly, when you are working in any standard Markdown editor.
+It is ideal for admonition type directives (as documented in [Directives](syntax/directives)) or tables with titles, for example:
+
+::::::{tab-set}
+:::::{tab-item} Markdown Input
+```md
+:::{note}
+This text is **standard** _Markdown_
+:::
+
+:::{table} This is a **standard** _Markdown_ title
+:align: center
+:widths: grid
+
+abc | mnp | xyz
+--- | --- | ---
+123 | 456 | 789
+:::
+```
+
+:::::
+
+:::::{tab-item} Rendered Output
+
+:::{note}
+This text is **standard** _Markdown_
+:::
+
+:::{table} This is a **standard** _Markdown_ title
+:align: center
+:widths: grid
+
+abc | mnp | xyz
+--- | --- | ---
+123 | 456 | 789
+:::
+
+:::::
+::::::
+
+Similar to normal directives, these directives can also be nested:
+
+```md
+::::{important}
+:::{note}
+This text is **standard** _Markdown_
+:::
+::::
+```
+
+::::{important}
+:::{note}
+This text is **standard** _Markdown_
+:::
+::::
+
+and also parameter options can be used:
+
+```md
+:::{admonition} This *is* also **Markdown**
+:class: warning
+
+This text is **standard** _Markdown_
+:::
+```
+
+:::{admonition} This *is* also **Markdown**
+:class: warning
+
+This text is **standard** _Markdown_
+:::
+
+(syntax/admonitions)=
+
+## Admonition directives
+
+:::{important}
+`myst_admonition_enable` is deprecated and replaced by `myst_enable_extensions = ["colon_fence"]` (see above).
+Also, classes should now be set with the `:class: myclass` option.
+
+Also see [](syntax/html-admonition).
+:::
+
+(syntax/header-anchors)=
+
+## Auto-generated header anchors
+
+The MyST Parser can automatically generate label "slugs" for header anchors so that you can reference them from markdown links.
+For example, you can use header bookmark links, locally; `[](#header-anchor)`, or cross-file `[](path/to/file.md#header-anchor)`.
+To achieve this, use the `myst_heading_anchors = DEPTH` configuration option, where `DEPTH` is the depth of header levels for which you wish to generate links.
+
+For example, the following configuration in `conf.py` tells the `myst_parser` to generate labels for heading anchors for `h1`, `h2`, and `h3` level headings (corresponding to `#`, `##`, and `###` in markdown).
+
+```python
+myst_heading_anchors = 3
+```
+
+You can then insert markdown links directly to anchors that are generated from your header titles in your documentation.
+For example `[](#auto-generated-header-anchors)`: [](#auto-generated-header-anchors).
+
+The paths to other files should be relative to the current file, for example
+`[**link text**](./syntax.md#core-syntax)`: [**link text**](./syntax.md#core-syntax).
+
+
+### Anchor slug structure
+
+The anchor "slugs" created aim to follow the [GitHub implementation](https://github.com/Flet/github-slugger):
+
+- lower-case text
+- remove punctuation
+- replace spaces with `-`
+- enforce uniqueness *via* suffix enumeration `-1`
+
+To change the slug generation function, set `myst_heading_slug_func` in your `conf.py` to a function that accepts a string and returns a string.
+
+### Inspect the links that will be created
+
+You can inspect the links that will be created using the command-line tool:
+
+```console
+$ myst-anchors -l 2 docs/syntax/optional.md
+<h1 id="optional-myst-syntaxes"></h1>
+<h2 id="admonition-directives"></h2>
+<h2 id="auto-generated-header-anchors"></h2>
+<h2 id="definition-lists"></h2>
+<h2 id="images"></h2>
+<h2 id="markdown-figures"></h2>
+<h2 id="direct-latex-math"></h2>
+```
+
+(syntax/definition-lists)=
+
+## Definition Lists
+
+By adding `"deflist"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+you will be able to utilise definition lists.
+Definition lists utilise the [markdown-it-py deflist plugin](markdown_it:md/plugins), which itself is based on the [Pandoc definition list specification](http://johnmacfarlane.net/pandoc/README.html#definition-lists).
+
+This syntax can be useful, for example, as an alternative to nested bullet-lists:
+
+- Term 1
+ - Definition
+- Term 2
+ - Definition
+
+Using instead:
+
+```md
+Term 1
+: Definition
+
+Term 2
+: Definition
+```
+
+Term 1
+: Definition
+
+Term 2
+: Definition
+
+From the Pandoc documentation:
+
+> Each term must fit on one line, which may optionally be followed by a blank line, and must be followed by one or more definitions.
+> A definition begins with a colon or tilde, which may be indented one or two spaces.
+
+> A term may have multiple definitions, and each definition may consist of one or more block elements (paragraph, code block, list, etc.)
+
+Here is a more complex example, demonstrating some of these features:
+
+Term *with Markdown*
+: Definition [with reference](syntax/definition-lists)
+
+ A second paragraph
+: A second definition
+
+Term 2
+ ~ Definition 2a
+ ~ Definition 2b
+
+Term 3
+: A code block
+: > A quote
+: A final definition, that can even include images:
+
+ <img src="img/fun-fish.png" alt="fishy" width="200px">
+
+This was created from:
+
+```md
+Term *with Markdown*
+: Definition [with reference](syntax/definition-lists)
+
+ A second paragraph
+: A second definition
+
+Term 2
+ ~ Definition 2a
+ ~ Definition 2b
+
+Term 3
+: A code block
+
+: > A quote
+
+: A final definition, that can even include images:
+
+ <img src="img/fun-fish.png" alt="fishy" width="200px">
+```
+
+(syntax/tasklists)=
+## Task Lists
+
+By adding `"tasklist"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+you will be able to utilise task lists.
+Task lists utilise the [markdown-it-py tasklists plugin](markdown_it:md/plugins),
+and are applied to markdown list items starting with `[ ]` or `[x]`:
+
+```markdown
+- [ ] An item that needs doing
+- [x] An item that is complete
+```
+
+- [ ] An item that needs doing
+- [x] An item that is complete
+
+(syntax/fieldlists)=
+## Field Lists
+
+```{versionadded} 0.16.0
+```
+
+Field lists are mappings from field names to field bodies,
+based on the [reStructureText syntax](https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#field-lists).
+
+````md
+:name only:
+:name: body
+:*Nested syntax*: Both name and body may contain **nested syntax**.
+:Paragraphs: Since the field marker may be quite long, the second
+ and subsequent lines of a paragraph do not have to line up
+ with the first line.
+:Alignment 1: If the field body starts on the first line...
+
+ Then the entire field body must be indented the same.
+:Alignment 2:
+ If the field body starts on a subsequent line...
+
+ Then the indentation is always two spaces.
+:Blocks:
+
+ As well as paragraphs, any block syntaxes may be used in a field body:
+
+ - Me
+ - Myself
+ - I
+
+ ```python
+ print("Hello, world!")
+ ```
+````
+
+:name only:
+:name: body
+:*Nested syntax*: Both name and body may contain **nested syntax**.
+:Paragraphs: Since the field marker may be quite long, the second
+ and subsequent lines of a paragraph do not have to line up
+ with the first line.
+:Alignment 1: If the field body starts on the first line...
+
+ Then the entire field body must be indented the same.
+:Alignment 2:
+ If the field body starts on a subsequent line...
+
+ Then the indentation is always two spaces.
+:Blocks:
+
+ As well as paragraphs, any block syntaxes may be used in a field body:
+
+ - Me
+ - Myself
+ - I
+
+ ```python
+ print("Hello, world!")
+ ```
+
+A prominent use case of field lists is for use in API docstrings, as used in [Sphinx's docstring renderers](sphinx:python-domain):
+
+````md
+```{py:function} send_message(sender, priority)
+
+Send a message to a recipient
+
+:param str sender: The person sending the message
+:param priority: The priority of the message, can be a number 1-5
+:type priority: int
+:return: the message id
+:rtype: int
+:raises ValueError: if the message_body exceeds 160 characters
+```
+````
+
+```{py:function} send_message(sender, priority)
+
+Send a message to a recipient
+
+:param str sender: The person sending the message
+:param priority: The priority of the message, can be a number 1-5
+:type priority: int
+:return: the message id
+:rtype: int
+:raises ValueError: if the message_body exceeds 160 characters
+```
+
+:::{note}
+Currently `sphinx.ext.autodoc` does not support MyST, see [](howto/autodoc).
+:::
+
+(syntax/images)=
+
+## Images
+
+MyST provides a few different syntaxes for including images in your documentation, as explained below.
+
+The first is the standard Markdown syntax:
+
+```md
+![fishy](img/fun-fish.png)
+```
+
+![fishy](img/fun-fish.png)
+
+This will correctly copy the image to the build folder and will render it in all output formats (HTML, TeX, etc).
+However, it is limited in the configuration that can be applied, for example setting a width.
+
+As discussed [above](syntax/directives), MyST allow for directives to be used such as `image` and `figure` (see {ref}`the sphinx documentation <sphinx:rst-primer>`):
+
+````md
+```{image} img/fun-fish.png
+:alt: fishy
+:class: bg-primary
+:width: 200px
+:align: center
+```
+````
+
+```{image} img/fun-fish.png
+:alt: fishy
+:class: bg-primary mb-1
+:width: 200px
+```
+
+Additional options can now be set, however, in contrast to the Markdown syntax, this syntax will not show the image in common Markdown viewers (for example when the files are viewed on GitHub).
+
+The final option is directly using HTML, which is also parsed by MyST.
+This is usually a bad option, because the HTML is treated as raw text during the build process and so sphinx will not recognise that the image file is to be copied, and will not output the HTML into non-HTML output formats.
+
+HTML parsing to the rescue!
+
+By adding `"html_image"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+MySt-Parser will attempt to convert any isolated `img` tags (i.e. not wrapped in any other HTML) to the internal representation used in sphinx.
+
+```html
+<img src="img/fun-fish.png" alt="fishy" width="200px">
+<img src="img/fun-fish.png" alt="fishy" width="200px" class="bg-primary">
+```
+
+<img src="img/fun-fish.png" alt="fishy" width="200px">
+<img src="img/fun-fish.png" alt="fishy" width="200px" class="bg-primary">
+
+Allowed attributes are equivalent to the `image` directive: src, alt, class, width, height and name.
+Any other attributes will be dropped.
+
+HTML image can also be used inline!
+
+I'm an inline image: <img src="img/fun-fish.png" height="20px">
+
+### Inline attributes
+
+:::{warning}
+This extension is currently experimental, and may change in future versions.
+:::
+
+By adding `"attrs_image"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+you can enable parsing of inline attributes for images.
+
+For example, the following Markdown:
+
+```md
+![image attrs](img/fun-fish.png){#imgattr .bg-primary width="100px" align=center}
+
+{ref}`a reference to the image <imgattr>`
+```
+
+will be parsed as:
+
+![image attrs](img/fun-fish.png){#imgattr .bg-primary width="100px" align=center}
+
+{ref}`a reference to the image <imgattr>`
+
+Inside the curly braces, the following syntax is possible:
+
+- `.foo` specifies `foo` as a class.
+ Multiple classes may be given in this way; they will be combined.
+- `#foo` specifies `foo` as an identifier.
+ An element may have only one identifier;
+ if multiple identifiers are given, the last one is used.
+- `key="value"` or `key=value` specifies a key-value attribute.
+ Quotes are not needed when the value consists entirely of
+ ASCII alphanumeric characters or `_` or `:` or `-`.
+ Backslash escapes may be used inside quoted values.
+- `%` begins a comment, which ends with the next `%` or the end of the attribute (`}`).
+
+(syntax/figures)=
+
+## Markdown Figures
+
+By adding `"colon_fence"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+we can combine the above two extended syntaxes,
+to create a fully Markdown compliant version of the `figure` directive named `figure-md`.
+
+:::{important}
+`myst_figure_enable` with the `figure` directive is deprecated and replaced by `myst_enable_extensions = ["colon_fence"]` and `figure-md`.
+:::
+
+The figure block must contain **only** two components; an image, in either Markdown or HTML syntax, and a single paragraph for the caption.
+
+The title is optional and taken as the reference target of the figure:
+
+```md
+:::{figure-md} fig-target
+:class: myclass
+
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+
+This is a caption in **Markdown**
+:::
+```
+
+:::{figure-md} fig-target
+:class: myclass
+
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+
+This is a caption in **Markdown**
+:::
+
+As we see here, the target we set can be referenced:
+
+```md
+[Go to the fish!](fig-target)
+```
+
+[Go to the fish!](fig-target)
+
+(syntax/html-admonition)=
+
+## HTML Admonitions
+
+By adding `"html_admonition"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+you can enable parsing of `<div class="admonition">` HTML blocks.
+These blocks will be converted internally to Sphinx admonition directives, and so will work correctly for all output formats.
+This is helpful when you care about viewing the "source" Markdown, such as in Jupyter Notebooks.
+
+If the first element within the `div` is `<div class="title">` or `<p class="title">`, then this will be set as the admonition title.
+All internal text (and the title) will be parsed as MyST-Markdown and all classes and an optional name will be passed to the admonition:
+
+```html
+<div class="admonition note" name="html-admonition" style="background: lightgreen; padding: 10px">
+<p class="title">This is the **title**</p>
+This is the *content*
+</div>
+```
+
+<div class="admonition note" name="html-admonition" style="background: lightgreen; padding: 10px">
+<div class="title">This is the **title**</div>
+This is the *content*
+</div>
+
+During the Sphinx render, both the `class` and `name` attributes will be used by Sphinx, but any other attributes like `style` will be discarded.
+
+:::{warning}
+There can be no empty lines in the block, otherwise they will be read as two separate blocks.
+If you want to use multiple paragraphs then they can be enclosed in `<p>`:
+
+```html
+<div class="admonition note">
+<p>Paragraph 1</p>
+<p>Paragraph 2</p>
+</div>
+```
+
+<div class="admonition note">
+<p>Paragraph 1</p>
+<p>Paragraph 2</p>
+</div>
+
+:::
+
+You can also nest HTML admonitions:
+
+```html
+<div class="admonition">
+<p>Some **content**</p>
+ <div class="admonition tip">
+ <div class="title">A *title*</div>
+ <p>Paragraph 1</p>
+ <p>Paragraph 2</p>
+ </div>
+</div>
+```
+
+<div class="admonition">
+<p>Some **content**</p>
+ <div class="admonition tip">
+ <div class="title">A *title*</div>
+ <p>Paragraph 1</p>
+ <p>Paragraph 2</p>
+ </div>
+</div>
+
+(syntax/amsmath)=
+
+## Direct LaTeX Math
+
+By adding `"amsmath"` to `myst_enable_extensions` (in the sphinx `conf.py` [configuration file](https://www.sphinx-doc.org/en/master/usage/configuration.html)),
+you can enable direct parsing of [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations.
+These top-level math environments will then be directly parsed:
+
+> equation, multline, gather, align, alignat, flalign, matrix, pmatrix, bmatrix, Bmatrix, vmatrix, Vmatrix, eqnarray.
+
+As expected, environments ending in `*` will not be numbered, for example:
+
+```latex
+\begin{gather*}
+a_1=b_1+c_1\\
+a_2=b_2+c_2-d_2+e_2
+\end{gather*}
+
+\begin{align}
+a_{11}& =b_{11}&
+ a_{12}& =b_{12}\\
+a_{21}& =b_{21}&
+ a_{22}& =b_{22}+c_{22}
+\end{align}
+```
+
+\begin{gather*}
+a_1=b_1+c_1\\
+a_2=b_2+c_2-d_2+e_2
+\end{gather*}
+
+\begin{align}
+a_{11}& =b_{11}&
+ a_{12}& =b_{12}\\
+a_{21}& =b_{21}&
+ a_{22}& =b_{22}+c_{22}
+\end{align}
+
+:::{note}
+`\labels` inside the environment are not currently identified, and so cannot be referenced.
+We hope to implement this in a future update (see [executablebooks/MyST-Parser#202](https://github.com/executablebooks/MyST-Parser/issues/202))!
+:::
+
+:::{important}
+See also [how Mathjax is configured with MyST-Parser](syntax/mathjax).
+:::
+
+This syntax will also work when nested in other block elements, like lists or quotes:
+
+```md
+- A list
+- \begin{gather*}
+ a_1=b_1+c_1\\a_2=b_2+c_2-d_2+e_2
+ \end{gather*}
+
+> A block quote
+> \begin{gather*}
+ a_1=b_1+c_1\\a_2=b_2+c_2-d_2+e_2
+ \end{gather*}
+```
+
+- A list
+- \begin{gather*}
+ a_1=b_1+c_1\\a_2=b_2+c_2-d_2+e_2
+ \end{gather*}
+
+> A block quote
+> \begin{gather*}
+ a_1=b_1+c_1\\a_2=b_2+c_2-d_2+e_2
+ \end{gather*}
diff --git a/docs/syntax/reference.md b/docs/syntax/reference.md
new file mode 100644
index 0000000..1e53b43
--- /dev/null
+++ b/docs/syntax/reference.md
@@ -0,0 +1,261 @@
+(syntax-tokens)=
+# Syntax tokens
+
+This page serves as a reference for the syntax that makes of MyST Markdown.
+
+:::{seealso}
+For more description and explanation of MyST syntax, see the [syntax guide](syntax.md).
+:::
+
+## Block (Multi-line) Tokens
+
+Block tokens span multiple lines of content. They are broken down into two sections:
+
+- {ref}`extended-block-tokens` contains *extra* tokens that are not in CommonMark.
+- {ref}`commonmark-block-tokens` contains CommonMark tokens that also work, for reference.
+
+:::{note}
+Because MyST markdown was inspired by functionality that exists in reStructuredText,
+we have shown equivalent rST syntax for many MyST markdown features below.
+:::
+
+(extended-block-tokens)=
+### Extended block tokens
+
+`````{list-table}
+:header-rows: 1
+:widths: 10 20 20
+
+* - Token
+ - Description
+ - Example
+* - FrontMatter
+ - A YAML block at the start of the document enclosed by `---`
+ - ```yaml
+ ---
+ key: value
+ ---
+ ```
+* - Directives
+ - enclosed in 3 or more backticks followed by the directive name wrapped
+ in curly brackets `{}`. See {ref}`syntax/directives` for more details.
+ - ````md
+ ```{directive}
+ :option: value
+
+ content
+ ```
+ ````
+* - Math
+ - `$$` (default) or `\[`...`\]` characters wrapping multi-line math, or even direct [amsmath](https://ctan.org/pkg/amsmath) LaTeX equations (optional).
+ See {ref}`syntax/math` for more information.
+ - ```latex
+ $$
+ a=1
+ $$
+ ```
+* - Table
+ - Standard markdown table style, with pipe separation.
+ - ```md
+ | a | b |
+ | :--- | ---: |
+ | c | d |
+ ```
+* - LineComment
+ - A commented line. See {ref}`syntax/comments` for more information.
+ - ```latex
+ % this is a comment
+ ```
+* - BlockBreak
+ - Define blocks of text. See {ref}`syntax/blockbreaks` for more information.
+ - ```md
+ +++ {"meta": "data"}
+ ```
+* - Footnote
+ - A definition for a referencing footnote, that is placed at the bottom of the document.
+ See {ref}`syntax/footnotes` for more details.
+ - ```md
+ [^ref]: Some footnote text
+ ```
+* - Admonitions (optional)
+ - An alternative approach for admonition style directives only, which has the benefit of allowing the content to be rendered in standard markdown editors.
+ See [admonition directives](syntax/admonitions) for more details.
+ - ````md
+ :::{note}
+ *content*
+ :::
+ ````
+`````
+
+(commonmark-block-tokens)=
+### CommonMark tokens
+
+`````{list-table}
+:header-rows: 1
+:widths: 10 20 20
+
+* - Token
+ - Description
+ - Example
+* - HTMLBlock
+ - Any valid HTML (rendered in HTML output only)
+ - ```html
+ <p>some text</p>
+ ```
+* - BlockCode
+ - indented text (4 spaces or a tab)
+ - ```md
+ included as literal *text*
+ ```
+* - Heading
+ - Level 1-6 headings, denoted by number of `#`
+ - ```md
+ ### Heading level 3
+ ```
+* - SetextHeading
+ - Underlined header (using multiple `=` or `-`)
+ - ```md
+ Header
+ ======
+ ```
+* - Quote
+ - Quoted text
+ - ```md
+ > this is a quote
+ ```
+* - CodeFence
+ - Enclosed in 3 or more `` ` `` or `~` with an optional language name.
+ See {ref}`syntax/code-blocks` for more information.
+ - ````md
+ ```python
+ print('this is python')
+ ```
+ ````
+* - ThematicBreak
+ - Creates a horizontal line in the output
+ - ```md
+ ---
+ ```
+* - List
+ - bullet points or enumerated.
+ - ```md
+ - item
+ - nested item
+ 1. numbered item
+ ```
+* - LinkDefinition
+ - A substitution for an inline link, which can have a reference target (no spaces), and an optional title (in `"`)
+ - ```md
+ [key]: https://www.google.com "a title"
+ ```
+* - Paragraph
+ - General inline text
+ - ```md
+ any *text*
+ ```
+`````
+
+## Span (Inline) Tokens
+
+Span (or inline) tokens are defined on a single line of content. They are broken down into two
+sections below:
+
+- {ref}`extended-span-tokens` contains *extra* tokens that are not in CommonMark.
+- {ref}`commonmark-span-tokens` contains CommonMark tokens that also work, for reference.
+
+(extended-span-tokens)=
+### Extended inline tokens
+
+`````{list-table}
+:header-rows: 1
+:widths: 10 20 20
+
+* - Token
+ - Description
+ - Example
+* - Role
+ - See {ref}`syntax/roles` for more information.
+ - ```md
+ {rolename}`interpreted text`
+ ```
+* - Target
+ - Precedes element to target, e.g. header. See
+ {ref}`syntax/targets` for more information.
+ - ```md
+ (target)=
+ ```
+* - Math
+ - `$` (default) or `\(`...`\)` enclosed math. See
+ {ref}`syntax/math` for more information.
+ - ```latex
+ $a=1$ or $$a=1$$
+ ```
+* - FootReference
+ - Reference a footnote. See {ref}`syntax/footnotes` for more details.
+ - ```md
+ [^abc]
+ ```
+`````
+
+(commonmark-span-tokens)=
+### CommonMark inline tokens
+
+`````{list-table}
+:header-rows: 1
+:widths: 10 20 20
+
+* - Token
+ - Description
+ - Example
+* - HTMLSpan
+ - Any valid HTML (rendered in HTML output only)
+ - ```html
+ <p>some text</p>
+ ```
+* - EscapeSequence
+ - Escaped symbols (to avoid them being interpreted as other syntax elements)
+ - ```md
+ \*
+ ```
+* - AutoLink
+ - Link that is shown in final output
+ - ```md
+ <http://www.google.com>
+ ```
+* - InlineCode
+ - Literal text
+ - ```md
+ `a=1`
+ ```
+* - LineBreak
+ - Soft or hard (ends with spaces or backslash)
+ - ```md
+ A hard break\
+ ```
+* - Image
+ - Link to an image.
+ You can also use HTML syntax, to include image size etc, [see here](syntax/images) for details
+ - ```md
+ ![alt](src "title")
+ ```
+* - Link
+ - Reference `LinkDefinitions`. See {ref}`syntax/referencing` for more details.
+ - ```md
+ [text](target "title") or [text][key]
+ ```
+* - Strong
+ - Bold text
+ - ```md
+ **strong**
+ ```
+* - Emphasis
+ - Italic text
+ - ```md
+ *emphasis*
+ ```
+* - RawText
+ - Any text
+ - ```md
+ any text
+ ```
+`````
diff --git a/docs/syntax/roles-and-directives.md b/docs/syntax/roles-and-directives.md
new file mode 100644
index 0000000..4b3d80a
--- /dev/null
+++ b/docs/syntax/roles-and-directives.md
@@ -0,0 +1,421 @@
+(roles-directives)=
+
+# Roles and Directives
+
+Roles and directives provide a way to extend the syntax of MyST in an unbound manner,
+by interpreting a chuck of text as a specific type of markup, according to its name.
+
+Mostly all [docutils roles](https://docutils.sourceforge.io/docs/ref/rst/roles.html), [docutils directives](https://docutils.sourceforge.io/docs/ref/rst/directives.html), [sphinx roles](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html), or [sphinx directives](https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html) can be used in MyST.
+
+## Syntax
+
+(syntax/directives)=
+
+### Directives - a block-level extension point
+
+Directives syntax is defined with triple-backticks and curly-brackets.
+It is effectively a Markdown code fence with curly brackets around the language, and a directive name in place of a language name.
+Here is the basic structure:
+
+`````{list-table}
+---
+header-rows: 1
+---
+* - MyST
+ - reStructuredText
+* - ````md
+ ```{directivename} arguments
+ ---
+ key1: val1
+ key2: val2
+ ---
+ This is
+ directive content
+ ```
+ ````
+ - ```rst
+ .. directivename:: arguments
+ :key1: val1
+ :key2: val2
+
+ This is
+ directive content
+ ```
+`````
+
+For example, the following code:
+
+````md
+```{admonition} This is my admonition
+This is my note
+```
+````
+
+Will generate this admonition:
+
+```{admonition} This is my admonition
+This is my note
+```
+
+#### Parameterizing directives
+
+For directives that take parameters as input, there are two ways to parameterize them.
+In each case, the options themselves are given as `key: value` pairs. An example of
+each is shown below:
+
+**Using YAML frontmatter**. A block of YAML front-matter just after the
+first line of the directive will be parsed as options for the directive. This needs to be
+surrounded by `---` lines. Everything in between will be parsed by YAML and
+passed as keyword arguments to your directive. For example:
+
+````md
+```{code-block} python
+---
+lineno-start: 10
+emphasize-lines: 1, 3
+caption: |
+ This is my
+ multi-line caption. It is *pretty nifty* ;-)
+---
+a = 2
+print('my 1st line')
+print(f'my {a}nd line')
+```
+````
+
+```{code-block} python
+---
+lineno-start: 10
+emphasize-lines: 1, 3
+caption: |
+ This is my
+ multi-line caption. It is *pretty nifty* ;-)
+---
+a = 2
+print('my 1st line')
+print(f'my {a}nd line')
+```
+
+**Short-hand options with `:` characters**. If you only need one or two options for your
+directive and wish to save lines, you may also specify directive options as a collection
+of lines just after the first line of the directive, each preceding with `:`. Then the
+leading `:` is removed from each line, and the rest is parsed as YAML.
+
+For example:
+
+````md
+```{code-block} python
+:lineno-start: 10
+:emphasize-lines: 1, 3
+
+a = 2
+print('my 1st line')
+print(f'my {a}nd line')
+```
+````
+
+(syntax/directives/parsing)=
+
+#### How directives parse content
+
+Some directives parse the content that is in their content block.
+MyST parses this content **as Markdown**.
+
+This means that MyST markdown can be written in the content areas of any directives written in MyST markdown. For example:
+
+````md
+```{admonition} My markdown link
+Here is [markdown link syntax](https://jupyter.org)
+```
+````
+
+```{admonition} My markdown link
+Here is [markdown link syntax](https://jupyter.org)
+```
+
+As a short-hand for directives that require no arguments, and when no parameter options are used (see below),
+you may start the content directly after the directive name.
+
+````md
+```{note} Notes require **no** arguments, so content can start here.
+```
+````
+
+```{note} Notes require **no** arguments, so content can start here.
+```
+
+For special cases, MySt also offers the `eval-rst` directive.
+This will parse the content **as ReStructuredText**:
+
+````md
+```{eval-rst}
+.. figure:: img/fun-fish.png
+ :width: 100px
+ :name: rst-fun-fish
+
+ Party time!
+
+A reference from inside: :ref:`rst-fun-fish`
+
+A reference from outside: :ref:`syntax/directives/parsing`
+```
+````
+
+```{eval-rst}
+.. figure:: img/fun-fish.png
+ :width: 100px
+ :name: rst-fun-fish
+
+ Party time!
+
+A reference from inside: :ref:`rst-fun-fish`
+
+A reference from outside: :ref:`syntax/directives/parsing`
+```
+
+Note how the text is integrated into the rest of the document, so we can also reference [party fish](rst-fun-fish) anywhere else in the documentation.
+
+#### Nesting directives
+
+You can nest directives by ensuring that the tick-lines corresponding to the
+outermost directive are longer than the tick-lines for the inner directives.
+For example, nest a warning inside a note block like so:
+
+`````md
+````{note}
+The next info should be nested
+```{warning}
+Here's my warning
+```
+````
+`````
+
+Here's how it looks rendered:
+
+````{note}
+The next info should be nested
+```{warning}
+Here's my warning
+```
+````
+
+You can indent inner-code fences, so long as they aren't indented by more than 3 spaces.
+Otherwise, they will be rendered as "raw code" blocks:
+
+`````md
+````{note}
+The warning block will be properly-parsed
+
+ ```{warning}
+ Here's my warning
+ ```
+
+But the next block will be parsed as raw text
+
+ ```{warning}
+ Here's my raw text warning that isn't parsed...
+ ```
+````
+`````
+
+````{note}
+The warning block will be properly-parsed
+
+ ```{warning}
+ Here's my warning
+ ```
+
+But the next block will be parsed as raw text
+
+ ```{warning}
+ Here's my raw text warning that isn't parsed...
+ ```
+````
+
+This can really be abused if you'd like ;-)
+
+``````{note}
+The next info should be nested
+`````{warning}
+Here's my warning
+````{admonition} Yep another admonition
+```python
+# All this fuss was about this boring python?!
+print('yep!')
+```
+````
+`````
+``````
+
+#### Markdown-friendly directives
+
+Want to use syntax that renders correctly in standard Markdown editors?
+See [the extended syntax option](syntax/colon_fence).
+
+```md
+:::{note}
+This text is **standard** *Markdown*
+:::
+```
+
+:::{note}
+This text is **standard** *Markdown*
+:::
+
+(syntax/roles)=
+
+### Roles - an in-line extension point
+
+Roles are similar to directives - they allow you to define arbitrary new functionality, but they are used *in-line*.
+To define an in-line role, use the following form:
+
+````{list-table}
+---
+header-rows: 1
+---
+* - MyST
+ - reStructuredText
+* - ````md
+ {role-name}`role content`
+ ````
+ - ```rst
+ :role-name:`role content`
+ ```
+````
+
+For example, the following code:
+
+```md
+Since Pythagoras, we know that {math}`a^2 + b^2 = c^2`
+```
+
+Becomes:
+
+Since Pythagoras, we know that {math}`a^2 + b^2 = c^2`
+
+You can use roles to do things like reference equations and other items in
+your book. For example:
+
+````md
+```{math} e^{i\pi} + 1 = 0
+---
+label: euler
+---
+```
+
+Euler's identity, equation {math:numref}`euler`, was elected one of the
+most beautiful mathematical formulas.
+````
+
+Becomes:
+
+```{math} e^{i\pi} + 1 = 0
+---
+label: euler
+---
+```
+
+Euler's identity, equation {math:numref}`euler`, was elected one of the
+most beautiful mathematical formulas.
+
+#### How roles parse content
+
+The content of roles is parsed differently depending on the role that you've used.
+Some roles expect inputs that will be used to change functionality. For example,
+the `ref` role will assume that input content is a reference to some other part of the
+site. However, other roles may use the MyST parser to parse the input as content.
+
+Some roles also **extend their functionality** depending on the content that you pass.
+For example, following the `ref` example above, if you pass a string like this:
+`Content to display <myref>`, then the `ref` will display `Content to display` and use
+`myref` as the reference to look up.
+
+How roles parse this content depends on the author that created the role.
+
+## Common roles and directives
+
+:::{admonition} {material-regular}`engineering;1.5rem;sd-mr-1` Currently Under Construction
+:class: no-icon
+Check back for more...
+:::
+
+### ToC Trees
+
+```{doc-directive} contents
+Insert a table of contents tree of the documents headings.
+```
+
+```{doc-directive} toctree
+Inserts a Sphinx "Table of Contents" tree, containing a list of (relative) child document paths.
+```
+
+### Admonitions
+
+```{doc-directive} admonition
+Create a generic "callout" box, containing the content.
+```
+
+```{doc-directive} note
+Create a "callout" box, specific to notes, containing the content.
+```
+
+Other admonitions (same structure as `note`): `attention`, `caution`, `danger`, `error`, `hint`, `important`, `tip`, `warning`.
+
+Sphinx only: `deprecated`, `versionadded`, `versionchanged`.
+
+### Images and Figures
+
+```{doc-directive} image
+Insert an image, from a (relative) path or URL.
+```
+
+```{doc-directive} figure
+Insert an image, from a (relative) path or URL,
+with a caption (first paragraph), and optional legend (subsequent content).
+```
+
+```{doc-directive} table
+Insert a (MyST) table with a caption.
+```
+
+### Tables
+
+```{doc-directive} list-table
+Create a table from data in a uniform two-level bullet list.
+```
+
+```{doc-directive} csv-table
+Create a table from CSV (comma-separated values) data.
+```
+
+### Code
+
+```{doc-directive} code-block
+Syntax highlight a block of code, according to the language.
+```
+
+(syntax/roles/special)=
+
+### MyST only
+
+This section contains information about special roles and directives that come bundled with the MyST Parser Sphinx extension.
+
+#### Insert the date and reading time
+
+```{versionadded} 0.14.0
+The `sub-ref` role and word counting.
+```
+
+You may insert the "last updated" date and estimated reading time into your document via substitution definitions, which can be accessed *via* the `sub-ref` role.
+
+For example:
+
+```markdown
+> {sub-ref}`today` | {sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
+```
+
+> {sub-ref}`today` | {sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
+
+`today` is replaced by either the date on which the document is parsed, with the format set by [`today_fmt`](https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-today_fmt), or the `today` variable if set in the configuration file.
+
+The reading speed is computed using the `myst_words_per_minute` configuration (see the [Sphinx configuration options](sphinx/config-options)).
diff --git a/docs/syntax/syntax.md b/docs/syntax/syntax.md
new file mode 100644
index 0000000..31c7f8d
--- /dev/null
+++ b/docs/syntax/syntax.md
@@ -0,0 +1,491 @@
+(syntax/core)=
+
+# Core Syntax
+
+## Introduction
+
+MyST is a strict superset of the [CommonMark syntax specification](https://spec.commonmark.org/).
+It adds features focussed on scientific and technical documentation authoring, as detailed below.
+
+In addition, the roles and directives syntax provide inline/block-level extension points for plugins.
+This is detailed further in the [Roles and Directives](roles-directives) section.
+
+:::{seealso}
+The [syntax token reference tables](syntax-tokens)
+:::
+
+(syntax/commonmark)=
+
+## CommonMark
+
+The [CommonMark syntax specification](https://spec.commonmark.org/) details the full set of syntax rules.
+Here we provide a summary of most features:
+
+Element | Syntax
+--------------- | -------------------------------------------
+Heading | `# H1` to `###### H6`
+Bold | `**bold**`
+Italic | `*italic*`
+Inline Code | `` `code` ``
+Autolink | `<https://www.example.com>`
+URL Link | `[title](https://www.example.com)`
+Image | `![alt](https://www.example.com/image.png)`
+Reference Link | `[title][link]`
+Link Definition | `[link]: https://www.example.com`
+Thematic break | `---`
+Blockquote | `> quote`
+Ordered List | `1. item`
+Unordered List | `- item`
+Code Fence | opening ```` ```lang ```` to closing ```` ``` ````
+
+(syntax/frontmatter)=
+
+## Front Matter
+
+This is a [YAML](https://en.wikipedia.org/wiki/YAML) block at the start of the document, as used for example in [jekyll](https://jekyllrb.com/docs/front-matter/).
+The document should start with three or more `---` markers, and YAML is parsed until a closing `---` marker is found:
+
+```yaml
+---
+key1: value
+key2: [value1, value2]
+key3:
+ subkey1: value
+---
+```
+
+:::{seealso}
+Top-matter is also used for the [substitution syntax extension](syntax/substitutions),
+and can be used to store information for blog posting (see [ablog's myst-parser support](https://ablog.readthedocs.io/en/latest/manual/markdown/)).
+:::
+
+### Setting a title
+
+```{versionadded} 0.17.0
+```
+
+If `myst_title_to_header` is set to `True`, and a `title` key is present in the front matter,
+then the title will be used as the document's header (parsed as Markdown).
+For example:
+
+```md
+---
+title: My Title with *emphasis*
+---
+```
+
+would be equivalent to:
+
+```md
+# My Title with *emphasis*
+```
+
+(syntax/html_meta)=
+
+### Setting HTML Metadata
+
+The front-matter can contain the special key `html_meta`; a dict with data to add to the generated HTML as [`<meta>` elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta).
+This is equivalent to using the [RST `meta` directive](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#html-metadata).
+
+HTML metadata can also be added globally in the `conf.py` *via* the `myst_html_meta` variable, in which case it will be added to all MyST documents.
+For each document, the `myst_html_meta` dict will be updated by the document level front-matter `html_meta`, with the front-matter taking precedence.
+
+::::{tab-set}
+:::{tab-item} Sphinx Configuration
+
+```python
+language = "en"
+myst_html_meta = {
+ "description lang=en": "metadata description",
+ "description lang=fr": "description des métadonnées",
+ "keywords": "Sphinx, MyST",
+ "property=og:locale": "en_US"
+}
+```
+
+:::
+
+:::{tab-item} MyST Front-Matter
+
+```yaml
+---
+myst:
+ html_meta:
+ "description lang=en": "metadata description"
+ "description lang=fr": "description des métadonnées"
+ "keywords": "Sphinx, MyST"
+ "property=og:locale": "en_US"
+---
+```
+
+:::
+
+:::{tab-item} RestructuredText
+
+```restructuredtext
+.. meta::
+ :description lang=en: metadata description
+ :description lang=fr: description des métadonnées
+ :keywords: Sphinx, MyST
+ :property=og:locale: en_US
+```
+
+:::
+
+:::{tab-item} HTML Output
+
+```html
+<html lang="en">
+ <head>
+ <meta content="metadata description" lang="en" name="description" xml:lang="en" />
+ <meta content="description des métadonnées" lang="fr" name="description" xml:lang="fr" />
+ <meta name="keywords" content="Sphinx, MyST">
+ <meta content="en_US" property="og:locale" />
+```
+
+:::
+::::
+
+(syntax/comments)=
+
+## Comments
+
+You may add comments by putting the `%` character at the beginning of a line. This will
+prevent the line from being parsed into the output document.
+
+For example, this code:
+
+```md
+% my comment
+```
+
+Is below, but it won't be parsed into the document.
+
+% my comment
+
+````{important}
+Since comments are a block-level entity, they will terminate the previous block.
+In practical terms, this means that the following lines
+will be broken up into two paragraphs, resulting in a new line between them:
+
+```
+a line
+% a comment
+another line
+```
+
+a line
+% a comment
+another line
+````
+
+:::{tip}
+Comments are equivalent to the RST syntax: `.. my comment`.
+:::
+
+(syntax/blockbreaks)=
+
+## Block Breaks
+
+You may add a block break by putting `+++` at the beginning of a line.
+This constuct's intended use case is for mapping to cell based document formats,
+like [jupyter notebooks](https://jupyter.org/),
+to indicate a new text cell. It will not show up in the rendered text,
+but is stored in the internal document structure for use by developers.
+
+For example, this code:
+
+```md
++++ some text
+```
+
+Is below, but it won't be parsed into the document.
+
++++
+
+(syntax/referencing)=
+
+## Markdown Links and Referencing
+
+Markdown links are of the form: `[text](link)`.
+
+If you set the configuration `myst_all_links_external = True` (`False` by default),
+then all links will be treated simply as "external" links.
+For example, in HTML outputs, `[text](link)` will be rendered as `<a href="link">text</a>`.
+
+Otherwise, links will only be treated as "external" links if they are prefixed with a scheme,
+configured with `myst_url_schemes` (by default, `http`, `https`, `ftp`, or `mailto`).
+For example, `[example.com](https://example.com)` becomes [example.com](https://example.com).
+
+:::{note}
+The `text` will be parsed as nested Markdown, for example `[here's some *emphasised text*](https://example.com)` will be parsed as [here's some *emphasised text*](https://example.com).
+:::
+
+For "internal" links, myst-parser in Sphinx will attempt to resolve the reference to either a relative document path, or a cross-reference to a target (see [](syntax/targets)):
+
+- `[this doc](syntax.md)` will link to a rendered source document: [this doc](syntax.md)
+ - This is similar to `` {doc}`this doc <syntax>` ``; {doc}`this doc <syntax>`, but allows for document extensions, and parses nested Markdown text.
+- `[example text](example.txt)` will link to a non-source (downloadable) file: [example text](example.txt)
+ - The linked document itself will be copied to the build directory.
+ - This is similar to `` {download}`example text <example.txt>` ``; {download}`example text <example.txt>`, but parses nested Markdown text.
+- `[reference](syntax/referencing)` will link to an internal cross-reference: [reference](syntax/referencing)
+ - This is similar to `` {any}`reference <syntax/referencing>` ``; {any}`reference <syntax/referencing>`, but parses nested Markdown text.
+ - You can limit the scope of the cross-reference to specific [sphinx domains](sphinx:domain), by using the `myst_ref_domains` configuration.
+ For example, `myst_ref_domains = ("std", "py")` will only allow cross-references to `std` and `py` domains.
+
+Additionally, only if [](syntax/header-anchors) are enabled, then internal links to document headers can be used.
+For example `[a header](syntax.md#markdown-links-and-referencing)` will link to a header anchor: [a header](syntax.md#markdown-links-and-referencing).
+
+(syntax/targets)=
+
+## Targets and Cross-Referencing
+
+Targets are used to define custom anchors that you can refer to elsewhere in your
+documentation. They generally go before section titles so that you can easily refer
+to them.
+
+:::{tip}
+
+If you'd like to *automatically* generate targets for each of your section headers,
+check out the [](syntax/header-anchors) section of extended syntaxes.
+
+:::
+
+Target headers are defined with this syntax:
+
+```md
+(header_target)=
+```
+
+They can then be referred to with the [ref inline role](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-ref):
+
+```md
+{ref}`header_target`
+```
+
+By default, the reference will use the text of the target (such as the section title), but also you can directly specify the text:
+
+```md
+{ref}`my text <header_target>`
+```
+
+For example, see this ref: {ref}`syntax/targets`, and here's a ref back to the top of this page: {ref}`my text <syntax/core>`.
+
+Alternatively using the markdown syntax:
+
+```md
+[my text](header_target)
+```
+
+is equivalent to using the [any inline role](https://www.sphinx-doc.org/en/master/usage/restructuredtext/roles.html#role-any):
+
+```md
+{any}`my text <header_target>`
+```
+
+but can also accept "nested" syntax (like bold text) and will recognise document paths that include extensions (e.g. `syntax/syntax` or `syntax/syntax.md`)
+
+Using the same example, see this ref: [](syntax/targets), here is a reference back to the top of
+this page: [my text with **nested** $\alpha$ syntax](syntax/core), and here is a reference to another page (`[](../intro.md)`): [](../intro.md).
+
+```{note}
+If you wish to have the target's title inserted into your text, you can
+leave the "text" section of the markdown link empty. For example, this
+markdown: `[](syntax.md)` will result in: [](syntax.md).
+```
+
+(syntax/code-blocks)=
+## Code syntax highlighting
+
+Code blocks contain a language identifier, which is used to determine the language of the code.
+This language is used to determine the syntax highlighting, using an available [pygments lexer](https://pygments.org/docs/lexers/).
+
+````markdown
+```python
+from a import b
+c = "string"
+```
+````
+
+```python
+from a import b
+c = "string"
+```
+
+You can create and register your own lexer, using the [`pygments.lexers` entry point](https://pygments.org/docs/plugins/#register-plugins),
+or within a sphinx extension, with the [`app.add_lexer` method](sphinx:sphinx.application.Sphinx.add_lexer).
+
+Using the `myst_number_code_blocks` configuration option, you can also control whether code blocks are numbered by line.
+For example, using `myst_number_code_blocks = ["typescript"]`:
+
+```typescript
+type MyBool = true | false;
+
+interface User {
+ name: string;
+ id: number;
+}
+```
+
+### Show backticks inside raw markdown blocks
+
+If you'd like to show backticks inside of your markdown, you can do so by nesting them
+in backticks of a greater length. Markdown will treat the outer-most backticks as the
+edges of the "raw" block and everything inside will show up. For example:
+
+``` `` `hi` `` ``` will be rendered as: `` `hi` ``
+
+and
+
+`````
+````
+```
+hi
+```
+````
+`````
+
+will be rendered as:
+
+````
+```
+hi
+```
+````
+
+## Tables
+
+Tables can be written using the standard [Github Flavoured Markdown syntax](https://github.github.com/gfm/#tables-extension-):
+
+```md
+| foo | bar |
+| --- | --- |
+| baz | bim |
+```
+
+| foo | bar |
+| --- | --- |
+| baz | bim |
+
+Cells in a column can be aligned using the `:` character:
+
+```md
+| left | center | right |
+| :--- | :----: | ----: |
+| a | b | c |
+```
+
+| left | center | right |
+| :--- | :----: | ----: |
+| a | b | c |
+
+:::{note}
+
+Text is aligned by assigning `text-left`, `text-center`, or `text-right` to the cell.
+It is then necessary for the theme you are using to include the appropriate css styling.
+
+```html
+<table class="colwidths-auto table">
+ <thead>
+ <tr><th class="text-left head"><p>left</p></th></tr>
+ </thead>
+ <tbody>
+ <tr><td class="text-left"><p>a</p></td></tr>
+ </tbody>
+</table>
+```
+
+:::
+
+## Images
+
+MyST provides a few different syntaxes for including images in your documentation.
+
+The standard Markdown syntax is:
+
+```md
+![fishy](img/fun-fish.png)
+```
+
+![fishy](img/fun-fish.png)
+
+But you can also enable extended image syntaxes, to control attributes like width and captions.
+See the [extended image syntax guide](syntax/images).
+
+(syntax/footnotes)=
+## Footnotes
+
+Footnotes use the [pandoc specification](https://pandoc.org/MANUAL.html#footnotes).
+Their labels **start with `^`** and can then be any alphanumeric string (no spaces), which is case-insensitive.
+
+- If the label is an integer, then it will always use that integer for the rendered label (i.e. they are manually numbered).
+- For any other labels, they will be auto-numbered in the order which they are referenced, skipping any manually numbered labels.
+
+All footnote definitions are collected, and displayed at the bottom of the page (in the order they are referenced).
+Note that un-referenced footnote definitions will not be displayed.
+
+```md
+- This is a manually-numbered footnote reference.[^3]
+- This is an auto-numbered footnote reference.[^myref]
+
+[^myref]: This is an auto-numbered footnote definition.
+[^3]: This is a manually-numbered footnote definition.
+```
+
+- This is a manually-numbered footnote reference.[^3]
+- This is an auto-numbered footnote reference.[^myref]
+
+[^myref]: This is an auto-numbered footnote definition.
+[^3]: This is a manually-numbered footnote definition.
+
+Any preceding text after a footnote definitions, which is
+indented by four or more spaces, will also be included in the footnote definition, and the text is rendered as MyST Markdown, e.g.
+
+```md
+A longer footnote definition.[^mylongdef]
+
+[^mylongdef]: This is the _**footnote definition**_.
+
+ That continues for all indented lines
+
+ - even other block elements
+
+ Plus any preceding unindented lines,
+that are not separated by a blank line
+
+This is not part of the footnote.
+```
+
+A longer footnote definition.[^mylongdef]
+
+[^mylongdef]: This is the _**footnote definition**_.
+
+ That continues for all indented lines
+
+ - even other block elements
+
+ Plus any subsequent unindented lines,
+that are not separated by a blank line
+
+This is not part of the footnote.
+
+````{important}
+Although footnote references can be used just fine within directives, e.g.[^myref],
+it is recommended that footnote definitions are not set within directives,
+unless they will only be referenced within that same directive:
+
+```md
+[^other]
+
+[^other]: A definition within a directive
+```
+
+[^other]
+
+[^other]: A definition within a directive
+
+This is because, in the current implementation, they may not be available to reference in text above that particular directive.
+````
+
+By default, a transition line (with a `footnotes` class) will be placed before any footnotes.
+This can be turned off by adding `myst_footnote_transition = False` to the config file.
diff --git a/example-include.md b/example-include.md
new file mode 100644
index 0000000..3f85dc5
--- /dev/null
+++ b/example-include.md
@@ -0,0 +1,2 @@
+[Used in how-to](docs/faq/index.md)
+![alt](docs/_static/logo-wide.svg)
diff --git a/myst_parser/__init__.py b/myst_parser/__init__.py
new file mode 100644
index 0000000..56dd460
--- /dev/null
+++ b/myst_parser/__init__.py
@@ -0,0 +1,10 @@
+"""An extended commonmark compliant parser, with bridges to docutils & sphinx."""
+__version__ = "0.18.1"
+
+
+def setup(app):
+ """Initialize the Sphinx extension."""
+ from myst_parser.sphinx_ext.main import setup_sphinx
+
+ setup_sphinx(app, load_parser=True)
+ return {"version": __version__, "parallel_read_safe": True}
diff --git a/myst_parser/_compat.py b/myst_parser/_compat.py
new file mode 100644
index 0000000..d29cf4d
--- /dev/null
+++ b/myst_parser/_compat.py
@@ -0,0 +1,11 @@
+"""Helpers for cross compatibility across dependency versions."""
+from typing import Callable, Iterable
+
+from docutils.nodes import Element
+
+
+def findall(node: Element) -> Callable[..., Iterable[Element]]:
+ """Iterate through"""
+ # findall replaces traverse in docutils v0.18
+ # note a difference is that findall is an iterator
+ return getattr(node, "findall", node.traverse)
diff --git a/myst_parser/_docs.py b/myst_parser/_docs.py
new file mode 100644
index 0000000..a7c46a3
--- /dev/null
+++ b/myst_parser/_docs.py
@@ -0,0 +1,198 @@
+"""Code to use internally, for documentation."""
+from __future__ import annotations
+
+import io
+from typing import Sequence, Union
+
+from docutils import nodes
+from docutils.frontend import OptionParser
+from docutils.parsers.rst import directives
+from sphinx.directives import other
+from sphinx.util import logging
+from sphinx.util.docutils import SphinxDirective
+from typing_extensions import get_args, get_origin
+
+from .config.main import MdParserConfig
+from .parsers.docutils_ import Parser as DocutilsParser
+
+logger = logging.getLogger(__name__)
+
+
+class _ConfigBase(SphinxDirective):
+ """Directive to automate rendering of the configuration."""
+
+ @staticmethod
+ def table_header():
+ return [
+ "```````{list-table}",
+ ":header-rows: 1",
+ ":widths: 15 10 20",
+ "",
+ "* - Name",
+ " - Type",
+ " - Description",
+ ]
+
+ @staticmethod
+ def field_default(value):
+ default = " ".join(f"{value!r}".splitlines())
+ return default
+
+ @staticmethod
+ def field_type(field):
+ ftypes: Sequence[str]
+ if get_origin(field.type) is Union:
+ ftypes = get_args(field.type)
+ else:
+ ftypes = [field.type]
+ ctype = " | ".join(
+ str("None" if ftype == type(None) else ftype) # type: ignore # noqa: E721
+ for ftype in ftypes
+ )
+ ctype = " ".join(ctype.splitlines())
+ ctype = ctype.replace("typing.", "")
+ ctype = ctype.replace("typing_extensions.", "")
+ for tname in ("str", "int", "float", "bool"):
+ ctype = ctype.replace(f"<class '{tname}'>", tname)
+ return ctype
+
+
+class MystConfigDirective(_ConfigBase):
+
+ option_spec = {
+ "sphinx": directives.flag,
+ "extensions": directives.flag,
+ "scope": lambda x: directives.choice(x, ["global", "local"]),
+ }
+
+ def run(self):
+ """Run the directive."""
+ config = MdParserConfig()
+ text = self.table_header()
+ count = 0
+ for name, value, field in config.as_triple():
+
+ # filter by sphinx options
+ if "sphinx" in self.options and field.metadata.get("sphinx_exclude"):
+ continue
+
+ if "extensions" in self.options:
+ if not field.metadata.get("extension"):
+ continue
+ else:
+ if field.metadata.get("extension"):
+ continue
+
+ if self.options.get("scope") == "local":
+ if field.metadata.get("global_only"):
+ continue
+
+ if self.options.get("scope") == "global":
+ name = f"myst_{name}"
+
+ description = " ".join(field.metadata.get("help", "").splitlines())
+ if field.metadata.get("extension"):
+ description = f"{field.metadata.get('extension')}: {description}"
+ default = self.field_default(value)
+ ctype = self.field_type(field)
+ text.extend(
+ [
+ f"* - `{name}`",
+ f" - `{ctype}`",
+ f" - {description} (default: `{default}`)",
+ ]
+ )
+
+ count += 1
+
+ if not count:
+ return []
+
+ text.append("```````")
+ node = nodes.Element()
+ self.state.nested_parse(text, 0, node)
+ return node.children
+
+
+class DocutilsCliHelpDirective(SphinxDirective):
+ """Directive to print the docutils CLI help."""
+
+ has_content = False
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = False
+
+ def run(self):
+ """Run the directive."""
+ stream = io.StringIO()
+ OptionParser(
+ components=(DocutilsParser,),
+ usage="myst-docutils-<writer> [options] [<source> [<destination>]]",
+ ).print_help(stream)
+ return [nodes.literal_block("", stream.getvalue())]
+
+
+class DirectiveDoc(SphinxDirective):
+ """Load and document a directive."""
+
+ required_arguments = 1 # name of the directive
+ has_content = True
+
+ def run(self):
+ """Run the directive."""
+ name = self.arguments[0]
+ # load the directive class
+ klass, _ = directives.directive(
+ name, self.state.memo.language, self.state.document
+ )
+ if klass is None:
+ logger.warning(f"Directive {name} not found.", line=self.lineno)
+ return []
+ content = " ".join(self.content)
+ text = f"""\
+:Name: `{name}`
+:Description: {content}
+:Arguments: {klass.required_arguments} required, {klass.optional_arguments} optional
+:Content: {'yes' if klass.has_content else 'no'}
+:Options:
+"""
+ if klass.option_spec:
+ text += " name | type\n -----|------\n"
+ for key, func in klass.option_spec.items():
+ text += f" {key} | {convert_opt(name, func)}\n"
+ node = nodes.Element()
+ self.state.nested_parse(text.splitlines(), 0, node)
+ return node.children
+
+
+def convert_opt(name, func):
+ """Convert an option function to a string."""
+ if func is directives.flag:
+ return "flag"
+ if func is directives.unchanged:
+ return "text"
+ if func is directives.unchanged_required:
+ return "text"
+ if func is directives.class_option:
+ return "space-delimited list"
+ if func is directives.uri:
+ return "URI"
+ if func is directives.path:
+ return "path"
+ if func is int:
+ return "integer"
+ if func is directives.positive_int:
+ return "integer (positive)"
+ if func is directives.nonnegative_int:
+ return "integer (non-negative)"
+ if func is directives.positive_int_list:
+ return "space/comma-delimited list of integers (positive)"
+ if func is directives.percentage:
+ return "percentage"
+ if func is directives.length_or_unitless:
+ return "length or unitless"
+ if func is directives.length_or_percentage_or_unitless:
+ return "length, percentage or unitless"
+ if func is other.int_or_nothing:
+ return "integer"
+ return ""
diff --git a/myst_parser/cli.py b/myst_parser/cli.py
new file mode 100644
index 0000000..b9bb1ba
--- /dev/null
+++ b/myst_parser/cli.py
@@ -0,0 +1,42 @@
+import argparse
+import sys
+
+from markdown_it.renderer import RendererHTML
+
+from myst_parser.config.main import MdParserConfig
+from myst_parser.parsers.mdit import create_md_parser
+
+
+def print_anchors(args=None):
+ """ """
+ arg_parser = argparse.ArgumentParser()
+ arg_parser.add_argument(
+ "input",
+ nargs="?",
+ type=argparse.FileType("r", encoding="utf8"),
+ default=sys.stdin,
+ help="Input file (default stdin)",
+ )
+ arg_parser.add_argument(
+ "-o",
+ "--output",
+ type=argparse.FileType("w", encoding="utf8"),
+ default=sys.stdout,
+ help="Output file (default stdout)",
+ )
+ arg_parser.add_argument(
+ "-l", "--level", type=int, default=2, help="Maximum heading level."
+ )
+ args = arg_parser.parse_args(args)
+ parser = create_md_parser(MdParserConfig(heading_anchors=args.level), RendererHTML)
+
+ def _filter_plugin(state):
+ state.tokens = [
+ t
+ for t in state.tokens
+ if t.type.startswith("heading_") and int(t.tag[1]) <= args.level
+ ]
+
+ parser.use(lambda p: p.core.ruler.push("filter", _filter_plugin))
+ text = parser.render(args.input.read())
+ args.output.write(text)
diff --git a/myst_parser/config/__init__.py b/myst_parser/config/__init__.py
new file mode 100644
index 0000000..898f9ce
--- /dev/null
+++ b/myst_parser/config/__init__.py
@@ -0,0 +1 @@
+"""This module holds the global configuration for the parser ``MdParserConfig``."""
diff --git a/myst_parser/config/dc_validators.py b/myst_parser/config/dc_validators.py
new file mode 100644
index 0000000..765cfb9
--- /dev/null
+++ b/myst_parser/config/dc_validators.py
@@ -0,0 +1,161 @@
+"""Validators for dataclasses, mirroring those of https://github.com/python-attrs/attrs."""
+from __future__ import annotations
+
+import dataclasses as dc
+from typing import Any, Sequence
+
+from typing_extensions import Protocol
+
+
+def validate_field(inst: Any, field: dc.Field, value: Any) -> None:
+ """Validate the field of a dataclass,
+ according to a `validator` function set in the field.metadata.
+
+ The validator function should take as input (inst, field, value) and
+ raise an exception if the value is invalid.
+ """
+ if "validator" not in field.metadata:
+ return
+ if isinstance(field.metadata["validator"], list):
+ for validator in field.metadata["validator"]:
+ validator(inst, field, value)
+ else:
+ field.metadata["validator"](inst, field, value)
+
+
+def validate_fields(inst: Any) -> None:
+ """Validate the fields of a dataclass,
+ according to `validator` functions set in the field metadata.
+
+ This function should be called in the `__post_init__` of the dataclass.
+
+ The validator function should take as input (inst, field, value) and
+ raise an exception if the value is invalid.
+ """
+ for field in dc.fields(inst):
+ validate_field(inst, field, getattr(inst, field.name))
+
+
+class ValidatorType(Protocol):
+ def __call__(
+ self, inst: bytes, field: dc.Field, value: Any, suffix: str = ""
+ ) -> None:
+ ...
+
+
+def instance_of(type: type[Any] | tuple[type[Any], ...]) -> ValidatorType:
+ """
+ A validator that raises a `TypeError` if the initializer is called
+ with a wrong type for this particular attribute (checks are performed using
+ `isinstance` therefore it's also valid to pass a tuple of types).
+
+ :param type: The type to check for.
+ """
+
+ def _validator(inst, field, value, suffix=""):
+ """
+ We use a callable class to be able to change the ``__repr__``.
+ """
+ if not isinstance(value, type):
+ raise TypeError(
+ f"'{field.name}{suffix}' must be of type {type!r} "
+ f"(got {value!r} that is a {value.__class__!r})."
+ )
+
+ return _validator
+
+
+def optional(validator: ValidatorType) -> ValidatorType:
+ """
+ A validator that makes an attribute optional. An optional attribute is one
+ which can be set to ``None`` in addition to satisfying the requirements of
+ the sub-validator.
+ """
+
+ def _validator(inst, field, value, suffix=""):
+ if value is None:
+ return
+
+ validator(inst, field, value, suffix=suffix)
+
+ return _validator
+
+
+def is_callable(inst, field, value, suffix=""):
+ """
+ A validator that raises a `TypeError` if the
+ initializer is called with a value for this particular attribute
+ that is not callable.
+ """
+ if not callable(value):
+ raise TypeError(
+ f"'{field.name}{suffix}' must be callable "
+ f"(got {value!r} that is a {value.__class__!r})."
+ )
+
+
+def in_(options: Sequence) -> ValidatorType:
+ """
+ A validator that raises a `ValueError` if the initializer is called
+ with a value that does not belong in the options provided. The check is
+ performed using ``value in options``.
+
+ :param options: Allowed options.
+ """
+
+ def _validator(inst, field, value, suffix=""):
+ try:
+ in_options = value in options
+ except TypeError: # e.g. `1 in "abc"`
+ in_options = False
+
+ if not in_options:
+ raise ValueError(
+ f"'{field.name}{suffix}' must be in {options!r} (got {value!r})"
+ )
+
+ return _validator
+
+
+def deep_iterable(
+ member_validator: ValidatorType, iterable_validator: ValidatorType | None = None
+) -> ValidatorType:
+ """
+ A validator that performs deep validation of an iterable.
+
+ :param member_validator: Validator to apply to iterable members
+ :param iterable_validator: Validator to apply to iterable itself
+ """
+
+ def _validator(inst, field, value, suffix=""):
+ if iterable_validator is not None:
+ iterable_validator(inst, field, value, suffix=suffix)
+
+ for idx, member in enumerate(value):
+ member_validator(inst, field, member, suffix=f"{suffix}[{idx}]")
+
+ return _validator
+
+
+def deep_mapping(
+ key_validator: ValidatorType,
+ value_validator: ValidatorType,
+ mapping_validator: ValidatorType | None = None,
+) -> ValidatorType:
+ """
+ A validator that performs deep validation of a dictionary.
+
+ :param key_validator: Validator to apply to dictionary keys
+ :param value_validator: Validator to apply to dictionary values
+ :param mapping_validator: Validator to apply to top-level mapping attribute (optional)
+ """
+
+ def _validator(inst, field: dc.Field, value, suffix=""):
+ if mapping_validator is not None:
+ mapping_validator(inst, field, value)
+
+ for key in value:
+ key_validator(inst, field, key, suffix=f"{suffix}[{key!r}]")
+ value_validator(inst, field, value[key], suffix=f"{suffix}[{key!r}]")
+
+ return _validator
diff --git a/myst_parser/config/main.py b/myst_parser/config/main.py
new file mode 100644
index 0000000..a134ea7
--- /dev/null
+++ b/myst_parser/config/main.py
@@ -0,0 +1,409 @@
+"""The configuration for the myst parser."""
+import dataclasses as dc
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ Optional,
+ Sequence,
+ Tuple,
+ Union,
+ cast,
+)
+
+from .dc_validators import (
+ deep_iterable,
+ deep_mapping,
+ in_,
+ instance_of,
+ is_callable,
+ optional,
+ validate_field,
+ validate_fields,
+)
+
+
+def check_extensions(_, __, value):
+ if not isinstance(value, Iterable):
+ raise TypeError(f"'enable_extensions' not iterable: {value}")
+ diff = set(value).difference(
+ [
+ "amsmath",
+ "attrs_image",
+ "colon_fence",
+ "deflist",
+ "dollarmath",
+ "fieldlist",
+ "html_admonition",
+ "html_image",
+ "linkify",
+ "replacements",
+ "smartquotes",
+ "strikethrough",
+ "substitution",
+ "tasklist",
+ ]
+ )
+ if diff:
+ raise ValueError(f"'enable_extensions' items not recognised: {diff}")
+
+
+def check_sub_delimiters(_, __, value):
+ if (not isinstance(value, (tuple, list))) or len(value) != 2:
+ raise TypeError(f"myst_sub_delimiters is not a tuple of length 2: {value}")
+ for delim in value:
+ if (not isinstance(delim, str)) or len(delim) != 1:
+ raise TypeError(
+ f"myst_sub_delimiters does not contain strings of length 1: {value}"
+ )
+
+
+@dc.dataclass()
+class MdParserConfig:
+ """Configuration options for the Markdown Parser.
+
+ Note in the sphinx configuration these option names are prepended with ``myst_``
+ """
+
+ # TODO replace commonmark_only, gfm_only with a single option
+
+ commonmark_only: bool = dc.field(
+ default=False,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Use strict CommonMark parser",
+ },
+ )
+ gfm_only: bool = dc.field(
+ default=False,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Use strict Github Flavoured Markdown parser",
+ },
+ )
+
+ enable_extensions: Sequence[str] = dc.field(
+ default_factory=list,
+ metadata={"validator": check_extensions, "help": "Enable syntax extensions"},
+ )
+
+ disable_syntax: Iterable[str] = dc.field(
+ default_factory=list,
+ metadata={
+ "validator": deep_iterable(instance_of(str), instance_of((list, tuple))),
+ "help": "Disable Commonmark syntax elements",
+ },
+ )
+
+ all_links_external: bool = dc.field(
+ default=False,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Parse all links as simple hyperlinks",
+ },
+ )
+
+ # see https://en.wikipedia.org/wiki/List_of_URI_schemes
+ url_schemes: Optional[Iterable[str]] = dc.field(
+ default=cast(Optional[Iterable[str]], ("http", "https", "mailto", "ftp")),
+ metadata={
+ "validator": optional(
+ deep_iterable(instance_of(str), instance_of((list, tuple)))
+ ),
+ "help": "URL scheme prefixes identified as external links",
+ },
+ )
+
+ ref_domains: Optional[Iterable[str]] = dc.field(
+ default=None,
+ metadata={
+ "validator": optional(
+ deep_iterable(instance_of(str), instance_of((list, tuple)))
+ ),
+ "help": "Sphinx domain names to search in for link references",
+ },
+ )
+
+ highlight_code_blocks: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Syntax highlight code blocks with pygments",
+ "docutils_only": True,
+ },
+ )
+
+ number_code_blocks: Sequence[str] = dc.field(
+ default_factory=list,
+ metadata={
+ "validator": deep_iterable(instance_of(str), instance_of((list, tuple))),
+ "help": "Add line numbers to code blocks with these languages",
+ },
+ )
+
+ title_to_header: bool = dc.field(
+ default=False,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Convert a `title` field in the top-matter to a H1 header",
+ },
+ )
+
+ heading_anchors: Optional[int] = dc.field(
+ default=None,
+ metadata={
+ "validator": optional(in_([1, 2, 3, 4, 5, 6, 7])),
+ "help": "Heading level depth to assign HTML anchors",
+ },
+ )
+
+ heading_slug_func: Optional[Callable[[str], str]] = dc.field(
+ default=None,
+ metadata={
+ "validator": optional(is_callable),
+ "help": "Function for creating heading anchors",
+ "global_only": True,
+ },
+ )
+
+ html_meta: Dict[str, str] = dc.field(
+ default_factory=dict,
+ repr=False,
+ metadata={
+ "validator": deep_mapping(
+ instance_of(str), instance_of(str), instance_of(dict)
+ ),
+ "merge_topmatter": True,
+ "help": "HTML meta tags",
+ },
+ )
+
+ footnote_transition: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Place a transition before any footnotes",
+ },
+ )
+
+ words_per_minute: int = dc.field(
+ default=200,
+ metadata={
+ "validator": instance_of(int),
+ "help": "For reading speed calculations",
+ },
+ )
+
+ # Extension specific
+
+ substitutions: Dict[str, Union[str, int, float]] = dc.field(
+ default_factory=dict,
+ repr=False,
+ metadata={
+ "validator": deep_mapping(
+ instance_of(str), instance_of((str, int, float)), instance_of(dict)
+ ),
+ "merge_topmatter": True,
+ "help": "Substitutions mapping",
+ "extension": "substitutions",
+ },
+ )
+
+ sub_delimiters: Tuple[str, str] = dc.field(
+ default=("{", "}"),
+ metadata={
+ "validator": check_sub_delimiters,
+ "help": "Substitution delimiters",
+ "extension": "substitutions",
+ },
+ )
+
+ linkify_fuzzy_links: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Recognise URLs without schema prefixes",
+ "extension": "linkify",
+ },
+ )
+
+ dmath_allow_labels: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Parse `$$...$$ (label)`",
+ "extension": "dollarmath",
+ },
+ )
+ dmath_allow_space: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Allow initial/final spaces in `$ ... $`",
+ "extension": "dollarmath",
+ },
+ )
+ dmath_allow_digits: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Allow initial/final digits `1$ ...$2`",
+ "extension": "dollarmath",
+ },
+ )
+ dmath_double_inline: bool = dc.field(
+ default=False,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Parse inline `$$ ... $$`",
+ "extension": "dollarmath",
+ },
+ )
+
+ update_mathjax: bool = dc.field(
+ default=True,
+ metadata={
+ "validator": instance_of(bool),
+ "help": "Update sphinx.ext.mathjax configuration to ignore `$` delimiters",
+ "extension": "dollarmath",
+ "global_only": True,
+ },
+ )
+
+ mathjax_classes: str = dc.field(
+ default="tex2jax_process|mathjax_process|math|output_area",
+ metadata={
+ "validator": instance_of(str),
+ "help": "MathJax classes to add to math HTML",
+ "extension": "dollarmath",
+ "global_only": True,
+ },
+ )
+
+ def __post_init__(self):
+ validate_fields(self)
+
+ def copy(self, **kwargs: Any) -> "MdParserConfig":
+ """Return a new object replacing specified fields with new values.
+
+ Note: initiating the copy will also validate the new fields.
+ """
+ return dc.replace(self, **kwargs)
+
+ @classmethod
+ def get_fields(cls) -> Tuple[dc.Field, ...]:
+ """Return all attribute fields in this class."""
+ return dc.fields(cls)
+
+ def as_dict(self, dict_factory=dict) -> dict:
+ """Return a dictionary of field name -> value."""
+ return dc.asdict(self, dict_factory=dict_factory)
+
+ def as_triple(self) -> Iterable[Tuple[str, Any, dc.Field]]:
+ """Yield triples of (name, value, field)."""
+ fields = {f.name: f for f in dc.fields(self.__class__)}
+ for name, value in dc.asdict(self).items():
+ yield name, value, fields[name]
+
+
+def merge_file_level(
+ config: MdParserConfig,
+ topmatter: Dict[str, Any],
+ warning: Callable[[str, str], None],
+) -> MdParserConfig:
+ """Merge the file-level topmatter with the global config.
+
+ :param config: Global config.
+ :param topmatter: Topmatter from the file.
+ :param warning: Function to call with a warning (type, message).
+ :returns: A new config object
+ """
+ # get updates
+ updates: Dict[str, Any] = {}
+ myst = topmatter.get("myst", {})
+ if not isinstance(myst, dict):
+ warning("topmatter", f"'myst' key not a dict: {type(myst)}")
+ else:
+ updates = myst
+
+ # allow html_meta and substitutions at top-level for back-compatibility
+ if "html_meta" in topmatter:
+ warning(
+ "topmatter",
+ "top-level 'html_meta' key is deprecated, "
+ "place under 'myst' key instead",
+ )
+ updates["html_meta"] = topmatter["html_meta"]
+ if "substitutions" in topmatter:
+ warning(
+ "topmatter",
+ "top-level 'substitutions' key is deprecated, "
+ "place under 'myst' key instead",
+ )
+ updates["substitutions"] = topmatter["substitutions"]
+
+ new = config.copy()
+
+ # validate each update
+ fields = {name: (value, field) for name, value, field in config.as_triple()}
+ for name, value in updates.items():
+
+ if name not in fields:
+ warning("topmatter", f"Unknown field: {name}")
+ continue
+
+ old_value, field = fields[name]
+
+ try:
+ validate_field(new, field, value)
+ except Exception as exc:
+ warning("topmatter", str(exc))
+ continue
+
+ if field.metadata.get("merge_topmatter"):
+ value = {**old_value, **value}
+
+ setattr(new, name, value)
+
+ return new
+
+
+class TopmatterReadError(Exception):
+ """Topmatter parsing error."""
+
+
+def read_topmatter(text: Union[str, Iterator[str]]) -> Optional[Dict[str, Any]]:
+ """Read the (optional) YAML topmatter from a source string.
+
+ This is identified by the first line starting with `---`,
+ then read up to a terminating line of `---`, or `...`.
+
+ :param source: The source string to read from
+ :return: The topmatter
+ """
+ import yaml
+
+ if isinstance(text, str):
+ if not text.startswith("---"): # skip creating the line list in memory
+ return None
+ text = (line for line in text.splitlines())
+ try:
+ if not next(text).startswith("---"):
+ return None
+ except StopIteration:
+ return None
+ top_matter = []
+ for line in text:
+ if line.startswith("---") or line.startswith("..."):
+ break
+ top_matter.append(line.rstrip() + "\n")
+ try:
+ metadata = yaml.safe_load("".join(top_matter))
+ assert isinstance(metadata, dict)
+ except (yaml.parser.ParserError, yaml.scanner.ScannerError) as err:
+ raise TopmatterReadError("Malformed YAML") from err
+ if not isinstance(metadata, dict):
+ raise TopmatterReadError(f"YAML is not a dict: {type(metadata)}")
+ return metadata
diff --git a/myst_parser/docutils_.py b/myst_parser/docutils_.py
new file mode 100644
index 0000000..6f2cc84
--- /dev/null
+++ b/myst_parser/docutils_.py
@@ -0,0 +1,6 @@
+"""A module for compatibility with the docutils>=0.17 `include` directive, in RST documents::
+
+ .. include:: path/to/file.md
+ :parser: myst_parser.docutils_
+"""
+from myst_parser.parsers.docutils_ import Parser # noqa: F401
diff --git a/myst_parser/mdit_to_docutils/__init__.py b/myst_parser/mdit_to_docutils/__init__.py
new file mode 100644
index 0000000..0b9307f
--- /dev/null
+++ b/myst_parser/mdit_to_docutils/__init__.py
@@ -0,0 +1 @@
+"""Conversion of Markdown-it tokens to docutils AST."""
diff --git a/myst_parser/mdit_to_docutils/base.py b/myst_parser/mdit_to_docutils/base.py
new file mode 100644
index 0000000..cedd6c3
--- /dev/null
+++ b/myst_parser/mdit_to_docutils/base.py
@@ -0,0 +1,1483 @@
+"""Convert Markdown-it tokens to docutils nodes."""
+from __future__ import annotations
+
+import inspect
+import json
+import os
+import re
+from collections import OrderedDict
+from contextlib import contextmanager
+from datetime import date, datetime
+from types import ModuleType
+from typing import TYPE_CHECKING, Any, Iterator, MutableMapping, Sequence, cast
+from urllib.parse import urlparse
+
+import jinja2
+import yaml
+from docutils import nodes
+from docutils.frontend import OptionParser
+from docutils.languages import get_language
+from docutils.parsers.rst import Directive, DirectiveError
+from docutils.parsers.rst import Parser as RSTParser
+from docutils.parsers.rst import directives, roles
+from docutils.parsers.rst.directives.misc import Include
+from docutils.parsers.rst.languages import get_language as get_language_rst
+from docutils.statemachine import StringList
+from docutils.transforms.components import Filter
+from docutils.utils import Reporter, new_document
+from docutils.utils.code_analyzer import Lexer, LexerError, NumberLines
+from markdown_it import MarkdownIt
+from markdown_it.common.utils import escapeHtml
+from markdown_it.renderer import RendererProtocol
+from markdown_it.token import Token
+from markdown_it.tree import SyntaxTreeNode
+
+from myst_parser._compat import findall
+from myst_parser.config.main import MdParserConfig
+from myst_parser.mocking import (
+ MockIncludeDirective,
+ MockingError,
+ MockInliner,
+ MockRSTParser,
+ MockState,
+ MockStateMachine,
+)
+from myst_parser.parsers.directives import DirectiveParsingError, parse_directive_text
+from .html_to_nodes import html_to_nodes
+from .utils import is_external_url
+
+if TYPE_CHECKING:
+ from sphinx.environment import BuildEnvironment
+
+
+def make_document(source_path="notset", parser_cls=RSTParser) -> nodes.document:
+ """Create a new docutils document, with the parser classes' default settings."""
+ settings = OptionParser(components=(parser_cls,)).get_default_values()
+ return new_document(source_path, settings=settings)
+
+
+REGEX_DIRECTIVE_START = re.compile(r"^[\s]{0,3}([`]{3,10}|[~]{3,10}|[:]{3,10})\{")
+
+
+def token_line(token: SyntaxTreeNode, default: int | None = None) -> int:
+ """Retrieve the initial line of a token."""
+ if not getattr(token, "map", None):
+ if default is not None:
+ return default
+ raise ValueError(f"token map not set: {token}")
+ return token.map[0] # type: ignore[index]
+
+
+def create_warning(
+ document: nodes.document,
+ message: str,
+ *,
+ line: int | None = None,
+ append_to: nodes.Element | None = None,
+ wtype: str = "myst",
+ subtype: str = "other",
+) -> nodes.system_message | None:
+ """Generate a warning, logging if it is necessary.
+
+ Note this is overridden in the ``SphinxRenderer``,
+ to handle suppressed warning types.
+ """
+ kwargs = {"line": line} if line is not None else {}
+ msg_node = document.reporter.warning(f"{message} [{wtype}.{subtype}]", **kwargs)
+ if append_to is not None:
+ append_to.append(msg_node)
+ return msg_node
+
+
+class DocutilsRenderer(RendererProtocol):
+ """A markdown-it-py renderer to populate (in-place) a `docutils.document` AST.
+
+ Note, this render is not dependent on Sphinx.
+ """
+
+ __output__ = "docutils"
+
+ def __init__(self, parser: MarkdownIt) -> None:
+ """Load the renderer (called by ``MarkdownIt``)"""
+ self.md = parser
+ self.rules = {
+ k: v
+ for k, v in inspect.getmembers(self, predicate=inspect.ismethod)
+ if k.startswith("render_") and k != "render_children"
+ }
+
+ def __getattr__(self, name: str):
+ """Warn when the renderer has not been setup yet."""
+ if name in (
+ "md_env",
+ "md_config",
+ "md_options",
+ "document",
+ "current_node",
+ "reporter",
+ "language_module_rst",
+ "_level_to_elem",
+ ):
+ raise AttributeError(
+ f"'{name}' attribute is not available until setup_render() is called"
+ )
+ raise AttributeError(
+ f"'{type(self).__name__}' object has no attribute '{name}'"
+ )
+
+ def setup_render(
+ self, options: dict[str, Any], env: MutableMapping[str, Any]
+ ) -> None:
+ """Setup the renderer with per render variables."""
+ self.md_env = env
+ self.md_options = options
+ self.md_config: MdParserConfig = options["myst_config"]
+ self.document: nodes.document = options.get("document", make_document())
+ self.current_node: nodes.Element = options.get("current_node", self.document)
+ self.reporter: Reporter = self.document.reporter
+ # note there are actually two possible language modules:
+ # one from docutils.languages, and one from docutils.parsers.rst.languages
+ self.language_module_rst: ModuleType = get_language_rst(
+ self.document.settings.language_code
+ )
+ # a mapping of heading levels to its currently associated node
+ self._level_to_elem: dict[int, nodes.document | nodes.section] = {
+ 0: self.document
+ }
+
+ @property
+ def sphinx_env(self) -> BuildEnvironment | None:
+ """Return the sphinx env, if using Sphinx."""
+ try:
+ return self.document.settings.env
+ except AttributeError:
+ return None
+
+ def create_warning(
+ self,
+ message: str,
+ *,
+ line: int | None = None,
+ append_to: nodes.Element | None = None,
+ wtype: str = "myst",
+ subtype: str = "other",
+ ) -> nodes.system_message | None:
+ """Generate a warning, logging if it is necessary.
+
+ Note this is overridden in the ``SphinxRenderer``,
+ to handle suppressed warning types.
+ """
+ return create_warning(
+ self.document,
+ message,
+ line=line,
+ append_to=append_to,
+ wtype=wtype,
+ subtype=subtype,
+ )
+
+ def _render_tokens(self, tokens: list[Token]) -> None:
+ """Render the tokens."""
+ # propagate line number down to inline elements
+ for token in tokens:
+ if not token.map:
+ continue
+ # For docutils we want 1 based line numbers (not 0)
+ token.map = [token.map[0] + 1, token.map[1] + 1]
+ for token_child in token.children or []:
+ token_child.map = token.map
+
+ # nest tokens
+ node_tree = SyntaxTreeNode(tokens)
+
+ # move footnote definitions to env
+ self.md_env.setdefault("foot_refs", {})
+ for node in node_tree.walk(include_self=True):
+ new_children = []
+ for child in node.children:
+ if child.type == "footnote_reference":
+ label = child.meta["label"]
+ self.md_env["foot_refs"].setdefault(label, []).append(child)
+ else:
+ new_children.append(child)
+
+ node.children = new_children
+
+ # render
+ for child in node_tree.children:
+ # skip hidden?
+ if f"render_{child.type}" in self.rules:
+ self.rules[f"render_{child.type}"](child)
+ else:
+ self.create_warning(
+ f"No render method for: {child.type}",
+ line=token_line(child, default=0),
+ subtype="render",
+ append_to=self.current_node,
+ )
+
+ def render(
+ self, tokens: Sequence[Token], options, md_env: MutableMapping[str, Any]
+ ) -> nodes.document:
+ """Run the render on a token stream.
+
+ :param tokens: list on block tokens to render
+ :param options: params of parser instance
+ :param md_env: the markdown-it environment sandbox associated with the tokens,
+ containing additional metadata like reference info
+ """
+ self.setup_render(options, md_env)
+ self._render_initialise()
+ self._render_tokens(list(tokens))
+ self._render_finalise()
+ return self.document
+
+ def _render_initialise(self) -> None:
+ """Initialise the render of the document."""
+ self.current_node.extend(
+ html_meta_to_nodes(
+ self.md_config.html_meta,
+ document=self.document,
+ line=0,
+ reporter=self.reporter,
+ )
+ )
+
+ def _render_finalise(self) -> None:
+ """Finalise the render of the document."""
+
+ # log warnings for duplicate reference definitions
+ # "duplicate_refs": [{"href": "ijk", "label": "B", "map": [4, 5], "title": ""}],
+ for dup_ref in self.md_env.get("duplicate_refs", []):
+ self.create_warning(
+ f"Duplicate reference definition: {dup_ref['label']}",
+ line=dup_ref["map"][0] + 1,
+ subtype="ref",
+ append_to=self.document,
+ )
+
+ # we don't use the foot_references stored in the env
+ # since references within directives/roles will have been added after
+ # those from the initial markdown parse
+ # instead we gather them from a walk of the created document
+ foot_refs = OrderedDict()
+ for refnode in findall(self.document)(nodes.footnote_reference):
+ if refnode["refname"] not in foot_refs:
+ foot_refs[refnode["refname"]] = True
+
+ if foot_refs and self.md_config.footnote_transition:
+ self.current_node.append(nodes.transition(classes=["footnotes"]))
+ for footref in foot_refs:
+ foot_ref_tokens = self.md_env["foot_refs"].get(footref, [])
+ if len(foot_ref_tokens) > 1:
+ self.create_warning(
+ f"Multiple footnote definitions found for label: '{footref}'",
+ subtype="footnote",
+ append_to=self.current_node,
+ )
+
+ if len(foot_ref_tokens) < 1:
+ self.create_warning(
+ f"No footnote definitions found for label: '{footref}'",
+ subtype="footnote",
+ append_to=self.current_node,
+ )
+ else:
+ self.render_footnote_reference(foot_ref_tokens[0])
+
+ # Add the wordcount, generated by the ``mdit_py_plugins.wordcount_plugin``.
+ wordcount_metadata = self.md_env.get("wordcount", {})
+ if wordcount_metadata:
+
+ # save the wordcount to the sphinx BuildEnvironment metadata
+ if self.sphinx_env is not None:
+ meta = self.sphinx_env.metadata.setdefault(self.sphinx_env.docname, {})
+ meta["wordcount"] = wordcount_metadata
+
+ # now add the wordcount as substitution definitions,
+ # so we can reference them in the document
+ for key in ("words", "minutes"):
+ value = wordcount_metadata.get(key, None)
+ if value is None:
+ continue
+ substitution_node = nodes.substitution_definition(
+ str(value), nodes.Text(str(value))
+ )
+ substitution_node.source = self.document["source"]
+ substitution_node["names"].append(f"wordcount-{key}")
+ self.document.note_substitution_def(
+ substitution_node, f"wordcount-{key}"
+ )
+
+ def nested_render_text(
+ self, text: str, lineno: int, inline: bool = False, allow_headings: bool = True
+ ) -> None:
+ """Render unparsed text (appending to the current node).
+
+ :param text: the text to render
+ :param lineno: the starting line number of the text, within the full source
+ :param inline: whether the text is inline or block
+ :param allow_headings: whether to allow headings in the text
+ """
+ if inline:
+ tokens = self.md.parseInline(text, self.md_env)
+ else:
+ tokens = self.md.parse(text + "\n", self.md_env)
+
+ # remove front matter, if present, e.g. from included documents
+ if tokens and tokens[0].type == "front_matter":
+ tokens.pop(0)
+
+ # update the line numbers
+ for token in tokens:
+ if token.map:
+ token.map = [token.map[0] + lineno, token.map[1] + lineno]
+
+ current_match_titles = self.md_env.get("match_titles", None)
+ try:
+ self.md_env["match_titles"] = allow_headings
+ self._render_tokens(tokens)
+ finally:
+ self.md_env["match_titles"] = current_match_titles
+
+ @contextmanager
+ def current_node_context(
+ self, node: nodes.Element, append: bool = False
+ ) -> Iterator:
+ """Context manager for temporarily setting the current node."""
+ if append:
+ self.current_node.append(node)
+ current_node = self.current_node
+ self.current_node = node
+ yield
+ self.current_node = current_node
+
+ def render_children(self, token: SyntaxTreeNode) -> None:
+ """Render the children of a token."""
+ for child in token.children or []:
+ if f"render_{child.type}" in self.rules:
+ self.rules[f"render_{child.type}"](child)
+ else:
+ self.create_warning(
+ f"No render method for: {child.type}",
+ line=token_line(child, default=0),
+ subtype="render",
+ append_to=self.current_node,
+ )
+
+ def add_line_and_source_path(self, node, token: SyntaxTreeNode) -> None:
+ """Copy the line number and document source path to the docutils node."""
+ try:
+ node.line = token_line(token)
+ except ValueError:
+ pass
+ node.source = self.document["source"]
+
+ def add_line_and_source_path_r(
+ self, nodes: list[nodes.Element], token: SyntaxTreeNode
+ ) -> None:
+ """Copy the line number and document source path to the docutils nodes,
+ and recursively to all descendants.
+ """
+ for node in nodes:
+ self.add_line_and_source_path(node, token)
+ for child in findall(node)():
+ self.add_line_and_source_path(child, token)
+
+ def update_section_level_state(self, section: nodes.section, level: int) -> None:
+ """Update the section level state, with the new current section and level."""
+ # find the closest parent section
+ parent_level = max(
+ section_level
+ for section_level in self._level_to_elem
+ if level > section_level
+ )
+ parent = self._level_to_elem[parent_level]
+
+ # if we are jumping up to a non-consecutive level,
+ # then warn about this, since this will not be propagated in the docutils AST
+ if (level > parent_level) and (parent_level + 1 != level):
+ msg = f"Non-consecutive header level increase; H{parent_level} to H{level}"
+ if parent_level == 0:
+ msg = f"Document headings start at H{level}, not H1"
+ self.create_warning(
+ msg,
+ line=section.line,
+ subtype="header",
+ append_to=self.current_node,
+ )
+
+ # append the new section to the parent
+ parent.append(section)
+ # update the state for this section level
+ self._level_to_elem[level] = section
+
+ # Remove all descendant sections from the section level state
+ self._level_to_elem = {
+ section_level: section
+ for section_level, section in self._level_to_elem.items()
+ if section_level <= level
+ }
+
+ def renderInlineAsText(self, tokens: list[SyntaxTreeNode]) -> str:
+ """Special kludge for image `alt` attributes to conform CommonMark spec.
+
+ Don't try to use it! Spec requires to show `alt` content with stripped markup,
+ instead of simple escaping.
+ """
+ result = ""
+
+ for token in tokens or []:
+ if token.type == "text":
+ result += token.content
+ # elif token.type == "image":
+ # result += self.renderInlineAsText(token.children)
+ else:
+ result += self.renderInlineAsText(token.children or [])
+ return result
+
+ # ### render methods for commonmark tokens
+
+ def render_paragraph(self, token: SyntaxTreeNode) -> None:
+ para = nodes.paragraph(token.children[0].content if token.children else "")
+ self.add_line_and_source_path(para, token)
+ with self.current_node_context(para, append=True):
+ self.render_children(token)
+
+ def render_inline(self, token: SyntaxTreeNode) -> None:
+ self.render_children(token)
+
+ def render_text(self, token: SyntaxTreeNode) -> None:
+ self.current_node.append(nodes.Text(token.content))
+
+ def render_bullet_list(self, token: SyntaxTreeNode) -> None:
+ list_node = nodes.bullet_list()
+ if token.markup:
+ list_node["bullet"] = token.markup
+ if token.attrs.get("class"):
+ # this is used e.g. by tasklist
+ list_node["classes"] = str(token.attrs["class"]).split()
+ self.add_line_and_source_path(list_node, token)
+ with self.current_node_context(list_node, append=True):
+ self.render_children(token)
+
+ def render_ordered_list(self, token: SyntaxTreeNode) -> None:
+ list_node = nodes.enumerated_list(enumtype="arabic", prefix="")
+ list_node["suffix"] = token.markup # for CommonMark, this should be "." or ")"
+ if "start" in token.attrs: # starting number
+ list_node["start"] = token.attrs["start"]
+ self.add_line_and_source_path(list_node, token)
+ with self.current_node_context(list_node, append=True):
+ self.render_children(token)
+
+ def render_list_item(self, token: SyntaxTreeNode) -> None:
+ item_node = nodes.list_item()
+ if token.attrs.get("class"):
+ # this is used e.g. by tasklist
+ item_node["classes"] = str(token.attrs["class"]).split()
+ self.add_line_and_source_path(item_node, token)
+ with self.current_node_context(item_node, append=True):
+ self.render_children(token)
+
+ def render_em(self, token: SyntaxTreeNode) -> None:
+ node = nodes.emphasis()
+ self.add_line_and_source_path(node, token)
+ with self.current_node_context(node, append=True):
+ self.render_children(token)
+
+ def render_softbreak(self, token: SyntaxTreeNode) -> None:
+ self.current_node.append(nodes.Text("\n"))
+
+ def render_hardbreak(self, token: SyntaxTreeNode) -> None:
+ self.current_node.append(nodes.raw("", "<br />\n", format="html"))
+ self.current_node.append(nodes.raw("", "\\\\\n", format="latex"))
+
+ def render_strong(self, token: SyntaxTreeNode) -> None:
+ node = nodes.strong()
+ self.add_line_and_source_path(node, token)
+ with self.current_node_context(node, append=True):
+ self.render_children(token)
+
+ def render_blockquote(self, token: SyntaxTreeNode) -> None:
+ quote = nodes.block_quote()
+ self.add_line_and_source_path(quote, token)
+ with self.current_node_context(quote, append=True):
+ self.render_children(token)
+
+ def render_hr(self, token: SyntaxTreeNode) -> None:
+ node = nodes.transition()
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def render_code_inline(self, token: SyntaxTreeNode) -> None:
+ node = nodes.literal(token.content, token.content)
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def create_highlighted_code_block(
+ self,
+ text: str,
+ lexer_name: str | None,
+ number_lines: bool = False,
+ lineno_start: int = 1,
+ source: str | None = None,
+ line: int | None = None,
+ node_cls: type[nodes.Element] = nodes.literal_block,
+ ) -> nodes.Element:
+ """Create a literal block with syntax highlighting.
+
+ This mimics the behaviour of the `code-block` directive.
+
+ In docutils, this directive directly parses the text with the pygments lexer,
+ whereas in sphinx, the lexer name is only recorded as the `language` attribute,
+ and the text is lexed later by pygments within the `visit_literal_block`
+ method of the output format ``SphinxTranslator``.
+
+ Note, this function does not add the literal block to the document.
+ """
+ if self.sphinx_env is not None:
+ node = node_cls(text, text, language=lexer_name or "none")
+ if number_lines:
+ node["linenos"] = True
+ if lineno_start != 1:
+ node["highlight_args"] = {"linenostart": lineno_start}
+ else:
+ node = node_cls(
+ text, classes=["code"] + ([lexer_name] if lexer_name else [])
+ )
+ try:
+ lex_tokens = Lexer(
+ text,
+ lexer_name or "",
+ "short" if self.md_config.highlight_code_blocks else "none",
+ )
+ except LexerError as err:
+ self.reporter.warning(
+ str(err),
+ **{
+ name: value
+ for name, value in (("source", source), ("line", line))
+ if value is not None
+ },
+ )
+ lex_tokens = Lexer(text, lexer_name or "", "none")
+
+ if number_lines:
+ lex_tokens = NumberLines(
+ lex_tokens, lineno_start, lineno_start + len(text.splitlines())
+ )
+
+ for classes, value in lex_tokens:
+ if classes:
+ node += nodes.inline(value, value, classes=classes)
+ else:
+ # insert as Text to decrease the verbosity of the output
+ node += nodes.Text(value)
+
+ if source is not None:
+ node.source = source
+ if line is not None:
+ node.line = line
+ return node
+
+ def render_code_block(self, token: SyntaxTreeNode) -> None:
+ lexer = token.info.split()[0] if token.info else None
+ node = self.create_highlighted_code_block(
+ token.content,
+ lexer,
+ source=self.document["source"],
+ line=token_line(token, 0) or None,
+ )
+ self.current_node.append(node)
+
+ def render_fence(self, token: SyntaxTreeNode) -> None:
+ text = token.content
+ # Ensure that we'll have an empty string if info exists but is only spaces
+ info = token.info.strip() if token.info else token.info
+ language = info.split()[0] if info else ""
+
+ if (not self.md_config.commonmark_only) and (not self.md_config.gfm_only):
+ if language == "{eval-rst}":
+ return self.render_restructuredtext(token)
+ if language.startswith("{") and language.endswith("}"):
+ return self.render_directive(token)
+
+ if not language and self.sphinx_env is not None:
+ # use the current highlight setting, via the ``highlight`` directive,
+ # or ``highlight_language`` configuration.
+ language = self.sphinx_env.temp_data.get(
+ "highlight_language", self.sphinx_env.config.highlight_language
+ )
+
+ node = self.create_highlighted_code_block(
+ text,
+ language,
+ number_lines=language in self.md_config.number_code_blocks,
+ source=self.document["source"],
+ line=token_line(token, 0) or None,
+ )
+ self.current_node.append(node)
+
+ @property
+ def blocks_mathjax_processing(self) -> bool:
+ """Only add mathjax ignore classes if using sphinx,
+ and using the ``dollarmath`` extension, and ``myst_update_mathjax=True``.
+ """
+ return (
+ self.sphinx_env is not None
+ and "dollarmath" in self.md_config.enable_extensions
+ and self.md_config.update_mathjax
+ )
+
+ def render_heading(self, token: SyntaxTreeNode) -> None:
+ """Render a heading, e.g. `# Heading`."""
+
+ if self.md_env.get("match_titles", None) is False:
+ # this can occur if a nested parse is performed by a directive
+ # (such as an admonition) which contains a header.
+ # this would break the document structure
+ self.create_warning(
+ "Disallowed nested header found, converting to rubric",
+ line=token_line(token, default=0),
+ subtype="nested_header",
+ append_to=self.current_node,
+ )
+ rubric = nodes.rubric(token.content, "")
+ self.add_line_and_source_path(rubric, token)
+ with self.current_node_context(rubric, append=True):
+ self.render_children(token)
+ return
+
+ level = int(token.tag[1])
+
+ # create the section node
+ new_section = nodes.section()
+ self.add_line_and_source_path(new_section, token)
+ # if a top level section,
+ # then add classes to set default mathjax processing to false
+ # we then turn it back on, on a per-node basis
+ if level == 1 and self.blocks_mathjax_processing:
+ new_section["classes"].extend(["tex2jax_ignore", "mathjax_ignore"])
+
+ # update the state of the section levels
+ self.update_section_level_state(new_section, level)
+
+ # create the title for this section
+ title_node = nodes.title(token.children[0].content if token.children else "")
+ self.add_line_and_source_path(title_node, token)
+ new_section.append(title_node)
+ # render the heading children into the title
+ with self.current_node_context(title_node):
+ self.render_children(token)
+
+ # create a target reference for the section, based on the heading text
+ name = nodes.fully_normalize_name(title_node.astext())
+ new_section["names"].append(name)
+ self.document.note_implicit_target(new_section, new_section)
+
+ # set the section as the current node for subsequent rendering
+ self.current_node = new_section
+
+ def render_link(self, token: SyntaxTreeNode) -> None:
+ """Parse `<http://link.com>` or `[text](link "title")` syntax to docutils AST:
+
+ - If `<>` autolink, forward to `render_autolink`
+ - If `myst_all_links_external` is True, forward to `render_external_url`
+ - If link is an external URL, forward to `render_external_url`
+ - External URLs start with a scheme (e.g. `http:`) in `myst_url_schemes`,
+ or any scheme if `myst_url_schemes` is None.
+ - Otherwise, forward to `render_internal_link`
+ """
+ if token.info == "auto": # handles both autolink and linkify
+ return self.render_autolink(token)
+
+ if (
+ self.md_config.commonmark_only
+ or self.md_config.gfm_only
+ or self.md_config.all_links_external
+ ):
+ return self.render_external_url(token)
+
+ # Check for external URL
+ url_scheme = urlparse(cast(str, token.attrGet("href") or "")).scheme
+ allowed_url_schemes = self.md_config.url_schemes
+ if (allowed_url_schemes is None and url_scheme) or (
+ allowed_url_schemes is not None and url_scheme in allowed_url_schemes
+ ):
+ return self.render_external_url(token)
+
+ return self.render_internal_link(token)
+
+ def render_external_url(self, token: SyntaxTreeNode) -> None:
+ """Render link token `[text](link "title")`,
+ where the link has been identified as an external URL::
+
+ <reference refuri="link" title="title">
+ text
+
+ `text` can contain nested syntax, e.g. `[**bold**](url "title")`.
+ """
+ ref_node = nodes.reference()
+ self.add_line_and_source_path(ref_node, token)
+ ref_node["refuri"] = cast(str, token.attrGet("href") or "")
+ title = token.attrGet("title")
+ if title:
+ ref_node["title"] = title
+ with self.current_node_context(ref_node, append=True):
+ self.render_children(token)
+
+ def render_internal_link(self, token: SyntaxTreeNode) -> None:
+ """Render link token `[text](link "title")`,
+ where the link has not been identified as an external URL::
+
+ <reference refname="link" title="title">
+ text
+
+ `text` can contain nested syntax, e.g. `[**bold**](link "title")`.
+
+ Note, this is overridden by `SphinxRenderer`, to use `pending_xref` nodes.
+ """
+ ref_node = nodes.reference()
+ self.add_line_and_source_path(ref_node, token)
+ ref_node["refname"] = cast(str, token.attrGet("href") or "")
+ self.document.note_refname(ref_node)
+ title = token.attrGet("title")
+ if title:
+ ref_node["title"] = title
+ with self.current_node_context(ref_node, append=True):
+ self.render_children(token)
+
+ def render_autolink(self, token: SyntaxTreeNode) -> None:
+ refuri = escapeHtml(token.attrGet("href") or "") # type: ignore[arg-type]
+ ref_node = nodes.reference()
+ ref_node["refuri"] = refuri
+ self.add_line_and_source_path(ref_node, token)
+ with self.current_node_context(ref_node, append=True):
+ self.render_children(token)
+
+ def render_html_inline(self, token: SyntaxTreeNode) -> None:
+ self.render_html_block(token)
+
+ def render_html_block(self, token: SyntaxTreeNode) -> None:
+ node_list = html_to_nodes(token.content, token_line(token), self)
+ self.current_node.extend(node_list)
+
+ def render_image(self, token: SyntaxTreeNode) -> None:
+ img_node = nodes.image()
+ self.add_line_and_source_path(img_node, token)
+ destination = cast(str, token.attrGet("src") or "")
+
+ if self.md_env.get("relative-images", None) is not None and not is_external_url(
+ destination, None, True
+ ):
+ # make the path relative to an "including" document
+ # this is set when using the `relative-images` option of the MyST `include` directive
+ destination = os.path.normpath(
+ os.path.join(
+ self.md_env.get("relative-images", ""),
+ os.path.normpath(destination),
+ )
+ )
+
+ img_node["uri"] = destination
+
+ img_node["alt"] = self.renderInlineAsText(token.children or [])
+ title = token.attrGet("title")
+ if title:
+ img_node["title"] = token.attrGet("title")
+
+ # apply other attributes that can be set on the image
+ if "class" in token.attrs:
+ img_node["classes"].extend(str(token.attrs["class"]).split())
+ if "width" in token.attrs:
+ try:
+ width = directives.length_or_percentage_or_unitless(
+ str(token.attrs["width"])
+ )
+ except ValueError:
+ self.create_warning(
+ f"Invalid width value for image: {token.attrs['width']!r}",
+ line=token_line(token, default=0),
+ subtype="image",
+ append_to=self.current_node,
+ )
+ else:
+ img_node["width"] = width
+ if "height" in token.attrs:
+ try:
+ height = directives.length_or_unitless(str(token.attrs["height"]))
+ except ValueError:
+ self.create_warning(
+ f"Invalid height value for image: {token.attrs['height']!r}",
+ line=token_line(token, default=0),
+ subtype="image",
+ append_to=self.current_node,
+ )
+ else:
+ img_node["height"] = height
+ if "align" in token.attrs:
+ if token.attrs["align"] not in ("left", "center", "right"):
+ self.create_warning(
+ f"Invalid align value for image: {token.attrs['align']!r}",
+ line=token_line(token, default=0),
+ subtype="image",
+ append_to=self.current_node,
+ )
+ else:
+ img_node["align"] = token.attrs["align"]
+ if "id" in token.attrs:
+ name = nodes.fully_normalize_name(str(token.attrs["id"]))
+ img_node["names"].append(name)
+ self.document.note_explicit_target(img_node, img_node)
+
+ self.current_node.append(img_node)
+
+ # ### render methods for plugin tokens
+
+ def render_front_matter(self, token: SyntaxTreeNode) -> None:
+ """Pass document front matter data."""
+ position = token_line(token, default=0)
+
+ if isinstance(token.content, str):
+ try:
+ data = yaml.safe_load(token.content)
+ except (yaml.parser.ParserError, yaml.scanner.ScannerError):
+ self.create_warning(
+ "Malformed YAML",
+ line=position,
+ append_to=self.current_node,
+ subtype="topmatter",
+ )
+ return
+ else:
+ data = token.content
+
+ if not isinstance(data, dict):
+ self.create_warning(
+ f"YAML is not a dict: {type(data)}",
+ line=position,
+ append_to=self.current_node,
+ subtype="topmatter",
+ )
+ return
+
+ fields = {
+ k: v
+ for k, v in data.items()
+ if k not in ("myst", "mystnb", "substitutions", "html_meta")
+ }
+ if fields:
+ field_list = self.dict_to_fm_field_list(
+ fields, language_code=self.document.settings.language_code
+ )
+ self.current_node.append(field_list)
+
+ if data.get("title") and self.md_config.title_to_header:
+ self.nested_render_text(f"# {data['title']}", 0)
+
+ def dict_to_fm_field_list(
+ self, data: dict[str, Any], language_code: str, line: int = 0
+ ) -> nodes.field_list:
+ """Render each key/val pair as a docutils ``field_node``.
+
+ Bibliographic keys below will be parsed as Markdown,
+ all others will be left as literal text.
+
+ The field list should be at the start of the document,
+ and will then be converted to a `docinfo` node during the
+ `docutils.docutils.transforms.frontmatter.DocInfo` transform (priority 340),
+ and bibliographic keys (or their translation) will be converted to nodes::
+
+ {'author': docutils.nodes.author,
+ 'authors': docutils.nodes.authors,
+ 'organization': docutils.nodes.organization,
+ 'address': docutils.nodes.address,
+ 'contact': docutils.nodes.contact,
+ 'version': docutils.nodes.version,
+ 'revision': docutils.nodes.revision,
+ 'status': docutils.nodes.status,
+ 'date': docutils.nodes.date,
+ 'copyright': docutils.nodes.copyright,
+ 'dedication': docutils.nodes.topic,
+ 'abstract': docutils.nodes.topic}
+
+ Also, the 'dedication' and 'abstract' will be placed outside the `docinfo`,
+ and so will always be shown in the document.
+
+ If using sphinx, this `docinfo` node will later be extracted from the AST,
+ by the `DoctreeReadEvent` transform (priority 880),
+ calling `MetadataCollector.process_doc`.
+ In this case keys and values will be converted to strings and stored in
+ `app.env.metadata[app.env.docname]`
+
+ See
+ https://www.sphinx-doc.org/en/master/usage/restructuredtext/field-lists.html
+ for docinfo fields used by sphinx.
+
+ """
+ field_list = nodes.field_list()
+ field_list.source, field_list.line = self.document["source"], line
+
+ bibliofields = get_language(language_code).bibliographic_fields
+
+ for key, value in data.items():
+ if not isinstance(value, (str, int, float, date, datetime)):
+ value = json.dumps(value)
+ value = str(value)
+ body = nodes.paragraph()
+ body.source, body.line = self.document["source"], line
+ if key in bibliofields:
+ with self.current_node_context(body):
+ self.nested_render_text(value, line, inline=True)
+ else:
+ body += nodes.literal(value, value)
+
+ field_node = nodes.field()
+ field_node.source = value
+ field_node += nodes.field_name(key, "", nodes.Text(key))
+ field_node += nodes.field_body(value, *[body])
+ field_list += field_node
+
+ return field_list
+
+ def render_table(self, token: SyntaxTreeNode) -> None:
+
+ # markdown-it table always contains at least a header:
+ assert token.children
+ header = token.children[0]
+ # with one header row
+ assert header.children
+ header_row = header.children[0]
+ assert header_row.children
+
+ # top-level element
+ table = nodes.table()
+ table["classes"] += ["colwidths-auto"]
+ self.add_line_and_source_path(table, token)
+ self.current_node.append(table)
+
+ # column settings element
+ maxcols = len(header_row.children)
+ colwidths = [100 // maxcols] * maxcols
+ tgroup = nodes.tgroup(cols=len(colwidths))
+ table += tgroup
+ for colwidth in colwidths:
+ colspec = nodes.colspec(colwidth=colwidth)
+ tgroup += colspec
+
+ # header
+ thead = nodes.thead()
+ tgroup += thead
+ with self.current_node_context(thead):
+ self.render_table_row(header_row)
+
+ # body
+ if len(token.children) > 1:
+ body = token.children[1]
+ tbody = nodes.tbody()
+ tgroup += tbody
+ with self.current_node_context(tbody):
+ for body_row in body.children or []:
+ self.render_table_row(body_row)
+
+ def render_table_row(self, token: SyntaxTreeNode) -> None:
+ row = nodes.row()
+ with self.current_node_context(row, append=True):
+ for child in token.children or []:
+ entry = nodes.entry()
+ para = nodes.paragraph(
+ child.children[0].content if child.children else ""
+ )
+ style = child.attrGet("style") # i.e. the alignment when using e.g. :--
+ if style and style in (
+ "text-align:left",
+ "text-align:right",
+ "text-align:center",
+ ):
+ entry["classes"].append(f"text-{cast(str, style).split(':')[1]}")
+ with self.current_node_context(entry, append=True):
+ with self.current_node_context(para, append=True):
+ self.render_children(child)
+
+ def render_s(self, token: SyntaxTreeNode) -> None:
+ """Render a strikethrough token."""
+ # TODO strikethrough not currently directly supported in docutils
+ self.create_warning(
+ "Strikethrough is currently only supported in HTML output",
+ line=token_line(token, 0),
+ subtype="strikethrough",
+ append_to=self.current_node,
+ )
+ self.current_node.append(nodes.raw("", "<s>", format="html"))
+ self.render_children(token)
+ self.current_node.append(nodes.raw("", "</s>", format="html"))
+
+ def render_math_inline(self, token: SyntaxTreeNode) -> None:
+ content = token.content
+ node = nodes.math(content, content)
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def render_math_inline_double(self, token: SyntaxTreeNode) -> None:
+ content = token.content
+ node = nodes.math_block(content, content, nowrap=False, number=None)
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def render_math_single(self, token: SyntaxTreeNode) -> None:
+ content = token.content
+ node = nodes.math(content, content)
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def render_math_block(self, token: SyntaxTreeNode) -> None:
+ content = token.content
+ node = nodes.math_block(content, content, nowrap=False, number=None)
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def render_amsmath(self, token: SyntaxTreeNode) -> None:
+ # note docutils does not currently support the nowrap attribute
+ # or equation numbering, so this is overridden in the sphinx renderer
+ node = nodes.math_block(
+ token.content, token.content, nowrap=True, classes=["amsmath"]
+ )
+ if token.meta["numbered"] != "*":
+ node["numbered"] = True
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def render_footnote_ref(self, token: SyntaxTreeNode) -> None:
+ """Footnote references are added as auto-numbered,
+ .i.e. `[^a]` is read as rST `[#a]_`
+ """
+ target = token.meta["label"]
+
+ refnode = nodes.footnote_reference(f"[^{target}]")
+ self.add_line_and_source_path(refnode, token)
+ if not target.isdigit():
+ refnode["auto"] = 1
+ self.document.note_autofootnote_ref(refnode)
+ else:
+ refnode += nodes.Text(target)
+
+ refnode["refname"] = target
+ self.document.note_footnote_ref(refnode)
+
+ self.current_node.append(refnode)
+
+ def render_footnote_reference(self, token: SyntaxTreeNode) -> None:
+ target = token.meta["label"]
+
+ footnote = nodes.footnote()
+ self.add_line_and_source_path(footnote, token)
+ footnote["names"].append(target)
+ if not target.isdigit():
+ footnote["auto"] = 1
+ self.document.note_autofootnote(footnote)
+ else:
+ footnote += nodes.label("", target)
+ self.document.note_footnote(footnote)
+ self.document.note_explicit_target(footnote, footnote)
+ with self.current_node_context(footnote, append=True):
+ self.render_children(token)
+
+ def render_myst_block_break(self, token: SyntaxTreeNode) -> None:
+ block_break = nodes.comment(token.content, token.content)
+ block_break["classes"] += ["block_break"]
+ self.add_line_and_source_path(block_break, token)
+ self.current_node.append(block_break)
+
+ def render_myst_target(self, token: SyntaxTreeNode) -> None:
+ text = token.content
+ name = nodes.fully_normalize_name(text)
+ target = nodes.target(text)
+ target["names"].append(name)
+ self.add_line_and_source_path(target, token)
+ self.document.note_explicit_target(target, self.current_node)
+ self.current_node.append(target)
+
+ def render_myst_line_comment(self, token: SyntaxTreeNode) -> None:
+ self.current_node.append(nodes.comment(token.content, token.content.strip()))
+
+ def render_myst_role(self, token: SyntaxTreeNode) -> None:
+ name = token.meta["name"]
+ text = token.content
+ rawsource = f":{name}:`{token.content}`"
+ lineno = token_line(token) if token.map else 0
+ role_func, messages = roles.role(
+ name, self.language_module_rst, lineno, self.reporter
+ )
+ inliner = MockInliner(self)
+ if role_func:
+ nodes, messages2 = role_func(name, rawsource, text, lineno, inliner)
+ # return nodes, messages + messages2
+ self.current_node += nodes
+ else:
+ message = self.reporter.error(
+ f'Unknown interpreted text role "{name}".', line=lineno
+ )
+ problematic = inliner.problematic(text, rawsource, message)
+ self.current_node += problematic
+
+ def render_colon_fence(self, token: SyntaxTreeNode) -> None:
+ """Render a code fence with ``:`` colon delimiters."""
+
+ if token.content.startswith(":::"):
+ # the content starts with a nested fence block,
+ # but must distinguish between ``:options:``, so we add a new line
+ assert token.token is not None, '"colon_fence" must have a `token`'
+ linear_token = token.token.copy()
+ linear_token.content = "\n" + linear_token.content
+ token.token = linear_token
+
+ return self.render_fence(token)
+
+ def render_dl(self, token: SyntaxTreeNode) -> None:
+ """Render a definition list."""
+ node = nodes.definition_list(classes=["simple", "myst"])
+ self.add_line_and_source_path(node, token)
+ with self.current_node_context(node, append=True):
+ item = None
+ for child in token.children or []:
+ if child.type == "dt":
+ item = nodes.definition_list_item()
+ self.add_line_and_source_path(item, child)
+ with self.current_node_context(item, append=True):
+ term = nodes.term(
+ child.children[0].content if child.children else ""
+ )
+ self.add_line_and_source_path(term, child)
+ with self.current_node_context(term, append=True):
+ self.render_children(child)
+ elif child.type == "dd":
+ if item is None:
+ error = self.reporter.error(
+ (
+ "Found a definition in a definition list, "
+ "with no preceding term"
+ ),
+ # nodes.literal_block(content, content),
+ line=token_line(child),
+ )
+ self.current_node += [error]
+ with self.current_node_context(item):
+ definition = nodes.definition()
+ self.add_line_and_source_path(definition, child)
+ with self.current_node_context(definition, append=True):
+ self.render_children(child)
+ else:
+ error_msg = self.reporter.error(
+ (
+ "Expected a term/definition as a child of a definition list"
+ f", but found a: {child.type}"
+ ),
+ # nodes.literal_block(content, content),
+ line=token_line(child),
+ )
+ self.current_node += [error_msg]
+
+ def render_field_list(self, token: SyntaxTreeNode) -> None:
+ """Render a field list."""
+ field_list = nodes.field_list(classes=["myst"])
+ self.add_line_and_source_path(field_list, token)
+ with self.current_node_context(field_list, append=True):
+ # raise ValueError(token.pretty(show_text=True))
+ children = (token.children or [])[:]
+ while children:
+ child = children.pop(0)
+ if not child.type == "fieldlist_name":
+ error_msg = self.reporter.error(
+ (
+ "Expected a fieldlist_name as a child of a field_list"
+ f", but found a: {child.type}"
+ ),
+ # nodes.literal_block(content, content),
+ line=token_line(child),
+ )
+ self.current_node += [error_msg]
+ break
+ field = nodes.field()
+ self.add_line_and_source_path(field, child)
+ field_list += field
+ field_name = nodes.field_name()
+ self.add_line_and_source_path(field_name, child)
+ field += field_name
+ with self.current_node_context(field_name):
+ self.render_children(child)
+ field_body = nodes.field_body()
+ self.add_line_and_source_path(field_name, child)
+ field += field_body
+ if children and children[0].type == "fieldlist_body":
+ child = children.pop(0)
+ with self.current_node_context(field_body):
+ self.render_children(child)
+
+ def render_restructuredtext(self, token: SyntaxTreeNode) -> None:
+ """Render the content of the token as restructuredtext."""
+ # copy necessary elements (source, line no, env, reporter)
+ newdoc = make_document()
+ newdoc["source"] = self.document["source"]
+ newdoc.settings = self.document.settings
+ newdoc.reporter = self.reporter
+ # pad the line numbers artificially so they offset with the fence block
+ pseudosource = ("\n" * token_line(token)) + token.content
+ # actually parse the rst into our document
+ MockRSTParser().parse(pseudosource, newdoc)
+ for node in newdoc:
+ if node["names"]:
+ self.document.note_explicit_target(node, node)
+ self.current_node.extend(newdoc.children)
+
+ def render_directive(self, token: SyntaxTreeNode) -> None:
+ """Render special fenced code blocks as directives."""
+ first_line = token.info.split(maxsplit=1)
+ name = first_line[0][1:-1]
+ arguments = "" if len(first_line) == 1 else first_line[1]
+ content = token.content
+ position = token_line(token)
+ nodes_list = self.run_directive(name, arguments, content, position)
+ self.current_node += nodes_list
+
+ def run_directive(
+ self, name: str, first_line: str, content: str, position: int
+ ) -> list[nodes.Element]:
+ """Run a directive and return the generated nodes.
+
+ :param name: the name of the directive
+ :param first_line: The text on the same line as the directive name.
+ May be an argument or body text, dependent on the directive
+ :param content: All text after the first line. Can include options.
+ :param position: The line number of the first line
+
+ """
+ # TODO directive name white/black lists
+
+ self.document.current_line = position
+
+ # get directive class
+ output: tuple[Directive, list] = directives.directive(
+ name, self.language_module_rst, self.document
+ )
+ directive_class, messages = output
+ if not directive_class:
+ error = self.reporter.error(
+ f'Unknown directive type "{name}".\n',
+ # nodes.literal_block(content, content),
+ line=position,
+ )
+ return [error] + messages
+
+ if issubclass(directive_class, Include):
+ # this is a Markdown only option,
+ # to allow for altering relative image reference links
+ directive_class.option_spec["relative-images"] = directives.flag
+ directive_class.option_spec["relative-docs"] = directives.path
+
+ try:
+ arguments, options, body_lines, content_offset = parse_directive_text(
+ directive_class, first_line, content
+ )
+ except DirectiveParsingError as error:
+ error = self.reporter.error(
+ f"Directive '{name}': {error}",
+ nodes.literal_block(content, content),
+ line=position,
+ )
+ return [error]
+
+ # initialise directive
+ if issubclass(directive_class, Include):
+ directive_instance = MockIncludeDirective(
+ self,
+ name=name,
+ klass=directive_class,
+ arguments=arguments,
+ options=options,
+ body=body_lines,
+ lineno=position,
+ )
+ else:
+ state_machine = MockStateMachine(self, position)
+ state = MockState(self, state_machine, position)
+ directive_instance = directive_class(
+ name=name,
+ # the list of positional arguments
+ arguments=arguments,
+ # a dictionary mapping option names to values
+ options=options,
+ # the directive content line by line
+ content=StringList(body_lines, self.document["source"]),
+ # the absolute line number of the first line of the directive
+ lineno=position,
+ # the line offset of the first line of the content
+ content_offset=content_offset,
+ # a string containing the entire directive
+ block_text="\n".join(body_lines),
+ state=state,
+ state_machine=state_machine,
+ )
+
+ # run directive
+ try:
+ result = directive_instance.run()
+ except DirectiveError as error:
+ msg_node = self.reporter.system_message(
+ error.level, error.msg, line=position
+ )
+ msg_node += nodes.literal_block(content, content)
+ result = [msg_node]
+ except MockingError as exc:
+ error_msg = self.reporter.error(
+ "Directive '{}' cannot be mocked: {}: {}".format(
+ name, exc.__class__.__name__, exc
+ ),
+ nodes.literal_block(content, content),
+ line=position,
+ )
+ return [error_msg]
+
+ assert isinstance(
+ result, list
+ ), f'Directive "{name}" must return a list of nodes.'
+ for i in range(len(result)):
+ assert isinstance(
+ result[i], nodes.Node
+ ), 'Directive "{}" returned non-Node object (index {}): {}'.format(
+ name, i, result[i]
+ )
+ return result
+
+ def render_substitution_inline(self, token: SyntaxTreeNode) -> None:
+ """Render inline substitution {{key}}."""
+ self.render_substitution(token, inline=True)
+
+ def render_substitution_block(self, token: SyntaxTreeNode) -> None:
+ """Render block substitution {{key}}."""
+ self.render_substitution(token, inline=False)
+
+ def render_substitution(self, token: SyntaxTreeNode, inline: bool) -> None:
+ """Substitutions are rendered by:
+
+ 1. Combining global substitutions with front-matter substitutions
+ to create a variable context (front-matter takes priority)
+ 2. Add the sphinx `env` to the variable context (if available)
+ 3. Create the string content with Jinja2 (passing it the variable context)
+ 4. If the substitution is inline and not a directive,
+ parse to nodes ignoring block syntaxes (like lists or block-quotes),
+ otherwise parse to nodes with all syntax rules.
+
+ """
+ position = token_line(token)
+
+ # front-matter substitutions take priority over config ones
+ variable_context: dict[str, Any] = {**self.md_config.substitutions}
+ if self.sphinx_env is not None:
+ variable_context["env"] = self.sphinx_env
+
+ # fail on undefined variables
+ env = jinja2.Environment(undefined=jinja2.StrictUndefined)
+
+ # try rendering
+ try:
+ rendered = env.from_string(f"{{{{{token.content}}}}}").render(
+ variable_context
+ )
+ except Exception as error:
+ error_msg = self.reporter.error(
+ f"Substitution error:{error.__class__.__name__}: {error}",
+ line=position,
+ )
+ self.current_node += [error_msg]
+ return
+
+ # handle circular references
+ ast = env.parse(f"{{{{{token.content}}}}}")
+ references = {
+ n.name for n in ast.find_all(jinja2.nodes.Name) if n.name != "env"
+ }
+ self.document.sub_references = getattr(self.document, "sub_references", set())
+ cyclic = references.intersection(self.document.sub_references)
+ if cyclic:
+ error_msg = self.reporter.error(
+ f"circular substitution reference: {cyclic}",
+ line=position,
+ )
+ self.current_node += [error_msg]
+ return
+
+ # TODO improve error reporting;
+ # at present, for a multi-line substitution,
+ # an error may point to a line lower than the substitution
+ # should it point to the source of the substitution?
+ # or the error message should at least indicate that its a substitution
+
+ # we record used references before nested parsing, then remove them after
+ self.document.sub_references.update(references)
+ try:
+ if inline and not REGEX_DIRECTIVE_START.match(rendered):
+ self.nested_render_text(rendered, position, inline=True)
+ else:
+ self.nested_render_text(rendered, position, allow_headings=False)
+ finally:
+ self.document.sub_references.difference_update(references)
+
+
+def html_meta_to_nodes(
+ data: dict[str, Any], document: nodes.document, line: int, reporter: Reporter
+) -> list[nodes.pending | nodes.system_message]:
+ """Replicate the `meta` directive,
+ by converting a dictionary to a list of pending meta nodes
+
+ See:
+ https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#html-metadata
+ """
+ if not data:
+ return []
+
+ try:
+ # if sphinx available
+ from sphinx.addnodes import meta as meta_cls
+ except ImportError:
+ try:
+ # docutils >= 0.19
+ meta_cls = nodes.meta # type: ignore
+ except AttributeError:
+ from docutils.parsers.rst.directives.html import MetaBody
+
+ meta_cls = MetaBody.meta # type: ignore
+
+ output = []
+
+ for key, value in data.items():
+ content = str(value or "")
+ meta_node = meta_cls(content)
+ meta_node.source = document["source"]
+ meta_node.line = line
+ meta_node["content"] = content
+ try:
+ if not content:
+ raise ValueError("No content")
+ for i, key_part in enumerate(key.split()):
+ if "=" not in key_part and i == 0:
+ meta_node["name"] = key_part
+ continue
+ if "=" not in key_part:
+ raise ValueError(f"no '=' in {key_part}")
+ attr_name, attr_val = key_part.split("=", 1)
+ if not (attr_name and attr_val):
+ raise ValueError(f"malformed {key_part}")
+ meta_node[attr_name.lower()] = attr_val
+ except ValueError as error:
+ msg = reporter.error(f'Error parsing meta tag attribute "{key}": {error}.')
+ output.append(msg)
+ continue
+
+ pending = nodes.pending(
+ Filter,
+ {"component": "writer", "format": "html", "nodes": [meta_node]},
+ )
+ document.note_pending(pending)
+ output.append(pending)
+
+ return output
diff --git a/myst_parser/mdit_to_docutils/html_to_nodes.py b/myst_parser/mdit_to_docutils/html_to_nodes.py
new file mode 100644
index 0000000..2cc3066
--- /dev/null
+++ b/myst_parser/mdit_to_docutils/html_to_nodes.py
@@ -0,0 +1,139 @@
+"""Convert HTML to docutils nodes."""
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING
+
+from docutils import nodes
+
+from myst_parser.parsers.parse_html import Data, tokenize_html
+
+if TYPE_CHECKING:
+ from .base import DocutilsRenderer
+
+
+def make_error(
+ document: nodes.document, error_msg: str, text: str, line_number: int
+) -> nodes.system_message:
+ return document.reporter.error(
+ error_msg,
+ nodes.literal_block(text, text),
+ line=line_number,
+ )
+
+
+OPTION_KEYS_IMAGE = {"class", "alt", "height", "width", "align", "name"}
+# note: docutils also has scale and target
+
+OPTION_KEYS_ADMONITION = {"class", "name"}
+
+# See https://github.com/micromark/micromark-extension-gfm-tagfilter
+RE_FLOW = re.compile(
+ r"<(\/?)(iframe|noembed|noframes|plaintext|script|style|title|textarea|xmp)(?=[\t\n\f\r />])",
+ re.IGNORECASE,
+)
+
+
+def default_html(text: str, source: str, line_number: int) -> list[nodes.Element]:
+ raw_html = nodes.raw("", text, format="html")
+ raw_html.source = source
+ raw_html.line = line_number
+ return [raw_html]
+
+
+def html_to_nodes(
+ text: str, line_number: int, renderer: DocutilsRenderer
+) -> list[nodes.Element]:
+ """Convert HTML to docutils nodes."""
+ if renderer.md_config.gfm_only:
+ text, _ = RE_FLOW.subn(lambda s: s.group(0).replace("<", "&lt;"), text)
+
+ enable_html_img = "html_image" in renderer.md_config.enable_extensions
+ enable_html_admonition = "html_admonition" in renderer.md_config.enable_extensions
+ if not (enable_html_img or enable_html_admonition):
+ return default_html(text, renderer.document["source"], line_number)
+
+ # parse the HTML to AST
+ try:
+ root = tokenize_html(text).strip(inplace=True, recurse=False)
+ except Exception:
+ msg_node = renderer.create_warning(
+ "HTML could not be parsed", line=line_number, subtype="html"
+ )
+ return ([msg_node] if msg_node else []) + default_html(
+ text, renderer.document["source"], line_number
+ )
+
+ if len(root) < 1:
+ # if empty
+ return default_html(text, renderer.document["source"], line_number)
+
+ if not all(
+ (enable_html_img and child.name == "img")
+ or (
+ enable_html_admonition
+ and child.name == "div"
+ and "admonition" in child.attrs.classes
+ )
+ for child in root
+ ):
+ return default_html(text, renderer.document["source"], line_number)
+
+ nodes_list = []
+ for child in root:
+
+ if child.name == "img":
+ if "src" not in child.attrs:
+ return [
+ renderer.reporter.error(
+ "<img> missing 'src' attribute", line=line_number
+ )
+ ]
+ content = "\n".join(
+ f":{k}: {v}"
+ for k, v in sorted(child.attrs.items())
+ if k in OPTION_KEYS_IMAGE
+ )
+ nodes_list.extend(
+ renderer.run_directive(
+ "image", child.attrs["src"], content, line_number
+ )
+ )
+
+ else:
+ children = child.strip().children
+ if (
+ children
+ and children[0].name in ("div", "p")
+ and (
+ "title" in children[0].attrs.classes
+ or "admonition-title" in children[0].attrs.classes
+ )
+ ):
+ title = "".join(child.render() for child in children.pop(0))
+ else:
+ title = "Note"
+
+ options = "\n".join(
+ f":{k}: {v}"
+ for k, v in sorted(child.attrs.items())
+ if k in OPTION_KEYS_ADMONITION
+ ).rstrip()
+ new_children = []
+ for child in children:
+ if child.name == "p":
+ new_children.extend(child.children)
+ new_children.append(Data("\n\n"))
+ else:
+ new_children.append(child)
+ content = (
+ options
+ + ("\n\n" if options else "")
+ + "".join(child.render() for child in new_children).lstrip()
+ )
+
+ nodes_list.extend(
+ renderer.run_directive("admonition", title, content, line_number)
+ )
+
+ return nodes_list
diff --git a/myst_parser/mdit_to_docutils/sphinx_.py b/myst_parser/mdit_to_docutils/sphinx_.py
new file mode 100644
index 0000000..3c1bc23
--- /dev/null
+++ b/myst_parser/mdit_to_docutils/sphinx_.py
@@ -0,0 +1,245 @@
+"""Convert Markdown-it tokens to docutils nodes, including sphinx specific elements."""
+from __future__ import annotations
+
+import os
+from pathlib import Path
+from typing import cast
+from urllib.parse import unquote
+from uuid import uuid4
+
+from docutils import nodes
+from markdown_it.tree import SyntaxTreeNode
+from sphinx import addnodes
+from sphinx.domains.math import MathDomain
+from sphinx.domains.std import StandardDomain
+from sphinx.environment import BuildEnvironment
+from sphinx.util import logging
+from sphinx.util.nodes import clean_astext
+
+from myst_parser.mdit_to_docutils.base import DocutilsRenderer
+
+LOGGER = logging.getLogger(__name__)
+
+
+def create_warning(
+ document: nodes.document,
+ message: str,
+ *,
+ line: int | None = None,
+ append_to: nodes.Element | None = None,
+ wtype: str = "myst",
+ subtype: str = "other",
+) -> nodes.system_message | None:
+ """Generate a warning, logging it if necessary.
+
+ If the warning type is listed in the ``suppress_warnings`` configuration,
+ then ``None`` will be returned and no warning logged.
+ """
+ message = f"{message} [{wtype}.{subtype}]"
+ kwargs = {"line": line} if line is not None else {}
+
+ if logging.is_suppressed_warning(
+ wtype, subtype, document.settings.env.app.config.suppress_warnings
+ ):
+ return None
+
+ msg_node = document.reporter.warning(message, **kwargs)
+ if append_to is not None:
+ append_to.append(msg_node)
+
+ return None
+
+
+class SphinxRenderer(DocutilsRenderer):
+ """A markdown-it-py renderer to populate (in-place) a `docutils.document` AST.
+
+ This is sub-class of `DocutilsRenderer` that handles sphinx specific aspects,
+ such as cross-referencing.
+ """
+
+ @property
+ def doc_env(self) -> BuildEnvironment:
+ return self.document.settings.env
+
+ def create_warning(
+ self,
+ message: str,
+ *,
+ line: int | None = None,
+ append_to: nodes.Element | None = None,
+ wtype: str = "myst",
+ subtype: str = "other",
+ ) -> nodes.system_message | None:
+ """Generate a warning, logging it if necessary.
+
+ If the warning type is listed in the ``suppress_warnings`` configuration,
+ then ``None`` will be returned and no warning logged.
+ """
+ return create_warning(
+ self.document,
+ message,
+ line=line,
+ append_to=append_to,
+ wtype=wtype,
+ subtype=subtype,
+ )
+
+ def render_internal_link(self, token: SyntaxTreeNode) -> None:
+ """Render link token `[text](link "title")`,
+ where the link has not been identified as an external URL.
+ """
+ destination = unquote(cast(str, token.attrGet("href") or ""))
+
+ # make the path relative to an "including" document
+ # this is set when using the `relative-docs` option of the MyST `include` directive
+ relative_include = self.md_env.get("relative-docs", None)
+ if relative_include is not None and destination.startswith(relative_include[0]):
+ source_dir, include_dir = relative_include[1:]
+ destination = os.path.relpath(
+ os.path.join(include_dir, os.path.normpath(destination)), source_dir
+ )
+
+ potential_path = (
+ Path(self.doc_env.doc2path(self.doc_env.docname)).parent / destination
+ if self.doc_env.srcdir # not set in some test situations
+ else None
+ )
+ if (
+ potential_path
+ and potential_path.is_file()
+ and not any(
+ destination.endswith(suffix)
+ for suffix in self.doc_env.config.source_suffix
+ )
+ ):
+ wrap_node = addnodes.download_reference(
+ refdoc=self.doc_env.docname,
+ reftarget=destination,
+ reftype="myst",
+ refdomain=None, # Added to enable cross-linking
+ refexplicit=len(token.children or []) > 0,
+ refwarn=False,
+ )
+ classes = ["xref", "download", "myst"]
+ text = destination if not token.children else ""
+ else:
+ wrap_node = addnodes.pending_xref(
+ refdoc=self.doc_env.docname,
+ reftarget=destination,
+ reftype="myst",
+ refdomain=None, # Added to enable cross-linking
+ refexplicit=len(token.children or []) > 0,
+ refwarn=True,
+ )
+ classes = ["xref", "myst"]
+ text = ""
+
+ 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)
+
+ inner_node = nodes.inline("", text, classes=classes)
+ wrap_node.append(inner_node)
+ with self.current_node_context(inner_node):
+ self.render_children(token)
+
+ def render_heading(self, token: SyntaxTreeNode) -> None:
+ """This extends the docutils method, to allow for the addition of heading ids.
+ These ids are computed by the ``markdown-it-py`` ``anchors_plugin``
+ as "slugs" which are unique to a document.
+
+ The approach is similar to ``sphinx.ext.autosectionlabel``
+ """
+ super().render_heading(token)
+
+ if not isinstance(self.current_node, nodes.section):
+ return
+
+ # create the slug string
+ slug = cast(str, token.attrGet("id"))
+ if slug is None:
+ return
+
+ section = self.current_node
+ doc_slug = self.doc_env.doc2path(self.doc_env.docname, base=False) + "#" + slug
+
+ # save the reference in the standard domain, so that it can be handled properly
+ domain = cast(StandardDomain, self.doc_env.get_domain("std"))
+ if doc_slug in domain.labels:
+ other_doc = self.doc_env.doc2path(domain.labels[doc_slug][0])
+ self.create_warning(
+ f"duplicate label {doc_slug}, other instance in {other_doc}",
+ line=section.line,
+ subtype="anchor",
+ )
+ labelid = section["ids"][0]
+ domain.anonlabels[doc_slug] = self.doc_env.docname, labelid
+ domain.labels[doc_slug] = (
+ self.doc_env.docname,
+ labelid,
+ clean_astext(section[0]),
+ )
+
+ self.doc_env.metadata[self.doc_env.docname]["myst_anchors"] = True
+ section["myst-anchor"] = doc_slug
+
+ def render_math_block_label(self, token: SyntaxTreeNode) -> None:
+ """Render math with referencable labels, e.g. ``$a=1$ (label)``."""
+ label = token.info
+ content = token.content
+ node = nodes.math_block(
+ content, content, nowrap=False, number=None, label=label
+ )
+ target = self.add_math_target(node)
+ self.add_line_and_source_path(target, token)
+ self.current_node.append(target)
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def _random_label(self) -> str:
+ return str(uuid4())
+
+ def render_amsmath(self, token: SyntaxTreeNode) -> None:
+ """Renderer for the amsmath extension."""
+ # environment = token.meta["environment"]
+ content = token.content
+
+ if token.meta["numbered"] != "*":
+ # TODO how to parse and reference labels within environment?
+ # for now we give create a unique hash, so the equation will be numbered
+ # but there will be no reference clashes
+ label = self._random_label()
+ node = nodes.math_block(
+ content,
+ content,
+ nowrap=True,
+ number=None,
+ classes=["amsmath"],
+ label=label,
+ )
+ target = self.add_math_target(node)
+ self.add_line_and_source_path(target, token)
+ self.current_node.append(target)
+ else:
+ node = nodes.math_block(
+ content, content, nowrap=True, number=None, classes=["amsmath"]
+ )
+ self.add_line_and_source_path(node, token)
+ self.current_node.append(node)
+
+ def add_math_target(self, node: nodes.math_block) -> nodes.target:
+ # Code mainly copied from sphinx.directives.patches.MathDirective
+
+ # register label to domain
+ domain = cast(MathDomain, self.doc_env.get_domain("math"))
+ domain.note_equation(self.doc_env.docname, node["label"], location=node)
+ node["number"] = domain.get_equation_number_for(node["label"])
+ node["docname"] = self.doc_env.docname
+
+ # create target node
+ node_id = nodes.make_id("equation-%s" % node["label"])
+ target = nodes.target("", "", ids=[node_id])
+ self.document.note_explicit_target(target)
+ return target
diff --git a/myst_parser/mdit_to_docutils/utils.py b/myst_parser/mdit_to_docutils/utils.py
new file mode 100644
index 0000000..b31d8c7
--- /dev/null
+++ b/myst_parser/mdit_to_docutils/utils.py
@@ -0,0 +1,36 @@
+import html
+from typing import Iterable, Optional
+from urllib.parse import quote, urlparse
+
+
+def escape_url(raw: str) -> str:
+ """
+ Escape urls to prevent code injection craziness. (Hopefully.)
+ """
+ return html.escape(quote(html.unescape(raw), safe="/#:()*?=%@+,&"))
+
+
+def is_external_url(
+ reference: str,
+ known_url_schemes: Optional[Iterable[str]],
+ match_fragment: bool = False,
+) -> bool:
+ """Return if a reference should be recognised as an external URL.
+
+ URLs are of the format: scheme://netloc/path;parameters?query#fragment
+
+ This checks if there is a url scheme (e.g. 'https') and, if so,
+ if the scheme is is the list of known_url_schemes (if supplied).
+
+ :param known_url_schemes: e.g. ["http", "https", "mailto"]
+ If None, match all schemes
+ :param match_fragment: If True and a fragment found, then True will be returned,
+ irrespective of a scheme match
+
+ """
+ url_check = urlparse(reference)
+ if known_url_schemes is not None:
+ scheme_known = url_check.scheme in known_url_schemes
+ else:
+ scheme_known = bool(url_check.scheme)
+ return scheme_known or (match_fragment and url_check.fragment != "")
diff --git a/myst_parser/mocking.py b/myst_parser/mocking.py
new file mode 100644
index 0000000..b22475d
--- /dev/null
+++ b/myst_parser/mocking.py
@@ -0,0 +1,514 @@
+"""This module provides classes to Mock the core components of the docutils.RSTParser,
+the key difference being that nested parsing treats the text as Markdown not rST.
+"""
+from __future__ import annotations
+
+import os
+import re
+import sys
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, DirectiveError
+from docutils.parsers.rst import Parser as RSTParser
+from docutils.parsers.rst.directives.misc import Include
+from docutils.parsers.rst.states import Body, Inliner, RSTStateMachine
+from docutils.statemachine import StringList
+from docutils.utils import unescape
+
+from .parsers.directives import parse_directive_text
+
+if TYPE_CHECKING:
+ from .mdit_to_docutils.base import DocutilsRenderer
+
+
+class MockingError(Exception):
+ """An exception to signal an error during mocking of docutils components."""
+
+
+class MockInliner:
+ """A mock version of `docutils.parsers.rst.states.Inliner`.
+
+ This is parsed to role functions.
+ """
+
+ def __init__(self, renderer: DocutilsRenderer):
+ """Initialize the mock inliner."""
+ self._renderer = renderer
+ # here we mock that the `parse` method has already been called
+ # which is where these attributes are set (via the RST state Memo)
+ self.document = renderer.document
+ self.reporter = renderer.document.reporter
+ self.language = renderer.language_module_rst
+ self.parent = renderer.current_node
+
+ if not hasattr(self.reporter, "get_source_and_line"):
+ # In docutils this is set by `RSTState.runtime_init`
+ self.reporter.get_source_and_line = lambda l: (self.document["source"], l)
+
+ self.rfc_url = "rfc%d.html"
+
+ def problematic(
+ self, text: str, rawsource: str, message: nodes.system_message
+ ) -> nodes.problematic:
+ """Record a system message from parsing."""
+ msgid = self.document.set_id(message, self.parent)
+ problematic = nodes.problematic(rawsource, text, refid=msgid)
+ prbid = self.document.set_id(problematic)
+ message.add_backref(prbid)
+ return problematic
+
+ def parse(
+ self, text: str, lineno: int, memo: Any, parent: nodes.Node
+ ) -> tuple[list[nodes.Node], list[nodes.system_message]]:
+ """Parse the text and return a list of nodes."""
+ # note the only place this is normally called,
+ # is by `RSTState.inline_text`, or in directives: `self.state.inline_text`,
+ # and there the state parses its own parent
+ # self.reporter = memo.reporter
+ # self.document = memo.document
+ # self.language = memo.language
+ with self._renderer.current_node_context(parent):
+ # the parent is never actually appended to though,
+ # so we make a temporary parent to parse into
+ container = nodes.Element()
+ with self._renderer.current_node_context(container):
+ self._renderer.nested_render_text(text, lineno, inline=True)
+
+ return container.children, []
+
+ def __getattr__(self, name: str):
+ """This method is only be called if the attribute requested has not
+ been defined. Defined attributes will not be overridden.
+ """
+ # TODO use document.reporter mechanism?
+ if hasattr(Inliner, name):
+ msg = "{cls} has not yet implemented attribute '{name}'".format(
+ cls=type(self).__name__, name=name
+ )
+ raise MockingError(msg).with_traceback(sys.exc_info()[2])
+ msg = f"{type(self).__name__} has no attribute {name}"
+ raise MockingError(msg).with_traceback(sys.exc_info()[2])
+
+
+class MockState:
+ """A mock version of `docutils.parsers.rst.states.RSTState`.
+
+ This is parsed to the `Directives.run()` method,
+ so that they may run nested parses on their content that will be parsed as markdown,
+ rather than RST.
+ """
+
+ def __init__(
+ self,
+ renderer: DocutilsRenderer,
+ state_machine: MockStateMachine,
+ lineno: int,
+ ):
+ self._renderer = renderer
+ self._lineno = lineno
+ self.document = renderer.document
+ self.reporter = renderer.document.reporter
+ self.state_machine = state_machine
+ self.inliner = MockInliner(renderer)
+
+ class Struct:
+ document = self.document
+ reporter = self.document.reporter
+ language = renderer.language_module_rst
+ title_styles: list[str] = []
+ section_level = max(renderer._level_to_elem)
+ section_bubble_up_kludge = False
+ inliner = self.inliner
+
+ self.memo = Struct
+
+ def parse_directive_block(
+ self,
+ content: StringList,
+ line_offset: int,
+ directive: type[Directive],
+ option_presets: dict,
+ ) -> tuple[list, dict, StringList, int]:
+ """Parse the full directive text
+
+ :returns: (arguments, options, content, content_offset)
+ """
+ if option_presets:
+ raise MockingError("parse_directive_block: option_presets not implemented")
+ # TODO should argument_str always be ""?
+ arguments, options, body_lines, content_offset = parse_directive_text(
+ directive, "", "\n".join(content)
+ )
+ return (
+ arguments,
+ options,
+ StringList(body_lines, source=content.source),
+ line_offset + content_offset,
+ )
+
+ def nested_parse(
+ self,
+ block: StringList,
+ input_offset: int,
+ node: nodes.Element,
+ match_titles: bool = False,
+ state_machine_class=None,
+ state_machine_kwargs=None,
+ ) -> None:
+ """Perform a nested parse of the input block, with ``node`` as the parent.
+
+ :param block: The block of lines to parse.
+ :param input_offset: The offset of the first line of block,
+ to the starting line of the state (i.e. directive).
+ :param node: The parent node to attach the parsed content to.
+ :param match_titles: Whether to to allow the parsing of headings
+ (normally this is false,
+ since nested heading would break the document structure)
+ """
+ sm_match_titles = self.state_machine.match_titles
+ with self._renderer.current_node_context(node):
+ self._renderer.nested_render_text(
+ "\n".join(block),
+ self._lineno + input_offset,
+ allow_headings=match_titles,
+ )
+ self.state_machine.match_titles = sm_match_titles
+
+ def parse_target(self, block, block_text, lineno: int):
+ """
+ Taken from https://github.com/docutils-mirror/docutils/blob/e88c5fb08d5cdfa8b4ac1020dd6f7177778d5990/docutils/parsers/rst/states.py#L1927 # noqa: E501
+ """
+ # Commenting out this code because it only applies to rST
+ # if block and block[-1].strip()[-1:] == "_": # possible indirect target
+ # reference = " ".join([line.strip() for line in block])
+ # refname = self.is_reference(reference)
+ # if refname:
+ # return "refname", refname
+ reference = "".join(["".join(line.split()) for line in block])
+ return "refuri", unescape(reference)
+
+ def inline_text(
+ self, text: str, lineno: int
+ ) -> tuple[list[nodes.Element], list[nodes.Element]]:
+ """Parse text with only inline rules.
+
+ :returns: (list of nodes, list of messages)
+ """
+ return self.inliner.parse(text, lineno, self.memo, self._renderer.current_node)
+
+ # U+2014 is an em-dash:
+ attribution_pattern = re.compile("^((?:---?(?!-)|\u2014) *)(.+)")
+
+ def block_quote(self, lines: list[str], line_offset: int) -> list[nodes.Element]:
+ """Parse a block quote, which is a block of text,
+ followed by an (optional) attribution.
+
+ ::
+
+ No matter where you go, there you are.
+
+ -- Buckaroo Banzai
+ """
+ elements = []
+ # split attribution
+ last_line_blank = False
+ blockquote_lines = lines
+ attribution_lines = []
+ attribution_line_offset = None
+ # First line after a blank line must begin with a dash
+ for i, line in enumerate(lines):
+ if not line.strip():
+ last_line_blank = True
+ continue
+ if not last_line_blank:
+ last_line_blank = False
+ continue
+ last_line_blank = False
+ match = self.attribution_pattern.match(line)
+ if not match:
+ continue
+ attribution_line_offset = i
+ attribution_lines = [match.group(2)]
+ for at_line in lines[i + 1 :]:
+ indented_line = at_line[len(match.group(1)) :]
+ if len(indented_line) != len(at_line.lstrip()):
+ break
+ attribution_lines.append(indented_line)
+ blockquote_lines = lines[:i]
+ break
+ # parse block
+ blockquote = nodes.block_quote()
+ self.nested_parse(blockquote_lines, line_offset, blockquote)
+ elements.append(blockquote)
+ # parse attribution
+ if attribution_lines:
+ attribution_text = "\n".join(attribution_lines)
+ lineno = self._lineno + line_offset + (attribution_line_offset or 0)
+ textnodes, messages = self.inline_text(attribution_text, lineno)
+ attribution = nodes.attribution(attribution_text, "", *textnodes)
+ (
+ attribution.source,
+ attribution.line,
+ ) = self.state_machine.get_source_and_line(lineno)
+ blockquote += attribution
+ elements += messages
+ return elements
+
+ def build_table(self, tabledata, tableline, stub_columns: int = 0, widths=None):
+ return Body.build_table(self, tabledata, tableline, stub_columns, widths)
+
+ def build_table_row(self, rowdata, tableline):
+ return Body.build_table_row(self, rowdata, tableline)
+
+ def __getattr__(self, name: str):
+ """This method is only be called if the attribute requested has not
+ been defined. Defined attributes will not be overridden.
+ """
+ cls = type(self).__name__
+ if hasattr(Body, name):
+ msg = (
+ f"{cls} has not yet implemented attribute '{name}'. "
+ "You can parse RST directly via the `{eval-rst}` directive: "
+ "https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html#how-directives-parse-content" # noqa: E501
+ )
+ else:
+ # The requested `name` is not a docutils Body element
+ # (such as "footnote", "block_quote", "paragraph", …)
+ msg = f"{cls} has no attribute '{name}'"
+ raise MockingError(msg).with_traceback(sys.exc_info()[2])
+
+
+class MockStateMachine:
+ """A mock version of `docutils.parsers.rst.states.RSTStateMachine`.
+
+ This is parsed to the `Directives.run()` method.
+ """
+
+ def __init__(self, renderer: DocutilsRenderer, lineno: int):
+ self._renderer = renderer
+ self._lineno = lineno
+ self.document = renderer.document
+ self.language = renderer.language_module_rst
+ self.reporter = self.document.reporter
+ self.node: nodes.Element = renderer.current_node
+ self.match_titles: bool = True
+
+ def get_source(self, lineno: int | None = None):
+ """Return document source path."""
+ return self.document["source"]
+
+ def get_source_and_line(self, lineno: int | None = None):
+ """Return (source path, line) tuple for current or given line number."""
+ return self.document["source"], lineno or self._lineno
+
+ def __getattr__(self, name: str):
+ """This method is only be called if the attribute requested has not
+ been defined. Defined attributes will not be overridden.
+ """
+ if hasattr(RSTStateMachine, name):
+ msg = "{cls} has not yet implemented attribute '{name}'".format(
+ cls=type(self).__name__, name=name
+ )
+ raise MockingError(msg).with_traceback(sys.exc_info()[2])
+ msg = f"{type(self).__name__} has no attribute {name}"
+ raise MockingError(msg).with_traceback(sys.exc_info()[2])
+
+
+class MockIncludeDirective:
+ """This directive uses a lot of statemachine logic that is not yet mocked.
+ Therefore, we treat it as a special case (at least for now).
+
+ See:
+ https://docutils.sourceforge.io/docs/ref/rst/directives.html#including-an-external-document-fragment
+ """
+
+ def __init__(
+ self,
+ renderer: DocutilsRenderer,
+ name: str,
+ klass: Include,
+ arguments: list,
+ options: dict,
+ body: list[str],
+ lineno: int,
+ ):
+ self.renderer = renderer
+ self.document = renderer.document
+ self.name = name
+ self.klass = klass
+ self.arguments = arguments
+ self.options = options
+ self.body = body
+ self.lineno = lineno
+
+ def run(self) -> list[nodes.Element]:
+
+ from docutils.parsers.rst.directives.body import CodeBlock, NumberLines
+
+ if not self.document.settings.file_insertion_enabled:
+ raise DirectiveError(2, f'Directive "{self.name}" disabled.')
+
+ source_dir = Path(self.document["source"]).absolute().parent
+ include_arg = "".join([s.strip() for s in self.arguments[0].splitlines()])
+
+ if include_arg.startswith("<") and include_arg.endswith(">"):
+ # # docutils "standard" includes
+ path = Path(self.klass.standard_include_path).joinpath(include_arg[1:-1])
+ else:
+ # if using sphinx interpret absolute paths "correctly",
+ # i.e. relative to source directory
+ try:
+ sphinx_env = self.document.settings.env
+ except AttributeError:
+ pass
+ else:
+ _, include_arg = sphinx_env.relfn2path(self.arguments[0])
+ sphinx_env.note_included(include_arg)
+ path = Path(include_arg)
+ path = source_dir.joinpath(path)
+ # this ensures that the parent file is rebuilt if the included file changes
+ self.document.settings.record_dependencies.add(str(path))
+
+ # read file
+ encoding = self.options.get("encoding", self.document.settings.input_encoding)
+ error_handler = self.document.settings.input_encoding_error_handler
+ # tab_width = self.options.get("tab-width", self.document.settings.tab_width)
+ try:
+ file_content = path.read_text(encoding=encoding, errors=error_handler)
+ except Exception as error:
+ raise DirectiveError(
+ 4,
+ 'Directive "{}": error reading file: {}\n{}.'.format(
+ self.name, path, error
+ ),
+ )
+
+ # get required section of text
+ startline = self.options.get("start-line", None)
+ endline = self.options.get("end-line", None)
+ file_content = "\n".join(file_content.splitlines()[startline:endline])
+ startline = startline or 0
+ for split_on_type in ["start-after", "end-before"]:
+ split_on = self.options.get(split_on_type, None)
+ if not split_on:
+ continue
+ split_index = file_content.find(split_on)
+ if split_index < 0:
+ raise DirectiveError(
+ 4,
+ 'Directive "{}"; option "{}": text not found "{}".'.format(
+ self.name, split_on_type, split_on
+ ),
+ )
+ if split_on_type == "start-after":
+ startline += split_index + len(split_on)
+ file_content = file_content[split_index + len(split_on) :]
+ else:
+ file_content = file_content[:split_index]
+
+ if "literal" in self.options:
+ literal_block = nodes.literal_block(
+ file_content, source=str(path), classes=self.options.get("class", [])
+ )
+ literal_block.line = 1 # TODO don;t think this should be 1?
+ self.add_name(literal_block)
+ if "number-lines" in self.options:
+ try:
+ startline = int(self.options["number-lines"] or 1)
+ except ValueError:
+ raise DirectiveError(
+ 3, ":number-lines: with non-integer " "start value"
+ )
+ endline = startline + len(file_content.splitlines())
+ if file_content.endswith("\n"):
+ file_content = file_content[:-1]
+ tokens = NumberLines([([], file_content)], startline, endline)
+ for classes, value in tokens:
+ if classes:
+ literal_block += nodes.inline(value, value, classes=classes)
+ else:
+ literal_block += nodes.Text(value)
+ else:
+ literal_block += nodes.Text(file_content)
+ return [literal_block]
+ if "code" in self.options:
+ self.options["source"] = str(path)
+ state_machine = MockStateMachine(self.renderer, self.lineno)
+ state = MockState(self.renderer, state_machine, self.lineno)
+ codeblock = CodeBlock(
+ name=self.name,
+ arguments=[self.options.pop("code")],
+ options=self.options,
+ content=file_content.splitlines(),
+ lineno=self.lineno,
+ content_offset=0,
+ block_text=file_content,
+ state=state,
+ state_machine=state_machine,
+ )
+ return codeblock.run()
+
+ # Here we perform a nested render, but temporarily setup the document/reporter
+ # with the correct document path and lineno for the included file.
+ source = self.renderer.document["source"]
+ rsource = self.renderer.reporter.source
+ line_func = getattr(self.renderer.reporter, "get_source_and_line", None)
+ try:
+ self.renderer.document["source"] = str(path)
+ self.renderer.reporter.source = str(path)
+ self.renderer.reporter.get_source_and_line = lambda l: (str(path), l)
+ if "relative-images" in self.options:
+ self.renderer.md_env["relative-images"] = os.path.relpath(
+ path.parent, source_dir
+ )
+ if "relative-docs" in self.options:
+ self.renderer.md_env["relative-docs"] = (
+ self.options["relative-docs"],
+ source_dir,
+ path.parent,
+ )
+ self.renderer.nested_render_text(
+ file_content, startline + 1, allow_headings=True
+ )
+ finally:
+ self.renderer.document["source"] = source
+ self.renderer.reporter.source = rsource
+ self.renderer.md_env.pop("relative-images", None)
+ self.renderer.md_env.pop("relative-docs", None)
+ if line_func is not None:
+ self.renderer.reporter.get_source_and_line = line_func
+ else:
+ del self.renderer.reporter.get_source_and_line
+ return []
+
+ def add_name(self, node: nodes.Element):
+ """Append self.options['name'] to node['names'] if it exists.
+
+ Also normalize the name string and register it as explicit target.
+ """
+ if "name" in self.options:
+ name = nodes.fully_normalize_name(self.options.pop("name"))
+ if "name" in node:
+ del node["name"]
+ node["names"].append(name)
+ self.renderer.document.note_explicit_target(node, node)
+
+
+class MockRSTParser(RSTParser):
+ """RSTParser which avoids a negative side effect."""
+
+ def parse(self, inputstring: str, document: nodes.document):
+ """Parse the input to populate the document AST."""
+ from docutils.parsers.rst import roles
+
+ should_restore = False
+ if "" in roles._roles:
+ should_restore = True
+ blankrole = roles._roles[""]
+
+ super().parse(inputstring, document)
+
+ if should_restore:
+ roles._roles[""] = blankrole
diff --git a/myst_parser/parsers/__init__.py b/myst_parser/parsers/__init__.py
new file mode 100644
index 0000000..26fbfca
--- /dev/null
+++ b/myst_parser/parsers/__init__.py
@@ -0,0 +1 @@
+"""Parsers of MyST Markdown source text to docutils AST."""
diff --git a/myst_parser/parsers/directives.py b/myst_parser/parsers/directives.py
new file mode 100644
index 0000000..5637254
--- /dev/null
+++ b/myst_parser/parsers/directives.py
@@ -0,0 +1,190 @@
+"""Fenced code blocks are parsed as directives,
+if the block starts with ``{directive_name}``,
+followed by arguments on the same line.
+
+Directive options are read from a YAML block,
+if the first content line starts with ``---``, e.g.
+
+::
+
+ ```{directive_name} arguments
+ ---
+ option1: name
+ option2: |
+ Longer text block
+ ---
+ content...
+ ```
+
+Or the option block will be parsed if the first content line starts with ``:``,
+as a YAML block consisting of every line that starts with a ``:``, e.g.
+
+::
+
+ ```{directive_name} arguments
+ :option1: name
+ :option2: other
+
+ content...
+ ```
+
+If the first line of a directive's content is blank, this will be stripped
+from the content.
+This is to allow for separation between the option block and content.
+
+"""
+from __future__ import annotations
+
+import datetime
+import re
+from textwrap import dedent
+from typing import Any, Callable
+
+import yaml
+from docutils.parsers.rst import Directive
+from docutils.parsers.rst.directives.misc import TestDirective
+
+
+class DirectiveParsingError(Exception):
+ """Raise on parsing/validation error."""
+
+ pass
+
+
+def parse_directive_text(
+ directive_class: type[Directive],
+ first_line: str,
+ content: str,
+ validate_options: bool = True,
+) -> tuple[list[str], dict, list[str], int]:
+ """Parse (and validate) the full directive text.
+
+ :param first_line: The text on the same line as the directive name.
+ May be an argument or body text, dependent on the directive
+ :param content: All text after the first line. Can include options.
+ :param validate_options: Whether to validate the values of options
+
+ :returns: (arguments, options, body_lines, content_offset)
+ """
+ if directive_class.option_spec:
+ body, options = parse_directive_options(
+ content, directive_class, validate=validate_options
+ )
+ body_lines = body.splitlines()
+ content_offset = len(content.splitlines()) - len(body_lines)
+ else:
+ # If there are no possible options, we do not look for a YAML block
+ options = {}
+ body_lines = content.splitlines()
+ content_offset = 0
+
+ if not (directive_class.required_arguments or directive_class.optional_arguments):
+ # If there are no possible arguments, then the body starts on the argument line
+ if first_line:
+ body_lines.insert(0, first_line)
+ arguments = []
+ else:
+ arguments = parse_directive_arguments(directive_class, first_line)
+
+ # remove first line of body if blank
+ # this is to allow space between the options and the content
+ if body_lines and not body_lines[0].strip():
+ body_lines = body_lines[1:]
+ content_offset += 1
+
+ # check for body content
+ if body_lines and not directive_class.has_content:
+ raise DirectiveParsingError("No content permitted")
+
+ return arguments, options, body_lines, content_offset
+
+
+def parse_directive_options(
+ content: str, directive_class: type[Directive], validate: bool = True
+):
+ """Parse (and validate) the directive option section."""
+ options: dict[str, Any] = {}
+ if content.startswith("---"):
+ content = "\n".join(content.splitlines()[1:])
+ match = re.search(r"^-{3,}", content, re.MULTILINE)
+ if match:
+ yaml_block = content[: match.start()]
+ content = content[match.end() + 1 :] # TODO advance line number
+ else:
+ yaml_block = content
+ content = ""
+ yaml_block = dedent(yaml_block)
+ try:
+ options = yaml.safe_load(yaml_block) or {}
+ except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
+ raise DirectiveParsingError("Invalid options YAML: " + str(error))
+ elif content.lstrip().startswith(":"):
+ content_lines = content.splitlines() # type: list
+ yaml_lines = []
+ while content_lines:
+ if not content_lines[0].lstrip().startswith(":"):
+ break
+ yaml_lines.append(content_lines.pop(0).lstrip()[1:])
+ yaml_block = "\n".join(yaml_lines)
+ content = "\n".join(content_lines)
+ try:
+ options = yaml.safe_load(yaml_block) or {}
+ except (yaml.parser.ParserError, yaml.scanner.ScannerError) as error:
+ raise DirectiveParsingError("Invalid options YAML: " + str(error))
+ if not isinstance(options, dict):
+ raise DirectiveParsingError(f"Invalid options (not dict): {options}")
+
+ if (not validate) or issubclass(directive_class, TestDirective):
+ # technically this directive spec only accepts one option ('option')
+ # but since its for testing only we accept all options
+ return content, options
+
+ # check options against spec
+ options_spec: dict[str, Callable] = directive_class.option_spec
+ for name, value in list(options.items()):
+ try:
+ convertor = options_spec[name]
+ except KeyError:
+ raise DirectiveParsingError(f"Unknown option: {name}")
+ if not isinstance(value, str):
+ if value is True or value is None:
+ value = None # flag converter requires no argument
+ elif isinstance(value, (int, float, datetime.date, datetime.datetime)):
+ # convertor always requires string input
+ value = str(value)
+ else:
+ raise DirectiveParsingError(
+ f'option "{name}" value not string (enclose with ""): {value}'
+ )
+ try:
+ converted_value = convertor(value)
+ except (ValueError, TypeError) as error:
+ raise DirectiveParsingError(
+ "Invalid option value: (option: '{}'; value: {})\n{}".format(
+ name, value, error
+ )
+ )
+ options[name] = converted_value
+
+ return content, options
+
+
+def parse_directive_arguments(directive, arg_text):
+ """Parse (and validate) the directive argument section."""
+ required = directive.required_arguments
+ optional = directive.optional_arguments
+ arguments = arg_text.split()
+ if len(arguments) < required:
+ raise DirectiveParsingError(
+ f"{required} argument(s) required, {len(arguments)} supplied"
+ )
+ elif len(arguments) > required + optional:
+ if directive.final_argument_whitespace:
+ arguments = arg_text.split(None, required + optional - 1)
+ else:
+ raise DirectiveParsingError(
+ "maximum {} argument(s) allowed, {} supplied".format(
+ required + optional, len(arguments)
+ )
+ )
+ return arguments
diff --git a/myst_parser/parsers/docutils_.py b/myst_parser/parsers/docutils_.py
new file mode 100644
index 0000000..aaef5e2
--- /dev/null
+++ b/myst_parser/parsers/docutils_.py
@@ -0,0 +1,275 @@
+"""MyST Markdown parser for docutils."""
+from dataclasses import Field
+from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union
+
+from docutils import frontend, nodes
+from docutils.core import default_description, publish_cmdline
+from docutils.parsers.rst import Parser as RstParser
+from typing_extensions import Literal, get_args, get_origin
+
+from myst_parser.config.main import (
+ MdParserConfig,
+ TopmatterReadError,
+ merge_file_level,
+ read_topmatter,
+)
+from myst_parser.mdit_to_docutils.base import DocutilsRenderer, create_warning
+from myst_parser.parsers.mdit import create_md_parser
+
+
+def _validate_int(
+ setting, value, option_parser, config_parser=None, config_section=None
+) -> int:
+ """Validate an integer setting."""
+ return int(value)
+
+
+def _create_validate_tuple(length: int) -> Callable[..., Tuple[str, ...]]:
+ """Create a validator for a tuple of length `length`."""
+
+ def _validate(
+ setting, value, option_parser, config_parser=None, config_section=None
+ ):
+ string_list = frontend.validate_comma_separated_list(
+ setting, value, option_parser, config_parser, config_section
+ )
+ if len(string_list) != length:
+ raise ValueError(
+ f"Expecting {length} items in {setting}, got {len(string_list)}."
+ )
+ return tuple(string_list)
+
+ return _validate
+
+
+class Unset:
+ """A sentinel class for unset settings."""
+
+ def __repr__(self):
+ return "UNSET"
+
+
+DOCUTILS_UNSET = Unset()
+"""Sentinel for arguments not set through docutils.conf."""
+
+
+DOCUTILS_EXCLUDED_ARGS = (
+ # docutils.conf can't represent callables
+ "heading_slug_func",
+ # docutils.conf can't represent dicts
+ "html_meta",
+ "substitutions",
+ # we can't add substitutions so not needed
+ "sub_delimiters",
+ # sphinx only options
+ "heading_anchors",
+ "ref_domains",
+ "update_mathjax",
+ "mathjax_classes",
+)
+"""Names of settings that cannot be set in docutils.conf."""
+
+
+def _attr_to_optparse_option(at: Field, default: Any) -> Tuple[dict, str]:
+ """Convert a field into a Docutils optparse options dict."""
+ if at.type is int:
+ return {"metavar": "<int>", "validator": _validate_int}, f"(default: {default})"
+ if at.type is bool:
+ return {
+ "metavar": "<boolean>",
+ "validator": frontend.validate_boolean,
+ }, f"(default: {default})"
+ if at.type is str:
+ return {
+ "metavar": "<str>",
+ }, f"(default: '{default}')"
+ if get_origin(at.type) is Literal and all(
+ isinstance(a, str) for a in get_args(at.type)
+ ):
+ args = get_args(at.type)
+ return {
+ "metavar": f"<{'|'.join(repr(a) for a in args)}>",
+ "type": "choice",
+ "choices": args,
+ }, f"(default: {default!r})"
+ if at.type in (Iterable[str], Sequence[str]):
+ return {
+ "metavar": "<comma-delimited>",
+ "validator": frontend.validate_comma_separated_list,
+ }, f"(default: '{','.join(default)}')"
+ if at.type == Tuple[str, str]:
+ return {
+ "metavar": "<str,str>",
+ "validator": _create_validate_tuple(2),
+ }, f"(default: '{','.join(default)}')"
+ if at.type == Union[int, type(None)]:
+ return {
+ "metavar": "<null|int>",
+ "validator": _validate_int,
+ }, f"(default: {default})"
+ if at.type == Union[Iterable[str], type(None)]:
+ default_str = ",".join(default) if default else ""
+ return {
+ "metavar": "<null|comma-delimited>",
+ "validator": frontend.validate_comma_separated_list,
+ }, f"(default: {default_str!r})"
+ raise AssertionError(
+ f"Configuration option {at.name} not set up for use in docutils.conf."
+ )
+
+
+def attr_to_optparse_option(
+ attribute: Field, default: Any, prefix: str = "myst_"
+) -> Tuple[str, List[str], Dict[str, Any]]:
+ """Convert an ``MdParserConfig`` attribute into a Docutils setting tuple.
+
+ :returns: A tuple of ``(help string, option flags, optparse kwargs)``.
+ """
+ name = f"{prefix}{attribute.name}"
+ flag = "--" + name.replace("_", "-")
+ options = {"dest": name, "default": DOCUTILS_UNSET}
+ at_options, type_str = _attr_to_optparse_option(attribute, default)
+ options.update(at_options)
+ help_str = attribute.metadata.get("help", "") if attribute.metadata else ""
+ return (f"{help_str} {type_str}", [flag], options)
+
+
+def create_myst_settings_spec(
+ excluded: Sequence[str], config_cls=MdParserConfig, prefix: str = "myst_"
+):
+ """Return a list of Docutils setting for the docutils MyST section."""
+ defaults = config_cls()
+ return tuple(
+ attr_to_optparse_option(at, getattr(defaults, at.name), prefix)
+ for at in config_cls.get_fields()
+ if at.name not in excluded
+ )
+
+
+def create_myst_config(
+ settings: frontend.Values,
+ excluded: Sequence[str],
+ config_cls=MdParserConfig,
+ prefix: str = "myst_",
+):
+ """Create a configuration instance from the given settings."""
+ values = {}
+ for attribute in config_cls.get_fields():
+ if attribute.name in excluded:
+ continue
+ setting = f"{prefix}{attribute.name}"
+ val = getattr(settings, setting, DOCUTILS_UNSET)
+ if val is not DOCUTILS_UNSET:
+ values[attribute.name] = val
+ return config_cls(**values)
+
+
+class Parser(RstParser):
+ """Docutils parser for Markedly Structured Text (MyST)."""
+
+ supported: Tuple[str, ...] = ("md", "markdown", "myst")
+ """Aliases this parser supports."""
+
+ settings_spec = (
+ "MyST options",
+ None,
+ create_myst_settings_spec(DOCUTILS_EXCLUDED_ARGS),
+ *RstParser.settings_spec,
+ )
+ """Runtime settings specification."""
+
+ config_section = "myst parser"
+ config_section_dependencies = ("parsers",)
+ translate_section_name = None
+
+ def parse(self, inputstring: str, document: nodes.document) -> None:
+ """Parse source text.
+
+ :param inputstring: The source string to parse
+ :param document: The root docutils node to add AST elements to
+ """
+
+ self.setup_parse(inputstring, document)
+
+ # check for exorbitantly long lines
+ if hasattr(document.settings, "line_length_limit"):
+ for i, line in enumerate(inputstring.split("\n")):
+ if len(line) > document.settings.line_length_limit:
+ error = document.reporter.error(
+ f"Line {i+1} exceeds the line-length-limit:"
+ f" {document.settings.line_length_limit}."
+ )
+ document.append(error)
+ return
+
+ # create parsing configuration from the global config
+ try:
+ config = create_myst_config(document.settings, DOCUTILS_EXCLUDED_ARGS)
+ except Exception as exc:
+ error = document.reporter.error(f"Global myst configuration invalid: {exc}")
+ document.append(error)
+ config = MdParserConfig()
+
+ # update the global config with the file-level config
+ try:
+ topmatter = read_topmatter(inputstring)
+ except TopmatterReadError:
+ pass # this will be reported during the render
+ else:
+ if topmatter:
+ warning = lambda wtype, msg: create_warning( # noqa: E731
+ document, msg, line=1, append_to=document, subtype=wtype
+ )
+ config = merge_file_level(config, topmatter, warning)
+
+ # parse content
+ parser = create_md_parser(config, DocutilsRenderer)
+ parser.options["document"] = document
+ parser.render(inputstring)
+
+ # post-processing
+
+ # replace raw nodes if raw is not allowed
+ if not getattr(document.settings, "raw_enabled", True):
+ for node in document.traverse(nodes.raw):
+ warning = document.reporter.warning("Raw content disabled.")
+ node.parent.replace(node, warning)
+
+ self.finish_parse()
+
+
+def _run_cli(writer_name: str, writer_description: str, argv: Optional[List[str]]):
+ """Run the command line interface for a particular writer."""
+ publish_cmdline(
+ parser=Parser(),
+ writer_name=writer_name,
+ description=(
+ f"Generates {writer_description} from standalone MyST sources.\n{default_description}"
+ ),
+ argv=argv,
+ )
+
+
+def cli_html(argv: Optional[List[str]] = None) -> None:
+ """Cmdline entrypoint for converting MyST to HTML."""
+ _run_cli("html", "(X)HTML documents", argv)
+
+
+def cli_html5(argv: Optional[List[str]] = None):
+ """Cmdline entrypoint for converting MyST to HTML5."""
+ _run_cli("html5", "HTML5 documents", argv)
+
+
+def cli_latex(argv: Optional[List[str]] = None):
+ """Cmdline entrypoint for converting MyST to LaTeX."""
+ _run_cli("latex", "LaTeX documents", argv)
+
+
+def cli_xml(argv: Optional[List[str]] = None):
+ """Cmdline entrypoint for converting MyST to XML."""
+ _run_cli("xml", "Docutils-native XML", argv)
+
+
+def cli_pseudoxml(argv: Optional[List[str]] = None):
+ """Cmdline entrypoint for converting MyST to pseudo-XML."""
+ _run_cli("pseudoxml", "pseudo-XML", argv)
diff --git a/myst_parser/parsers/mdit.py b/myst_parser/parsers/mdit.py
new file mode 100644
index 0000000..8476495
--- /dev/null
+++ b/myst_parser/parsers/mdit.py
@@ -0,0 +1,123 @@
+"""This module holds the ``create_md_parser`` function,
+which creates a parser from the config.
+"""
+from __future__ import annotations
+
+from typing import Callable
+
+from markdown_it import MarkdownIt
+from markdown_it.renderer import RendererProtocol
+from mdit_py_plugins.amsmath import amsmath_plugin
+from mdit_py_plugins.anchors import anchors_plugin
+from mdit_py_plugins.attrs import attrs_plugin
+from mdit_py_plugins.colon_fence import colon_fence_plugin
+from mdit_py_plugins.deflist import deflist_plugin
+from mdit_py_plugins.dollarmath import dollarmath_plugin
+from mdit_py_plugins.field_list import fieldlist_plugin
+from mdit_py_plugins.footnote import footnote_plugin
+from mdit_py_plugins.front_matter import front_matter_plugin
+from mdit_py_plugins.myst_blocks import myst_block_plugin
+from mdit_py_plugins.myst_role import myst_role_plugin
+from mdit_py_plugins.substitution import substitution_plugin
+from mdit_py_plugins.tasklists import tasklists_plugin
+from mdit_py_plugins.wordcount import wordcount_plugin
+
+from myst_parser.config.main import MdParserConfig
+
+
+def create_md_parser(
+ config: MdParserConfig, renderer: Callable[[MarkdownIt], RendererProtocol]
+) -> MarkdownIt:
+ """Return a Markdown parser with the required MyST configuration."""
+
+ # TODO warn if linkify required and linkify-it-py not installed
+ # (currently the parse will unceremoniously except)
+
+ if config.commonmark_only:
+ # see https://spec.commonmark.org/
+ md = MarkdownIt("commonmark", renderer_cls=renderer).use(
+ wordcount_plugin, per_minute=config.words_per_minute
+ )
+ md.options.update({"myst_config": config})
+ return md
+
+ if config.gfm_only:
+ # see https://github.github.com/gfm/
+ md = (
+ MarkdownIt("commonmark", renderer_cls=renderer)
+ # note, strikethrough currently only supported tentatively for HTML
+ .enable("strikethrough")
+ .enable("table")
+ .use(tasklists_plugin)
+ .enable("linkify")
+ .use(wordcount_plugin, per_minute=config.words_per_minute)
+ )
+ md.options.update({"linkify": True, "myst_config": config})
+ return md
+
+ md = (
+ MarkdownIt("commonmark", renderer_cls=renderer)
+ .enable("table")
+ .use(front_matter_plugin)
+ .use(myst_block_plugin)
+ .use(myst_role_plugin)
+ .use(footnote_plugin)
+ .use(wordcount_plugin, per_minute=config.words_per_minute)
+ .disable("footnote_inline")
+ # disable this for now, because it need a new implementation in the renderer
+ .disable("footnote_tail")
+ )
+
+ typographer = False
+ if "smartquotes" in config.enable_extensions:
+ md.enable("smartquotes")
+ typographer = True
+ if "replacements" in config.enable_extensions:
+ md.enable("replacements")
+ typographer = True
+ if "linkify" in config.enable_extensions:
+ md.enable("linkify")
+ if md.linkify is not None:
+ md.linkify.set({"fuzzy_link": config.linkify_fuzzy_links})
+ if "strikethrough" in config.enable_extensions:
+ md.enable("strikethrough")
+ if "dollarmath" in config.enable_extensions:
+ md.use(
+ dollarmath_plugin,
+ allow_labels=config.dmath_allow_labels,
+ allow_space=config.dmath_allow_space,
+ allow_digits=config.dmath_allow_digits,
+ double_inline=config.dmath_double_inline,
+ )
+ if "colon_fence" in config.enable_extensions:
+ md.use(colon_fence_plugin)
+ if "amsmath" in config.enable_extensions:
+ md.use(amsmath_plugin)
+ if "deflist" in config.enable_extensions:
+ md.use(deflist_plugin)
+ if "fieldlist" in config.enable_extensions:
+ md.use(fieldlist_plugin)
+ if "tasklist" in config.enable_extensions:
+ md.use(tasklists_plugin)
+ if "substitution" in config.enable_extensions:
+ md.use(substitution_plugin, *config.sub_delimiters)
+ if "attrs_image" in config.enable_extensions:
+ md.use(attrs_plugin, after=("image",))
+ if config.heading_anchors is not None:
+ md.use(
+ anchors_plugin,
+ max_level=config.heading_anchors,
+ slug_func=config.heading_slug_func,
+ )
+ for name in config.disable_syntax:
+ md.disable(name, True)
+
+ md.options.update(
+ {
+ "typographer": typographer,
+ "linkify": "linkify" in config.enable_extensions,
+ "myst_config": config,
+ }
+ )
+
+ return md
diff --git a/myst_parser/parsers/parse_html.py b/myst_parser/parsers/parse_html.py
new file mode 100644
index 0000000..7539e42
--- /dev/null
+++ b/myst_parser/parsers/parse_html.py
@@ -0,0 +1,440 @@
+"""A simple but complete HTML to Abstract Syntax Tree (AST) parser.
+
+The AST can also reproduce the HTML text.
+
+Example::
+
+ >> text = '<div class="note"><p>text</p></div>'
+ >> ast = tokenize_html(text)
+ >> list(ast.walk(include_self=True))
+ [Root(''), Tag('div', {'class': 'note'}), Tag('p'), Data('text')]
+ >> str(ast)
+ '<div class="note"><p>text</p></div>'
+ >> str(ast[0][0])
+ '<p>text</p>'
+
+Note: optional tags are not accounted for
+(see https://html.spec.whatwg.org/multipage/syntax.html#optional-tags)
+
+"""
+from __future__ import annotations
+
+import inspect
+import itertools
+from collections import abc, deque
+from html.parser import HTMLParser
+from typing import Any, Callable, Iterable, Iterator
+
+
+class Attribute(dict):
+ """This class holds the tags's attributes."""
+
+ def __getitem__(self, key: str) -> str:
+ """If self doesn't have the key it returns ''."""
+ return self.get(key, "")
+
+ @property
+ def classes(self) -> list[str]:
+ """Return 'class' attribute as list."""
+ return self["class"].split()
+
+ def __str__(self) -> str:
+ """Return a htmlized representation for attributes."""
+ return " ".join(f'{key}="{value}"' for key, value in self.items())
+
+
+class Element(abc.MutableSequence):
+ """An Element of the xml/html document.
+
+ All xml/html entities inherit from this class.
+ """
+
+ def __init__(self, name: str = "", attr: dict | None = None) -> None:
+ """Initialise the element."""
+ self.name = name
+ self.attrs: Attribute = Attribute(attr or {})
+ self._parent: Element | None = None
+ self._children: list[Element] = []
+
+ @property
+ def parent(self) -> Element | None:
+ """Return parent."""
+ return self._parent
+
+ @property
+ def children(self) -> list[Element]:
+ """Return copy of children."""
+ return self._children[:]
+
+ def reset_children(self, children: list[Element], deepcopy: bool = False):
+ new_children = []
+ for i, item in enumerate(children):
+ assert isinstance(item, Element)
+ if deepcopy:
+ item = item.deepcopy()
+ if item._parent is None:
+ item._parent = self
+ elif item._parent != self:
+ raise AssertionError(f"different parent already set for item {i}")
+ new_children.append(item)
+ self._children = new_children
+
+ def __getitem__(self, index: int) -> Element: # type: ignore[override]
+ return self._children[index]
+
+ def __setitem__(self, index: int, item: Element): # type: ignore[override]
+ assert isinstance(item, Element)
+ if item._parent is not None and item._parent != self:
+ raise AssertionError(f"different parent already set for: {item!r}")
+ item._parent = self
+ return self._children.__setitem__(index, item)
+
+ def __delitem__(self, index: int): # type: ignore[override]
+ return self._children.__delitem__(index)
+
+ def __len__(self) -> int:
+ return self._children.__len__()
+
+ def __iter__(self) -> Iterator[Element]:
+ yield from self._children
+
+ def insert(self, index: int, item: Element):
+ assert isinstance(item, Element)
+ if item._parent is not None and item._parent != self:
+ raise AssertionError(f"different parent already set for: {item!r}")
+ item._parent = self
+ return self._children.insert(index, item)
+
+ def deepcopy(self) -> Element:
+ """Recursively copy and remove parent."""
+ _copy = self.__class__(self.name, self.attrs)
+ for child in self:
+ _copy_child = child.deepcopy()
+ _copy.append(_copy_child)
+ return _copy
+
+ def __repr__(self) -> str:
+ text = f"{self.__class__.__name__}({self.name!r}"
+ if self.attrs:
+ text += f", {self.attrs!r}"
+ text += ")"
+ return text
+
+ def render(
+ self,
+ tag_overrides: dict[str, Callable[[Element, dict], str]] | None = None,
+ **kwargs,
+ ) -> str:
+ """Returns a HTML string representation of the element.
+
+ :param tag_overrides: Provide a dictionary of render function
+ for specific tag names, to override the normal render format
+
+ """
+ raise NotImplementedError
+
+ def __str__(self) -> str:
+ return self.render()
+
+ def __eq__(self, item: Any) -> bool:
+ return item is self
+
+ def walk(self, include_self: bool = False) -> Iterator[Element]:
+ """Walk through the xml/html AST."""
+ if include_self:
+ yield self
+ for child in self:
+ yield child
+ yield from child.walk()
+
+ def strip(self, inplace: bool = False, recurse: bool = False) -> Element:
+ """Return copy with all `Data` tokens
+ that only contain whitespace / newlines removed.
+ """
+ element = self
+ if not inplace:
+ element = self.deepcopy()
+ element.reset_children(
+ [
+ e
+ for e in element.children
+ if not (isinstance(e, Data) and e.data.strip() == "")
+ ]
+ )
+ if recurse:
+ for child in element:
+ child.strip(inplace=True, recurse=True)
+ return element
+
+ def find(
+ self,
+ identifier: str | type[Element],
+ attrs: dict | None = None,
+ classes: Iterable[str] | None = None,
+ include_self: bool = False,
+ recurse: bool = True,
+ ) -> Iterator[Element]:
+ """Find all elements that match name and specific attributes."""
+ iterator = self.walk() if recurse else self
+ if include_self:
+ iterator = itertools.chain([self], iterator)
+ if inspect.isclass(identifier):
+ test_func = lambda c: isinstance(c, identifier) # noqa: E731
+ else:
+ test_func = lambda c: c.name == identifier # noqa: E731
+ classes = set(classes) if classes is not None else classes
+ for child in iterator:
+ if test_func(child):
+ if classes is not None and not classes.issubset(child.attrs.classes):
+ continue
+ for key, value in (attrs or {}).items():
+ if child.attrs[key] != value:
+ break
+ else:
+ yield child
+
+
+class Root(Element):
+ """The root of the AST tree."""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ """Returns a string HTML representation of the structure."""
+ return "".join(child.render(**kwargs) for child in self)
+
+
+class Tag(Element):
+ """Represent xml/html tags under the form: <name key="value" ...> ... </name>."""
+
+ def render(
+ self,
+ tag_overrides: dict[str, Callable[[Element, dict], str]] | None = None,
+ **kwargs,
+ ) -> str:
+ if tag_overrides and self.name in tag_overrides:
+ return tag_overrides[self.name](self, tag_overrides)
+ return (
+ f"<{self.name}{' ' if self.attrs else ''}{self.attrs}>"
+ + "".join(
+ child.render(tag_overrides=tag_overrides, **kwargs) for child in self
+ )
+ + f"</{self.name}>"
+ )
+
+
+class XTag(Element):
+ """Represent XHTML style tags with no children, like `<img src="t.gif" />`"""
+
+ def render(
+ self,
+ tag_overrides: dict[str, Callable[[Element, dict], str]] | None = None,
+ **kwargs,
+ ) -> str:
+ if tag_overrides is not None and self.name in tag_overrides:
+ return tag_overrides[self.name](self, tag_overrides)
+ return f"<{self.name}{' ' if self.attrs else ''}{self.attrs}/>"
+
+
+class VoidTag(Element):
+ """Represent tags with no children, only start tag, like `<img src="t.gif" >`"""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return f"<{self.name}{' ' if self.attrs else ''}{self.attrs}>"
+
+
+class TerminalElement(Element):
+ def __init__(self, data: str):
+ super().__init__("")
+ self.data: str = data
+
+ def __repr__(self) -> str:
+ text = self.data
+ if len(text) > 20:
+ text = text[:17] + "..."
+ return f"{self.__class__.__name__}({text!r})"
+
+ def deepcopy(self) -> TerminalElement:
+ """Copy and remove parent."""
+ _copy = self.__class__(self.data)
+ return _copy
+
+
+class Data(TerminalElement):
+ """Represent data inside xml/html documents, like raw text."""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return self.data
+
+
+class Declaration(TerminalElement):
+ """Represent declarations, like `<!DOCTYPE html>`"""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return f"<!{self.data}>"
+
+
+class Comment(TerminalElement):
+ """Represent HTML comments"""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return f"<!--{self.data}-->"
+
+
+class Pi(TerminalElement):
+ """Represent processing instructions like `<?xml-stylesheet ?>`"""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return f"<?{self.data}>"
+
+
+class Char(TerminalElement):
+ """Represent character codes like: `&#0`"""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return f"&#{self.data};"
+
+
+class Entity(TerminalElement):
+ """Represent entities like `&amp`"""
+
+ def render(self, **kwargs) -> str: # type: ignore[override]
+ return f"&{self.data};"
+
+
+class Tree:
+ """The engine class to generate the AST tree."""
+
+ def __init__(self, name: str = ""):
+ """Initialise Tree"""
+ self.name = name
+ self.outmost = Root(name)
+ self.stack: deque = deque()
+ self.stack.append(self.outmost)
+
+ def clear(self):
+ """Clear the outmost and stack for a new parsing."""
+ self.outmost = Root(self.name)
+ self.stack.clear()
+ self.stack.append(self.outmost)
+
+ def last(self) -> Element:
+ """Return the last pointer which point to the actual tag scope."""
+ return self.stack[-1]
+
+ def nest_tag(self, name: str, attrs: dict):
+ """Nest a given tag at the bottom of the tree using
+ the last stack's pointer.
+ """
+ pointer = self.stack.pop()
+ item = Tag(name, attrs)
+ pointer.append(item)
+ self.stack.append(pointer)
+ self.stack.append(item)
+
+ def nest_xtag(self, name: str, attrs: dict):
+ """Nest an XTag onto the tree."""
+ top = self.last()
+ item = XTag(name, attrs)
+ top.append(item)
+
+ def nest_vtag(self, name: str, attrs: dict):
+ """Nest a VoidTag onto the tree."""
+ top = self.last()
+ item = VoidTag(name, attrs)
+ top.append(item)
+
+ def nest_terminal(self, klass: type[TerminalElement], data: str):
+ """Nest the data onto the tree."""
+ top = self.last()
+ item = klass(data)
+ top.append(item)
+
+ def enclose(self, name: str):
+ """When a closing tag is found, pop the pointer's scope from the stack,
+ to then point to the earlier scope's tag.
+ """
+ count = 0
+ for ind in reversed(self.stack):
+ count = count + 1
+ if ind.name == name:
+ break
+ else:
+ count = 0
+
+ # It pops all the items which do not match with the closing tag.
+ for _ in range(0, count):
+ self.stack.pop()
+
+
+class HtmlToAst(HTMLParser):
+ """The tokenizer class."""
+
+ # see https://html.spec.whatwg.org/multipage/syntax.html#void-elements
+ void_elements = {
+ "area",
+ "base",
+ "br",
+ "col",
+ "embed",
+ "hr",
+ "img",
+ "input",
+ "link",
+ "meta",
+ "param",
+ "source",
+ "track",
+ "wbr",
+ }
+
+ def __init__(self, name: str = "", convert_charrefs: bool = False):
+ super().__init__(convert_charrefs=convert_charrefs)
+ self.struct = Tree(name)
+
+ def feed(self, source: str) -> Root: # type: ignore[override]
+ """Parse the source string."""
+ self.struct.clear()
+ super().feed(source)
+ return self.struct.outmost
+
+ def handle_starttag(self, name: str, attr):
+ """When found an opening tag then nest it onto the tree."""
+ if name in self.void_elements:
+ self.struct.nest_vtag(name, attr)
+ else:
+ self.struct.nest_tag(name, attr)
+
+ def handle_startendtag(self, name: str, attr):
+ """When found a XHTML tag style then nest it up to the tree."""
+ self.struct.nest_xtag(name, attr)
+
+ def handle_endtag(self, name: str):
+ """When found a closing tag then makes it point to the right scope."""
+ if name not in self.void_elements:
+ self.struct.enclose(name)
+
+ def handle_data(self, data: str):
+ """Nest data onto the tree."""
+ self.struct.nest_terminal(Data, data)
+
+ def handle_decl(self, decl: str):
+ self.struct.nest_terminal(Declaration, decl)
+
+ def unknown_decl(self, decl: str):
+ self.struct.nest_terminal(Declaration, decl)
+
+ def handle_charref(self, data: str):
+ self.struct.nest_terminal(Char, data)
+
+ def handle_entityref(self, data: str):
+ self.struct.nest_terminal(Entity, data)
+
+ def handle_pi(self, data: str):
+ self.struct.nest_terminal(Pi, data)
+
+ def handle_comment(self, data: str):
+ self.struct.nest_terminal(Comment, data)
+
+
+def tokenize_html(text: str, name: str = "", convert_charrefs: bool = False) -> Root:
+ parser = HtmlToAst(name, convert_charrefs=convert_charrefs)
+ return parser.feed(text)
diff --git a/myst_parser/parsers/sphinx_.py b/myst_parser/parsers/sphinx_.py
new file mode 100644
index 0000000..fff098f
--- /dev/null
+++ b/myst_parser/parsers/sphinx_.py
@@ -0,0 +1,69 @@
+"""MyST Markdown parser for sphinx."""
+from __future__ import annotations
+
+from docutils import nodes
+from docutils.parsers.rst import Parser as RstParser
+from sphinx.parsers import Parser as SphinxParser
+from sphinx.util import logging
+
+from myst_parser.config.main import (
+ MdParserConfig,
+ TopmatterReadError,
+ merge_file_level,
+ read_topmatter,
+)
+from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer, create_warning
+from myst_parser.parsers.mdit import create_md_parser
+
+SPHINX_LOGGER = logging.getLogger(__name__)
+
+
+class MystParser(SphinxParser):
+ """Sphinx parser for Markedly Structured Text (MyST)."""
+
+ supported: tuple[str, ...] = ("md", "markdown", "myst")
+ """Aliases this parser supports."""
+
+ settings_spec = RstParser.settings_spec
+ """Runtime settings specification.
+
+ Defines runtime settings and associated command-line options, as used by
+ `docutils.frontend.OptionParser`. This is a concatenation of tuples of:
+
+ - Option group title (string or `None` which implies no group, just a list
+ of single options).
+
+ - Description (string or `None`).
+
+ - A sequence of option tuples
+ """
+
+ config_section = "myst parser"
+ config_section_dependencies = ("parsers",)
+ translate_section_name = None
+
+ def parse(self, inputstring: str, document: nodes.document) -> None:
+ """Parse source text.
+
+ :param inputstring: The source string to parse
+ :param document: The root docutils node to add AST elements to
+
+ """
+ # get the global config
+ config: MdParserConfig = document.settings.env.myst_config
+
+ # update the global config with the file-level config
+ try:
+ topmatter = read_topmatter(inputstring)
+ except TopmatterReadError:
+ pass # this will be reported during the render
+ else:
+ if topmatter:
+ warning = lambda wtype, msg: create_warning( # noqa: E731
+ document, msg, line=1, append_to=document, subtype=wtype
+ )
+ config = merge_file_level(config, topmatter, warning)
+
+ parser = create_md_parser(config, SphinxRenderer)
+ parser.options["document"] = document
+ parser.render(inputstring)
diff --git a/myst_parser/py.typed b/myst_parser/py.typed
new file mode 100644
index 0000000..7632ecf
--- /dev/null
+++ b/myst_parser/py.typed
@@ -0,0 +1 @@
+# Marker file for PEP 561
diff --git a/myst_parser/sphinx_.py b/myst_parser/sphinx_.py
new file mode 100644
index 0000000..b085086
--- /dev/null
+++ b/myst_parser/sphinx_.py
@@ -0,0 +1,6 @@
+"""A module for compatibility with the docutils>=0.17 `include` directive, in RST documents::
+
+ .. include:: path/to/file.md
+ :parser: myst_parser.sphinx_
+"""
+from myst_parser.parsers.sphinx_ import MystParser as Parser # noqa: F401
diff --git a/myst_parser/sphinx_ext/__init__.py b/myst_parser/sphinx_ext/__init__.py
new file mode 100644
index 0000000..1bfeb71
--- /dev/null
+++ b/myst_parser/sphinx_ext/__init__.py
@@ -0,0 +1 @@
+"""Sphinx extension for myst_parser."""
diff --git a/myst_parser/sphinx_ext/directives.py b/myst_parser/sphinx_ext/directives.py
new file mode 100644
index 0000000..39ca2c6
--- /dev/null
+++ b/myst_parser/sphinx_ext/directives.py
@@ -0,0 +1,136 @@
+"""MyST specific directives"""
+from copy import copy
+from typing import List, Tuple, cast
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from sphinx.directives import SphinxDirective
+from sphinx.util.docutils import SphinxRole
+
+from myst_parser.mocking import MockState
+
+
+def align(argument):
+ return directives.choice(argument, ("left", "center", "right"))
+
+
+def figwidth_value(argument):
+ if argument.lower() == "image":
+ return "image"
+ else:
+ return directives.length_or_percentage_or_unitless(argument, "px")
+
+
+class SubstitutionReferenceRole(SphinxRole):
+ """Implement substitution references as a role.
+
+ Note, in ``docutils/parsers/rst/roles.py`` this is left unimplemented.
+ """
+
+ def run(self) -> Tuple[List[nodes.Node], List[nodes.system_message]]:
+ subref_node = nodes.substitution_reference(self.rawtext, self.text)
+ self.set_source_info(subref_node, self.lineno)
+ subref_node["refname"] = nodes.fully_normalize_name(self.text)
+ return [subref_node], []
+
+
+class FigureMarkdown(SphinxDirective):
+ """Directive for creating a figure with Markdown compatible syntax.
+
+ Example::
+
+ :::{figure-md} target
+ <img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+
+ This is a caption in **Markdown**
+ :::
+
+ """
+
+ required_arguments = 0
+ optional_arguments = 1 # image target
+ final_argument_whitespace = True
+ has_content = True
+
+ option_spec = {
+ "width": figwidth_value,
+ "class": directives.class_option,
+ "align": align,
+ "name": directives.unchanged,
+ }
+
+ def run(self) -> List[nodes.Node]:
+ figwidth = self.options.pop("width", None)
+ figclasses = self.options.pop("class", None)
+ align = self.options.pop("align", None)
+
+ if not isinstance(self.state, MockState):
+ return [self.figure_error("Directive is only supported in myst parser")]
+ state = cast(MockState, self.state)
+
+ # ensure html image enabled
+ myst_extensions = copy(state._renderer.md_config.enable_extensions)
+ node = nodes.Element()
+ try:
+ state._renderer.md_config.enable_extensions = list(
+ state._renderer.md_config.enable_extensions
+ ) + ["html_image"]
+ state.nested_parse(self.content, self.content_offset, node)
+ finally:
+ state._renderer.md_config.enable_extensions = myst_extensions
+
+ if not len(node.children) == 2:
+ return [
+ self.figure_error(
+ "content should be one image, "
+ "followed by a single paragraph caption"
+ )
+ ]
+
+ image_node, caption_para = node.children
+ if isinstance(image_node, nodes.paragraph):
+ image_node = image_node[0]
+
+ if not isinstance(image_node, nodes.image):
+ return [
+ self.figure_error(
+ "content should be one image (not found), "
+ "followed by single paragraph caption"
+ )
+ ]
+
+ if not isinstance(caption_para, nodes.paragraph):
+ return [
+ self.figure_error(
+ "content should be one image, "
+ "followed by single paragraph caption (not found)"
+ )
+ ]
+
+ caption_node = nodes.caption(caption_para.rawsource, "", *caption_para.children)
+ caption_node.source = caption_para.source
+ caption_node.line = caption_para.line
+
+ figure_node = nodes.figure("", image_node, caption_node)
+ self.set_source_info(figure_node)
+
+ if figwidth is not None:
+ figure_node["width"] = figwidth
+ if figclasses:
+ figure_node["classes"] += figclasses
+ if align:
+ figure_node["align"] = align
+ if self.arguments:
+ self.options["name"] = self.arguments[0]
+ self.add_name(figure_node)
+
+ return [figure_node]
+
+ def figure_error(self, message):
+ """A warning for reporting an invalid figure."""
+ error = self.state_machine.reporter.error(
+ message,
+ nodes.literal_block(self.block_text, self.block_text),
+ line=self.lineno,
+ )
+ return error
diff --git a/myst_parser/sphinx_ext/main.py b/myst_parser/sphinx_ext/main.py
new file mode 100644
index 0000000..f5aeffc
--- /dev/null
+++ b/myst_parser/sphinx_ext/main.py
@@ -0,0 +1,60 @@
+"""The setup for the sphinx extension."""
+from typing import Any
+
+from sphinx.application import Sphinx
+
+
+def setup_sphinx(app: Sphinx, load_parser=False):
+ """Initialize all settings and transforms in Sphinx."""
+ # we do this separately to setup,
+ # so that it can be called by external packages like myst_nb
+ from myst_parser.config.main import MdParserConfig
+ from myst_parser.parsers.sphinx_ import MystParser
+ from myst_parser.sphinx_ext.directives import (
+ FigureMarkdown,
+ SubstitutionReferenceRole,
+ )
+ from myst_parser.sphinx_ext.mathjax import override_mathjax
+ from myst_parser.sphinx_ext.myst_refs import MystReferenceResolver
+
+ if load_parser:
+ app.add_source_suffix(".md", "markdown")
+ app.add_source_parser(MystParser)
+
+ app.add_role("sub-ref", SubstitutionReferenceRole())
+ app.add_directive("figure-md", FigureMarkdown)
+
+ app.add_post_transform(MystReferenceResolver)
+
+ for name, default, field in MdParserConfig().as_triple():
+ if not field.metadata.get("docutils_only", False):
+ # TODO add types?
+ app.add_config_value(f"myst_{name}", default, "env", types=Any)
+
+ app.connect("builder-inited", create_myst_config)
+ app.connect("builder-inited", override_mathjax)
+
+
+def create_myst_config(app):
+ from sphinx.util import logging
+
+ # Ignore type checkers because the attribute is dynamically assigned
+ from sphinx.util.console import bold # type: ignore[attr-defined]
+
+ from myst_parser import __version__
+ from myst_parser.config.main import MdParserConfig
+
+ logger = logging.getLogger(__name__)
+
+ values = {
+ name: app.config[f"myst_{name}"]
+ for name, _, field in MdParserConfig().as_triple()
+ if not field.metadata.get("docutils_only", False)
+ }
+
+ try:
+ app.env.myst_config = MdParserConfig(**values)
+ logger.info(bold("myst v%s:") + " %s", __version__, app.env.myst_config)
+ except (TypeError, ValueError) as error:
+ logger.error("myst configuration invalid: %s", error.args[0])
+ app.env.myst_config = MdParserConfig()
diff --git a/myst_parser/sphinx_ext/mathjax.py b/myst_parser/sphinx_ext/mathjax.py
new file mode 100644
index 0000000..260f008
--- /dev/null
+++ b/myst_parser/sphinx_ext/mathjax.py
@@ -0,0 +1,118 @@
+"""Overrides to ``sphinx.ext.mathjax``
+
+This fixes two issues:
+
+1. Mathjax should not search for ``$`` delimiters, nor LaTeX amsmath environments,
+ since we already achieve this with the dollarmath and amsmath mrakdown-it-py plugins
+2. amsmath math blocks should be wrapped in mathjax delimiters (default ``\\[...\\]``),
+ and assigned an equation number
+
+"""
+from docutils import nodes
+from sphinx.application import Sphinx
+from sphinx.ext import mathjax
+from sphinx.locale import _
+from sphinx.util import logging
+from sphinx.util.math import get_node_equation_number
+from sphinx.writers.html import HTMLTranslator
+
+logger = logging.getLogger(__name__)
+
+
+def log_override_warning(app: Sphinx, version: int, current: str, new: str) -> None:
+ """Log a warning if MathJax configuration being overridden."""
+ if logging.is_suppressed_warning("myst", "mathjax", app.config.suppress_warnings):
+ return
+ config_name = (
+ "mathjax3_config['options']['processHtmlClass']"
+ if version == 3
+ else "mathjax_config['tex2jax']['processClass']"
+ )
+ logger.warning(
+ f"`{config_name}` is being overridden by myst-parser: '{current}' -> '{new}'. "
+ "Set `suppress_warnings=['myst.mathjax']` to ignore this warning, or "
+ "`myst_update_mathjax=False` if this is undesirable."
+ )
+
+
+def override_mathjax(app: Sphinx):
+ """Override aspects of the mathjax extension.
+
+ MyST-Parser parses dollar and latex math, via markdown-it plugins.
+ Therefore, we tell Mathjax to only render these HTML elements.
+ This is accompanied by setting the `ignoreClass` on the top-level section of each MyST document.
+ """
+ if (
+ "amsmath" in app.config["myst_enable_extensions"]
+ and "mathjax" in app.registry.html_block_math_renderers
+ ):
+ app.registry.html_block_math_renderers["mathjax"] = (
+ html_visit_displaymath, # type: ignore[assignment]
+ None,
+ )
+
+ if "dollarmath" not in app.config["myst_enable_extensions"]:
+ return
+ if not app.env.myst_config.update_mathjax: # type: ignore
+ return
+
+ mjax_classes = app.env.myst_config.mathjax_classes # type: ignore
+
+ if "mathjax3_config" in app.config:
+ # sphinx 4 + mathjax 3
+ app.config.mathjax3_config = app.config.mathjax3_config or {} # type: ignore
+ app.config.mathjax3_config.setdefault("options", {})
+ if (
+ "processHtmlClass" in app.config.mathjax3_config["options"]
+ and app.config.mathjax3_config["options"]["processHtmlClass"]
+ != mjax_classes
+ ):
+ log_override_warning(
+ app,
+ 3,
+ app.config.mathjax3_config["options"]["processHtmlClass"],
+ mjax_classes,
+ )
+ app.config.mathjax3_config["options"]["processHtmlClass"] = mjax_classes
+ elif "mathjax_config" in app.config:
+ # sphinx 3 + mathjax 2
+ app.config.mathjax_config = app.config.mathjax_config or {} # type: ignore[attr-defined]
+ app.config.mathjax_config.setdefault("tex2jax", {})
+ if (
+ "processClass" in app.config.mathjax_config["tex2jax"]
+ and app.config.mathjax_config["tex2jax"]["processClass"] != mjax_classes
+ ):
+ log_override_warning(
+ app,
+ 2,
+ app.config.mathjax_config["tex2jax"]["processClass"],
+ mjax_classes,
+ )
+ app.config.mathjax_config["tex2jax"]["processClass"] = mjax_classes
+
+
+def html_visit_displaymath(self: HTMLTranslator, node: nodes.math_block) -> None:
+ """Override for sphinx.ext.mathjax.html_visit_displaymath to handle amsmath.
+
+ By default displaymath, are normally wrapped in a prefix/suffix,
+ defined by mathjax_display, and labelled nodes are numbered.
+ However, this is not the case if the math_block is set as 'nowrap', as for amsmath.
+ Therefore, we need to override this behaviour.
+ """
+ if "amsmath" in node.get("classes", []):
+ self.body.append(
+ self.starttag(node, "div", CLASS="math notranslate nohighlight amsmath")
+ )
+ if node["number"]:
+ number = get_node_equation_number(self, node)
+ self.body.append('<span class="eqno">(%s)' % number)
+ self.add_permalink_ref(node, _("Permalink to this equation"))
+ self.body.append("</span>")
+ prefix, suffix = self.builder.config.mathjax_display
+ self.body.append(prefix)
+ self.body.append(self.encode(node.astext()))
+ self.body.append(suffix)
+ self.body.append("</div>\n")
+ raise nodes.SkipNode
+
+ return mathjax.html_visit_displaymath(self, node)
diff --git a/myst_parser/sphinx_ext/myst_refs.py b/myst_parser/sphinx_ext/myst_refs.py
new file mode 100644
index 0000000..f364345
--- /dev/null
+++ b/myst_parser/sphinx_ext/myst_refs.py
@@ -0,0 +1,282 @@
+"""A post-transform for overriding the behaviour of sphinx reference resolution.
+
+This is applied to MyST type references only, such as ``[text](target)``,
+and allows for nested syntax
+"""
+import os
+from typing import Any, List, Optional, Tuple, cast
+
+from docutils import nodes
+from docutils.nodes import Element, document
+from sphinx import addnodes, version_info
+from sphinx.addnodes import pending_xref
+from sphinx.domains.std import StandardDomain
+from sphinx.locale import __
+from sphinx.transforms.post_transforms import ReferencesResolver
+from sphinx.util import docname_join, logging
+from sphinx.util.nodes import clean_astext, make_refnode
+
+from myst_parser._compat import findall
+
+try:
+ from sphinx.errors import NoUri
+except ImportError:
+ # sphinx < 2.1
+ from sphinx.environment import NoUri # type: ignore
+
+logger = logging.getLogger(__name__)
+
+
+class MystReferenceResolver(ReferencesResolver):
+ """Resolves cross-references on doctrees.
+
+ Overrides default sphinx implementation, to allow for nested syntax
+ """
+
+ default_priority = 9 # higher priority than ReferencesResolver (10)
+
+ def run(self, **kwargs: Any) -> None:
+ self.document: document
+ for node in findall(self.document)(addnodes.pending_xref):
+ if node["reftype"] != "myst":
+ continue
+
+ contnode = cast(nodes.TextElement, node[0].deepcopy())
+ newnode = None
+
+ target = node["reftarget"]
+ refdoc = node.get("refdoc", self.env.docname)
+ domain = None
+
+ try:
+ newnode = self.resolve_myst_ref(refdoc, node, contnode)
+ if newnode is None:
+ # no new node found? try the missing-reference event
+ # but first we change the the reftype to 'any'
+ # this means it is picked up by extensions like intersphinx
+ node["reftype"] = "any"
+ try:
+ newnode = self.app.emit_firstresult(
+ "missing-reference",
+ self.env,
+ node,
+ contnode,
+ **(
+ {"allowed_exceptions": (NoUri,)}
+ if version_info[0] > 2
+ else {}
+ ),
+ )
+ finally:
+ node["reftype"] = "myst"
+ # still not found? warn if node wishes to be warned about or
+ # we are in nit-picky mode
+ if newnode is None:
+ node["refdomain"] = ""
+ # TODO ideally we would override the warning message here,
+ # to show the [ref.myst] for suppressing warning
+ self.warn_missing_reference(
+ refdoc, node["reftype"], target, node, domain
+ )
+ except NoUri:
+ newnode = contnode
+
+ node.replace_self(newnode or contnode)
+
+ def resolve_myst_ref(
+ self, refdoc: str, node: pending_xref, contnode: Element
+ ) -> Element:
+ """Resolve reference generated by the "myst" role; ``[text](reference)``.
+
+ This builds on the sphinx ``any`` role to also resolve:
+
+ - Document references with extensions; ``[text](./doc.md)``
+ - Document references with anchors with anchors; ``[text](./doc.md#target)``
+ - Nested syntax for explicit text with std:doc and std:ref;
+ ``[**nested**](reference)``
+
+ """
+ target = node["reftarget"] # type: str
+ results = [] # type: List[Tuple[str, Element]]
+
+ res_anchor = self._resolve_anchor(node, refdoc)
+ if res_anchor:
+ results.append(("std:doc", res_anchor))
+ else:
+ # if we've already found an anchored doc,
+ # don't search in the std:ref/std:doc (leads to duplication)
+
+ # resolve standard references
+ res = self._resolve_ref_nested(node, refdoc)
+ if res:
+ results.append(("std:ref", res))
+
+ # resolve doc names
+ res = self._resolve_doc_nested(node, refdoc)
+ if res:
+ results.append(("std:doc", res))
+
+ # get allowed domains for referencing
+ ref_domains = self.env.config.myst_ref_domains
+
+ assert self.app.builder
+
+ # next resolve for any other standard reference objects
+ if ref_domains is None or "std" in ref_domains:
+ stddomain = cast(StandardDomain, self.env.get_domain("std"))
+ for objtype in stddomain.object_types:
+ key = (objtype, target)
+ if objtype == "term":
+ key = (objtype, target.lower())
+ if key in stddomain.objects:
+ docname, labelid = stddomain.objects[key]
+ domain_role = "std:" + stddomain.role_for_objtype(objtype)
+ ref_node = make_refnode(
+ self.app.builder, refdoc, docname, labelid, contnode
+ )
+ results.append((domain_role, ref_node))
+
+ # finally resolve for any other type of allowed reference domain
+ for domain in self.env.domains.values():
+ if domain.name == "std":
+ continue # we did this one already
+ if ref_domains is not None and domain.name not in ref_domains:
+ continue
+ try:
+ results.extend(
+ domain.resolve_any_xref(
+ self.env, refdoc, self.app.builder, target, node, contnode
+ )
+ )
+ except NotImplementedError:
+ # the domain doesn't yet support the new interface
+ # we have to manually collect possible references (SLOW)
+ if not (getattr(domain, "__module__", "").startswith("sphinx.")):
+ logger.warning(
+ f"Domain '{domain.__module__}::{domain.name}' has not "
+ "implemented a `resolve_any_xref` method [myst.domains]",
+ type="myst",
+ subtype="domains",
+ once=True,
+ )
+ for role in domain.roles:
+ res = domain.resolve_xref(
+ self.env, refdoc, self.app.builder, role, target, node, contnode
+ )
+ if res and len(res) and isinstance(res[0], nodes.Element):
+ results.append((f"{domain.name}:{role}", res))
+
+ # now, see how many matches we got...
+ if not results:
+ return None
+ if len(results) > 1:
+
+ def stringify(name, node):
+ reftitle = node.get("reftitle", node.astext())
+ return f":{name}:`{reftitle}`"
+
+ candidates = " or ".join(stringify(name, role) for name, role in results)
+ logger.warning(
+ __(
+ f"more than one target found for 'myst' cross-reference {target}: "
+ f"could be {candidates} [myst.ref]"
+ ),
+ location=node,
+ type="myst",
+ subtype="ref",
+ )
+
+ res_role, newnode = results[0]
+ # Override "myst" class with the actual role type to get the styling
+ # approximately correct.
+ res_domain = res_role.split(":")[0]
+ if len(newnode) > 0 and isinstance(newnode[0], nodes.Element):
+ newnode[0]["classes"] = newnode[0].get("classes", []) + [
+ res_domain,
+ res_role.replace(":", "-"),
+ ]
+
+ return newnode
+
+ def _resolve_anchor(
+ self, node: pending_xref, fromdocname: str
+ ) -> Optional[Element]:
+ """Resolve doc with anchor."""
+ if self.env.config.myst_heading_anchors is None:
+ # no target anchors will have been created, so we don't look for them
+ return None
+ target = node["reftarget"] # type: str
+ if "#" not in target:
+ return None
+ # the link may be a heading anchor; we need to first get the relative path
+ rel_path, anchor = target.rsplit("#", 1)
+ rel_path = os.path.normpath(rel_path)
+ if rel_path == ".":
+ # anchor in the same doc as the node
+ doc_path = self.env.doc2path(node.get("refdoc", fromdocname), base=False)
+ else:
+ # anchor in a different doc from the node
+ doc_path = os.path.normpath(
+ os.path.join(node.get("refdoc", fromdocname), "..", rel_path)
+ )
+ return self._resolve_ref_nested(node, fromdocname, doc_path + "#" + anchor)
+
+ def _resolve_ref_nested(
+ self, node: pending_xref, fromdocname: str, target=None
+ ) -> Optional[Element]:
+ """This is the same as ``sphinx.domains.std._resolve_ref_xref``,
+ but allows for nested syntax, rather than converting the inner node to raw text.
+ """
+ stddomain = cast(StandardDomain, self.env.get_domain("std"))
+ target = target or node["reftarget"].lower()
+
+ if node["refexplicit"]:
+ # reference to anonymous label; the reference uses
+ # the supplied link caption
+ docname, labelid = stddomain.anonlabels.get(target, ("", ""))
+ sectname = node.astext()
+ innernode = nodes.inline(sectname, "")
+ innernode.extend(node[0].children)
+ else:
+ # reference to named label; the final node will
+ # contain the section name after the label
+ docname, labelid, sectname = stddomain.labels.get(target, ("", "", ""))
+ innernode = nodes.inline(sectname, sectname)
+
+ if not docname:
+ return None
+
+ assert self.app.builder
+ return make_refnode(self.app.builder, fromdocname, docname, labelid, innernode)
+
+ def _resolve_doc_nested(
+ self, node: pending_xref, fromdocname: str
+ ) -> Optional[Element]:
+ """This is the same as ``sphinx.domains.std._resolve_doc_xref``,
+ but allows for nested syntax, rather than converting the inner node to raw text.
+
+ It also allows for extensions on document names.
+ """
+ # directly reference to document by source name; can be absolute or relative
+ refdoc = node.get("refdoc", fromdocname)
+ docname = docname_join(refdoc, node["reftarget"])
+
+ if docname not in self.env.all_docs:
+ # try stripping known extensions from doc name
+ if os.path.splitext(docname)[1] in self.env.config.source_suffix:
+ docname = os.path.splitext(docname)[0]
+ if docname not in self.env.all_docs:
+ return None
+
+ if node["refexplicit"]:
+ # reference with explicit title
+ caption = node.astext()
+ innernode = nodes.inline(caption, "", classes=["doc"])
+ innernode.extend(node[0].children)
+ else:
+ # TODO do we want nested syntax for titles?
+ caption = clean_astext(self.env.titles[docname])
+ innernode = nodes.inline(caption, caption, classes=["doc"])
+
+ assert self.app.builder
+ return make_refnode(self.app.builder, fromdocname, docname, "", innernode)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..880a4fd
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,110 @@
+[build-system]
+requires = ["flit_core >=3.4,<4"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "myst-parser"
+dynamic = ["version", "description"]
+authors = [{name = "Chris Sewell", email = "chrisj_sewell@hotmail.com"}]
+readme = "README.md"
+license = {file = "LICENSE"}
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Framework :: Sphinx :: Extension",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Text Processing :: Markup",
+]
+keywords = [
+ "markdown",
+ "lexer",
+ "parser",
+ "development",
+ "docutils",
+ "sphinx",
+]
+requires-python = ">=3.7"
+dependencies = [
+ "docutils>=0.15,<0.20",
+ "jinja2", # required for substitutions, but let sphinx choose version
+ "markdown-it-py>=1.0.0,<3.0.0",
+ "mdit-py-plugins~=0.3.1",
+ "pyyaml",
+ "sphinx>=4,<6",
+ "typing-extensions",
+]
+
+[project.urls]
+Homepage = "https://github.com/executablebooks/MyST-Parser"
+Documentation = "https://myst-parser.readthedocs.io"
+
+[project.optional-dependencies]
+code_style = ["pre-commit~=2.12"]
+# for use with "linkify" extension
+linkify = ["linkify-it-py~=1.0"]
+# Note: This is only required for internal use
+rtd = [
+ "ipython",
+ "sphinx-book-theme",
+ "sphinx-design",
+ "sphinxext-rediraffe~=0.2.7",
+ "sphinxcontrib.mermaid~=0.7.1",
+ "sphinxext-opengraph~=0.6.3",
+]
+testing = [
+ "beautifulsoup4",
+ "coverage[toml]",
+ "pytest>=6,<7",
+ "pytest-cov",
+ "pytest-regressions",
+ "pytest-param-files~=0.3.4",
+ "sphinx-pytest",
+ "sphinx<5.2", # TODO 5.2 changes the attributes of desc/desc_signature nodes
+]
+
+[project.scripts]
+myst-anchors = "myst_parser.cli:print_anchors"
+myst-docutils-html = "myst_parser.parsers.docutils_:cli_html"
+myst-docutils-html5 = "myst_parser.parsers.docutils_:cli_html5"
+myst-docutils-latex = "myst_parser.parsers.docutils_:cli_latex"
+myst-docutils-xml = "myst_parser.parsers.docutils_:cli_xml"
+myst-docutils-pseudoxml = "myst_parser.parsers.docutils_:cli_pseudoxml"
+
+[tool.flit.module]
+name = "myst_parser"
+
+[tool.flit.sdist]
+exclude = [
+ "docs/",
+ "tests/",
+]
+
+[tool.isort]
+profile = "black"
+known_first_party = ["myst_parser", "tests"]
+known_third_party = ["docutils", "markdown_it", "sphinx"]
+# Group first party and local folder imports together
+no_lines_before = "LOCALFOLDER"
+
+[tool.mypy]
+show_error_codes = true
+check_untyped_defs = true
+strict_equality = true
+no_implicit_optional = true
+warn_unused_ignores = true
+
+[[tool.mypy.overrides]]
+module = ["docutils.*", "yaml.*"]
+ignore_missing_imports = true
+
+[tool.coverage.run]
+omit = ["*/_docs.py"]
diff --git a/tests/test_cli.py b/tests/test_cli.py
new file mode 100644
index 0000000..4725f93
--- /dev/null
+++ b/tests/test_cli.py
@@ -0,0 +1,15 @@
+from unittest import mock
+
+from myst_parser.cli import print_anchors
+
+
+def test_print_anchors():
+ from io import StringIO
+
+ in_stream = StringIO("# a\n\n## b\n\ntext")
+ out_stream = StringIO()
+ with mock.patch("sys.stdin", in_stream):
+ with mock.patch("sys.stdout", out_stream):
+ print_anchors(["-l", "1"])
+ out_stream.seek(0)
+ assert out_stream.read().strip() == '<h1 id="a"></h1>'
diff --git a/tests/test_commonmark/commonmark.json b/tests/test_commonmark/commonmark.json
new file mode 100644
index 0000000..222fee4
--- /dev/null
+++ b/tests/test_commonmark/commonmark.json
@@ -0,0 +1,5194 @@
+[
+ {
+ "markdown": "\tfoo\tbaz\t\tbim\n",
+ "html": "<pre><code>foo\tbaz\t\tbim\n</code></pre>\n",
+ "example": 1,
+ "start_line": 352,
+ "end_line": 357,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " \tfoo\tbaz\t\tbim\n",
+ "html": "<pre><code>foo\tbaz\t\tbim\n</code></pre>\n",
+ "example": 2,
+ "start_line": 359,
+ "end_line": 364,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " a\ta\n ὐ\ta\n",
+ "html": "<pre><code>a\ta\nὐ\ta\n</code></pre>\n",
+ "example": 3,
+ "start_line": 366,
+ "end_line": 373,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " - foo\n\n\tbar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 4,
+ "start_line": 379,
+ "end_line": 390,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "- foo\n\n\t\tbar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<pre><code> bar\n</code></pre>\n</li>\n</ul>\n",
+ "example": 5,
+ "start_line": 392,
+ "end_line": 404,
+ "section": "Tabs"
+ },
+ {
+ "markdown": ">\t\tfoo\n",
+ "html": "<blockquote>\n<pre><code> foo\n</code></pre>\n</blockquote>\n",
+ "example": 6,
+ "start_line": 415,
+ "end_line": 422,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "-\t\tfoo\n",
+ "html": "<ul>\n<li>\n<pre><code> foo\n</code></pre>\n</li>\n</ul>\n",
+ "example": 7,
+ "start_line": 424,
+ "end_line": 433,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " foo\n\tbar\n",
+ "html": "<pre><code>foo\nbar\n</code></pre>\n",
+ "example": 8,
+ "start_line": 436,
+ "end_line": 443,
+ "section": "Tabs"
+ },
+ {
+ "markdown": " - foo\n - bar\n\t - baz\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 9,
+ "start_line": 445,
+ "end_line": 461,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "#\tFoo\n",
+ "html": "<h1>Foo</h1>\n",
+ "example": 10,
+ "start_line": 463,
+ "end_line": 467,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "*\t*\t*\t\n",
+ "html": "<hr />\n",
+ "example": 11,
+ "start_line": 469,
+ "end_line": 473,
+ "section": "Tabs"
+ },
+ {
+ "markdown": "- `one\n- two`\n",
+ "html": "<ul>\n<li>`one</li>\n<li>two`</li>\n</ul>\n",
+ "example": 12,
+ "start_line": 496,
+ "end_line": 504,
+ "section": "Precedence"
+ },
+ {
+ "markdown": "***\n---\n___\n",
+ "html": "<hr />\n<hr />\n<hr />\n",
+ "example": 13,
+ "start_line": 535,
+ "end_line": 543,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "+++\n",
+ "html": "<p>+++</p>\n",
+ "example": 14,
+ "start_line": 548,
+ "end_line": 552,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "===\n",
+ "html": "<p>===</p>\n",
+ "example": 15,
+ "start_line": 555,
+ "end_line": 559,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "--\n**\n__\n",
+ "html": "<p>--\n**\n__</p>\n",
+ "example": 16,
+ "start_line": 564,
+ "end_line": 572,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " ***\n ***\n ***\n",
+ "html": "<hr />\n<hr />\n<hr />\n",
+ "example": 17,
+ "start_line": 577,
+ "end_line": 585,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " ***\n",
+ "html": "<pre><code>***\n</code></pre>\n",
+ "example": 18,
+ "start_line": 590,
+ "end_line": 595,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "Foo\n ***\n",
+ "html": "<p>Foo\n***</p>\n",
+ "example": 19,
+ "start_line": 598,
+ "end_line": 604,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "_____________________________________\n",
+ "html": "<hr />\n",
+ "example": 20,
+ "start_line": 609,
+ "end_line": 613,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " - - -\n",
+ "html": "<hr />\n",
+ "example": 21,
+ "start_line": 618,
+ "end_line": 622,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " ** * ** * ** * **\n",
+ "html": "<hr />\n",
+ "example": 22,
+ "start_line": 625,
+ "end_line": 629,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- - - -\n",
+ "html": "<hr />\n",
+ "example": 23,
+ "start_line": 632,
+ "end_line": 636,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- - - - \n",
+ "html": "<hr />\n",
+ "example": 24,
+ "start_line": 641,
+ "end_line": 645,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "_ _ _ _ a\n\na------\n\n---a---\n",
+ "html": "<p>_ _ _ _ a</p>\n<p>a------</p>\n<p>---a---</p>\n",
+ "example": 25,
+ "start_line": 650,
+ "end_line": 660,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": " *-*\n",
+ "html": "<p><em>-</em></p>\n",
+ "example": 26,
+ "start_line": 666,
+ "end_line": 670,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- foo\n***\n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<hr />\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 27,
+ "start_line": 675,
+ "end_line": 687,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "Foo\n***\nbar\n",
+ "html": "<p>Foo</p>\n<hr />\n<p>bar</p>\n",
+ "example": 28,
+ "start_line": 692,
+ "end_line": 700,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "Foo\n---\nbar\n",
+ "html": "<h2>Foo</h2>\n<p>bar</p>\n",
+ "example": 29,
+ "start_line": 709,
+ "end_line": 716,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "* Foo\n* * *\n* Bar\n",
+ "html": "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n<ul>\n<li>Bar</li>\n</ul>\n",
+ "example": 30,
+ "start_line": 722,
+ "end_line": 734,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "- Foo\n- * * *\n",
+ "html": "<ul>\n<li>Foo</li>\n<li>\n<hr />\n</li>\n</ul>\n",
+ "example": 31,
+ "start_line": 739,
+ "end_line": 749,
+ "section": "Thematic breaks"
+ },
+ {
+ "markdown": "# foo\n## foo\n### foo\n#### foo\n##### foo\n###### foo\n",
+ "html": "<h1>foo</h1>\n<h2>foo</h2>\n<h3>foo</h3>\n<h4>foo</h4>\n<h5>foo</h5>\n<h6>foo</h6>\n",
+ "example": 32,
+ "start_line": 768,
+ "end_line": 782,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "####### foo\n",
+ "html": "<p>####### foo</p>\n",
+ "example": 33,
+ "start_line": 787,
+ "end_line": 791,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "#5 bolt\n\n#hashtag\n",
+ "html": "<p>#5 bolt</p>\n<p>#hashtag</p>\n",
+ "example": 34,
+ "start_line": 802,
+ "end_line": 809,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "\\## foo\n",
+ "html": "<p>## foo</p>\n",
+ "example": 35,
+ "start_line": 814,
+ "end_line": 818,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo *bar* \\*baz\\*\n",
+ "html": "<h1>foo <em>bar</em> *baz*</h1>\n",
+ "example": 36,
+ "start_line": 823,
+ "end_line": 827,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo \n",
+ "html": "<h1>foo</h1>\n",
+ "example": 37,
+ "start_line": 832,
+ "end_line": 836,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": " ### foo\n ## foo\n # foo\n",
+ "html": "<h3>foo</h3>\n<h2>foo</h2>\n<h1>foo</h1>\n",
+ "example": 38,
+ "start_line": 841,
+ "end_line": 849,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": " # foo\n",
+ "html": "<pre><code># foo\n</code></pre>\n",
+ "example": 39,
+ "start_line": 854,
+ "end_line": 859,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "foo\n # bar\n",
+ "html": "<p>foo\n# bar</p>\n",
+ "example": 40,
+ "start_line": 862,
+ "end_line": 868,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "## foo ##\n ### bar ###\n",
+ "html": "<h2>foo</h2>\n<h3>bar</h3>\n",
+ "example": 41,
+ "start_line": 873,
+ "end_line": 879,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo ##################################\n##### foo ##\n",
+ "html": "<h1>foo</h1>\n<h5>foo</h5>\n",
+ "example": 42,
+ "start_line": 884,
+ "end_line": 890,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "### foo ### \n",
+ "html": "<h3>foo</h3>\n",
+ "example": 43,
+ "start_line": 895,
+ "end_line": 899,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "### foo ### b\n",
+ "html": "<h3>foo ### b</h3>\n",
+ "example": 44,
+ "start_line": 906,
+ "end_line": 910,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "# foo#\n",
+ "html": "<h1>foo#</h1>\n",
+ "example": 45,
+ "start_line": 915,
+ "end_line": 919,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "### foo \\###\n## foo #\\##\n# foo \\#\n",
+ "html": "<h3>foo ###</h3>\n<h2>foo ###</h2>\n<h1>foo #</h1>\n",
+ "example": 46,
+ "start_line": 925,
+ "end_line": 933,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "****\n## foo\n****\n",
+ "html": "<hr />\n<h2>foo</h2>\n<hr />\n",
+ "example": 47,
+ "start_line": 939,
+ "end_line": 947,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "Foo bar\n# baz\nBar foo\n",
+ "html": "<p>Foo bar</p>\n<h1>baz</h1>\n<p>Bar foo</p>\n",
+ "example": 48,
+ "start_line": 950,
+ "end_line": 958,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "## \n#\n### ###\n",
+ "html": "<h2></h2>\n<h1></h1>\n<h3></h3>\n",
+ "example": 49,
+ "start_line": 963,
+ "end_line": 971,
+ "section": "ATX headings"
+ },
+ {
+ "markdown": "Foo *bar*\n=========\n\nFoo *bar*\n---------\n",
+ "html": "<h1>Foo <em>bar</em></h1>\n<h2>Foo <em>bar</em></h2>\n",
+ "example": 50,
+ "start_line": 1006,
+ "end_line": 1015,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo *bar\nbaz*\n====\n",
+ "html": "<h1>Foo <em>bar\nbaz</em></h1>\n",
+ "example": 51,
+ "start_line": 1020,
+ "end_line": 1027,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " Foo *bar\nbaz*\t\n====\n",
+ "html": "<h1>Foo <em>bar\nbaz</em></h1>\n",
+ "example": 52,
+ "start_line": 1034,
+ "end_line": 1041,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n-------------------------\n\nFoo\n=\n",
+ "html": "<h2>Foo</h2>\n<h1>Foo</h1>\n",
+ "example": 53,
+ "start_line": 1046,
+ "end_line": 1055,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " Foo\n---\n\n Foo\n-----\n\n Foo\n ===\n",
+ "html": "<h2>Foo</h2>\n<h2>Foo</h2>\n<h1>Foo</h1>\n",
+ "example": 54,
+ "start_line": 1061,
+ "end_line": 1074,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " Foo\n ---\n\n Foo\n---\n",
+ "html": "<pre><code>Foo\n---\n\nFoo\n</code></pre>\n<hr />\n",
+ "example": 55,
+ "start_line": 1079,
+ "end_line": 1092,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n ---- \n",
+ "html": "<h2>Foo</h2>\n",
+ "example": 56,
+ "start_line": 1098,
+ "end_line": 1103,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n ---\n",
+ "html": "<p>Foo\n---</p>\n",
+ "example": 57,
+ "start_line": 1108,
+ "end_line": 1114,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n= =\n\nFoo\n--- -\n",
+ "html": "<p>Foo\n= =</p>\n<p>Foo</p>\n<hr />\n",
+ "example": 58,
+ "start_line": 1119,
+ "end_line": 1130,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo \n-----\n",
+ "html": "<h2>Foo</h2>\n",
+ "example": 59,
+ "start_line": 1135,
+ "end_line": 1140,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\\\n----\n",
+ "html": "<h2>Foo\\</h2>\n",
+ "example": 60,
+ "start_line": 1145,
+ "end_line": 1150,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "`Foo\n----\n`\n\n<a title=\"a lot\n---\nof dashes\"/>\n",
+ "html": "<h2>`Foo</h2>\n<p>`</p>\n<h2>&lt;a title=&quot;a lot</h2>\n<p>of dashes&quot;/&gt;</p>\n",
+ "example": 61,
+ "start_line": 1156,
+ "end_line": 1169,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "> Foo\n---\n",
+ "html": "<blockquote>\n<p>Foo</p>\n</blockquote>\n<hr />\n",
+ "example": 62,
+ "start_line": 1175,
+ "end_line": 1183,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "> foo\nbar\n===\n",
+ "html": "<blockquote>\n<p>foo\nbar\n===</p>\n</blockquote>\n",
+ "example": 63,
+ "start_line": 1186,
+ "end_line": 1196,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "- Foo\n---\n",
+ "html": "<ul>\n<li>Foo</li>\n</ul>\n<hr />\n",
+ "example": 64,
+ "start_line": 1199,
+ "end_line": 1207,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nBar\n---\n",
+ "html": "<h2>Foo\nBar</h2>\n",
+ "example": 65,
+ "start_line": 1214,
+ "end_line": 1221,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "---\nFoo\n---\nBar\n---\nBaz\n",
+ "html": "<hr />\n<h2>Foo</h2>\n<h2>Bar</h2>\n<p>Baz</p>\n",
+ "example": 66,
+ "start_line": 1227,
+ "end_line": 1239,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "\n====\n",
+ "html": "<p>====</p>\n",
+ "example": 67,
+ "start_line": 1244,
+ "end_line": 1249,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "---\n---\n",
+ "html": "<hr />\n<hr />\n",
+ "example": 68,
+ "start_line": 1256,
+ "end_line": 1262,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "- foo\n-----\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<hr />\n",
+ "example": 69,
+ "start_line": 1265,
+ "end_line": 1273,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " foo\n---\n",
+ "html": "<pre><code>foo\n</code></pre>\n<hr />\n",
+ "example": 70,
+ "start_line": 1276,
+ "end_line": 1283,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "> foo\n-----\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr />\n",
+ "example": 71,
+ "start_line": 1286,
+ "end_line": 1294,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "\\> foo\n------\n",
+ "html": "<h2>&gt; foo</h2>\n",
+ "example": 72,
+ "start_line": 1300,
+ "end_line": 1305,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\n\nbar\n---\nbaz\n",
+ "html": "<p>Foo</p>\n<h2>bar</h2>\n<p>baz</p>\n",
+ "example": 73,
+ "start_line": 1331,
+ "end_line": 1341,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nbar\n\n---\n\nbaz\n",
+ "html": "<p>Foo\nbar</p>\n<hr />\n<p>baz</p>\n",
+ "example": 74,
+ "start_line": 1347,
+ "end_line": 1359,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nbar\n* * *\nbaz\n",
+ "html": "<p>Foo\nbar</p>\n<hr />\n<p>baz</p>\n",
+ "example": 75,
+ "start_line": 1365,
+ "end_line": 1375,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": "Foo\nbar\n\\---\nbaz\n",
+ "html": "<p>Foo\nbar\n---\nbaz</p>\n",
+ "example": 76,
+ "start_line": 1380,
+ "end_line": 1390,
+ "section": "Setext headings"
+ },
+ {
+ "markdown": " a simple\n indented code block\n",
+ "html": "<pre><code>a simple\n indented code block\n</code></pre>\n",
+ "example": 77,
+ "start_line": 1408,
+ "end_line": 1415,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " - foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 78,
+ "start_line": 1422,
+ "end_line": 1433,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "1. foo\n\n - bar\n",
+ "html": "<ol>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 79,
+ "start_line": 1436,
+ "end_line": 1449,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " <a/>\n *hi*\n\n - one\n",
+ "html": "<pre><code>&lt;a/&gt;\n*hi*\n\n- one\n</code></pre>\n",
+ "example": 80,
+ "start_line": 1456,
+ "end_line": 1467,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " chunk1\n\n chunk2\n \n \n \n chunk3\n",
+ "html": "<pre><code>chunk1\n\nchunk2\n\n\n\nchunk3\n</code></pre>\n",
+ "example": 81,
+ "start_line": 1472,
+ "end_line": 1489,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " chunk1\n \n chunk2\n",
+ "html": "<pre><code>chunk1\n \n chunk2\n</code></pre>\n",
+ "example": 82,
+ "start_line": 1495,
+ "end_line": 1504,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "Foo\n bar\n\n",
+ "html": "<p>Foo\nbar</p>\n",
+ "example": 83,
+ "start_line": 1510,
+ "end_line": 1517,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " foo\nbar\n",
+ "html": "<pre><code>foo\n</code></pre>\n<p>bar</p>\n",
+ "example": 84,
+ "start_line": 1524,
+ "end_line": 1531,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "# Heading\n foo\nHeading\n------\n foo\n----\n",
+ "html": "<h1>Heading</h1>\n<pre><code>foo\n</code></pre>\n<h2>Heading</h2>\n<pre><code>foo\n</code></pre>\n<hr />\n",
+ "example": 85,
+ "start_line": 1537,
+ "end_line": 1552,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " foo\n bar\n",
+ "html": "<pre><code> foo\nbar\n</code></pre>\n",
+ "example": 86,
+ "start_line": 1557,
+ "end_line": 1564,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "\n \n foo\n \n\n",
+ "html": "<pre><code>foo\n</code></pre>\n",
+ "example": 87,
+ "start_line": 1570,
+ "end_line": 1579,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": " foo \n",
+ "html": "<pre><code>foo \n</code></pre>\n",
+ "example": 88,
+ "start_line": 1584,
+ "end_line": 1589,
+ "section": "Indented code blocks"
+ },
+ {
+ "markdown": "```\n<\n >\n```\n",
+ "html": "<pre><code>&lt;\n &gt;\n</code></pre>\n",
+ "example": 89,
+ "start_line": 1639,
+ "end_line": 1648,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~\n<\n >\n~~~\n",
+ "html": "<pre><code>&lt;\n &gt;\n</code></pre>\n",
+ "example": 90,
+ "start_line": 1653,
+ "end_line": 1662,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "``\nfoo\n``\n",
+ "html": "<p><code>foo</code></p>\n",
+ "example": 91,
+ "start_line": 1666,
+ "end_line": 1672,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\naaa\n~~~\n```\n",
+ "html": "<pre><code>aaa\n~~~\n</code></pre>\n",
+ "example": 92,
+ "start_line": 1677,
+ "end_line": 1686,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~\naaa\n```\n~~~\n",
+ "html": "<pre><code>aaa\n```\n</code></pre>\n",
+ "example": 93,
+ "start_line": 1689,
+ "end_line": 1698,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "````\naaa\n```\n``````\n",
+ "html": "<pre><code>aaa\n```\n</code></pre>\n",
+ "example": 94,
+ "start_line": 1703,
+ "end_line": 1712,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~~\naaa\n~~~\n~~~~\n",
+ "html": "<pre><code>aaa\n~~~\n</code></pre>\n",
+ "example": 95,
+ "start_line": 1715,
+ "end_line": 1724,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n",
+ "html": "<pre><code></code></pre>\n",
+ "example": 96,
+ "start_line": 1730,
+ "end_line": 1734,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "`````\n\n```\naaa\n",
+ "html": "<pre><code>\n```\naaa\n</code></pre>\n",
+ "example": 97,
+ "start_line": 1737,
+ "end_line": 1747,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "> ```\n> aaa\n\nbbb\n",
+ "html": "<blockquote>\n<pre><code>aaa\n</code></pre>\n</blockquote>\n<p>bbb</p>\n",
+ "example": 98,
+ "start_line": 1750,
+ "end_line": 1761,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n\n \n```\n",
+ "html": "<pre><code>\n \n</code></pre>\n",
+ "example": 99,
+ "start_line": 1766,
+ "end_line": 1775,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n```\n",
+ "html": "<pre><code></code></pre>\n",
+ "example": 100,
+ "start_line": 1780,
+ "end_line": 1785,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\n aaa\naaa\n```\n",
+ "html": "<pre><code>aaa\naaa\n</code></pre>\n",
+ "example": 101,
+ "start_line": 1792,
+ "end_line": 1801,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\naaa\n aaa\naaa\n ```\n",
+ "html": "<pre><code>aaa\naaa\naaa\n</code></pre>\n",
+ "example": 102,
+ "start_line": 1804,
+ "end_line": 1815,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\n aaa\n aaa\n aaa\n ```\n",
+ "html": "<pre><code>aaa\n aaa\naaa\n</code></pre>\n",
+ "example": 103,
+ "start_line": 1818,
+ "end_line": 1829,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\n aaa\n ```\n",
+ "html": "<pre><code>```\naaa\n```\n</code></pre>\n",
+ "example": 104,
+ "start_line": 1834,
+ "end_line": 1843,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n</code></pre>\n",
+ "example": 105,
+ "start_line": 1849,
+ "end_line": 1856,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": " ```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n</code></pre>\n",
+ "example": 106,
+ "start_line": 1859,
+ "end_line": 1866,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\naaa\n ```\n",
+ "html": "<pre><code>aaa\n ```\n</code></pre>\n",
+ "example": 107,
+ "start_line": 1871,
+ "end_line": 1879,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "``` ```\naaa\n",
+ "html": "<p><code> </code>\naaa</p>\n",
+ "example": 108,
+ "start_line": 1885,
+ "end_line": 1891,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~~~~\naaa\n~~~ ~~\n",
+ "html": "<pre><code>aaa\n~~~ ~~\n</code></pre>\n",
+ "example": 109,
+ "start_line": 1894,
+ "end_line": 1902,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "foo\n```\nbar\n```\nbaz\n",
+ "html": "<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n",
+ "example": 110,
+ "start_line": 1908,
+ "end_line": 1919,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "foo\n---\n~~~\nbar\n~~~\n# baz\n",
+ "html": "<h2>foo</h2>\n<pre><code>bar\n</code></pre>\n<h1>baz</h1>\n",
+ "example": 111,
+ "start_line": 1925,
+ "end_line": 1937,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```ruby\ndef foo(x)\n return 3\nend\n```\n",
+ "html": "<pre><code class=\"language-ruby\">def foo(x)\n return 3\nend\n</code></pre>\n",
+ "example": 112,
+ "start_line": 1947,
+ "end_line": 1958,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~~ ruby startline=3 $%@#$\ndef foo(x)\n return 3\nend\n~~~~~~~\n",
+ "html": "<pre><code class=\"language-ruby\">def foo(x)\n return 3\nend\n</code></pre>\n",
+ "example": 113,
+ "start_line": 1961,
+ "end_line": 1972,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "````;\n````\n",
+ "html": "<pre><code class=\"language-;\"></code></pre>\n",
+ "example": 114,
+ "start_line": 1975,
+ "end_line": 1980,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "``` aa ```\nfoo\n",
+ "html": "<p><code>aa</code>\nfoo</p>\n",
+ "example": 115,
+ "start_line": 1985,
+ "end_line": 1991,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "~~~ aa ``` ~~~\nfoo\n~~~\n",
+ "html": "<pre><code class=\"language-aa\">foo\n</code></pre>\n",
+ "example": 116,
+ "start_line": 1996,
+ "end_line": 2003,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "```\n``` aaa\n```\n",
+ "html": "<pre><code>``` aaa\n</code></pre>\n",
+ "example": 117,
+ "start_line": 2008,
+ "end_line": 2015,
+ "section": "Fenced code blocks"
+ },
+ {
+ "markdown": "<table><tr><td>\n<pre>\n**Hello**,\n\n_world_.\n</pre>\n</td></tr></table>\n",
+ "html": "<table><tr><td>\n<pre>\n**Hello**,\n<p><em>world</em>.\n</pre></p>\n</td></tr></table>\n",
+ "example": 118,
+ "start_line": 2087,
+ "end_line": 2102,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table>\n <tr>\n <td>\n hi\n </td>\n </tr>\n</table>\n\nokay.\n",
+ "html": "<table>\n <tr>\n <td>\n hi\n </td>\n </tr>\n</table>\n<p>okay.</p>\n",
+ "example": 119,
+ "start_line": 2116,
+ "end_line": 2135,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": " <div>\n *hello*\n <foo><a>\n",
+ "html": " <div>\n *hello*\n <foo><a>\n",
+ "example": 120,
+ "start_line": 2138,
+ "end_line": 2146,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "</div>\n*foo*\n",
+ "html": "</div>\n*foo*\n",
+ "example": 121,
+ "start_line": 2151,
+ "end_line": 2157,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<DIV CLASS=\"foo\">\n\n*Markdown*\n\n</DIV>\n",
+ "html": "<DIV CLASS=\"foo\">\n<p><em>Markdown</em></p>\n</DIV>\n",
+ "example": 122,
+ "start_line": 2162,
+ "end_line": 2172,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div id=\"foo\"\n class=\"bar\">\n</div>\n",
+ "html": "<div id=\"foo\"\n class=\"bar\">\n</div>\n",
+ "example": 123,
+ "start_line": 2178,
+ "end_line": 2186,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div id=\"foo\" class=\"bar\n baz\">\n</div>\n",
+ "html": "<div id=\"foo\" class=\"bar\n baz\">\n</div>\n",
+ "example": 124,
+ "start_line": 2189,
+ "end_line": 2197,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\n*foo*\n\n*bar*\n",
+ "html": "<div>\n*foo*\n<p><em>bar</em></p>\n",
+ "example": 125,
+ "start_line": 2201,
+ "end_line": 2210,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div id=\"foo\"\n*hi*\n",
+ "html": "<div id=\"foo\"\n*hi*\n",
+ "example": 126,
+ "start_line": 2217,
+ "end_line": 2223,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div class\nfoo\n",
+ "html": "<div class\nfoo\n",
+ "example": 127,
+ "start_line": 2226,
+ "end_line": 2232,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div *???-&&&-<---\n*foo*\n",
+ "html": "<div *???-&&&-<---\n*foo*\n",
+ "example": 128,
+ "start_line": 2238,
+ "end_line": 2244,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div><a href=\"bar\">*foo*</a></div>\n",
+ "html": "<div><a href=\"bar\">*foo*</a></div>\n",
+ "example": 129,
+ "start_line": 2250,
+ "end_line": 2254,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table><tr><td>\nfoo\n</td></tr></table>\n",
+ "html": "<table><tr><td>\nfoo\n</td></tr></table>\n",
+ "example": 130,
+ "start_line": 2257,
+ "end_line": 2265,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div></div>\n``` c\nint x = 33;\n```\n",
+ "html": "<div></div>\n``` c\nint x = 33;\n```\n",
+ "example": 131,
+ "start_line": 2274,
+ "end_line": 2284,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<a href=\"foo\">\n*bar*\n</a>\n",
+ "html": "<a href=\"foo\">\n*bar*\n</a>\n",
+ "example": 132,
+ "start_line": 2291,
+ "end_line": 2299,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<Warning>\n*bar*\n</Warning>\n",
+ "html": "<Warning>\n*bar*\n</Warning>\n",
+ "example": 133,
+ "start_line": 2304,
+ "end_line": 2312,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<i class=\"foo\">\n*bar*\n</i>\n",
+ "html": "<i class=\"foo\">\n*bar*\n</i>\n",
+ "example": 134,
+ "start_line": 2315,
+ "end_line": 2323,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "</ins>\n*bar*\n",
+ "html": "</ins>\n*bar*\n",
+ "example": 135,
+ "start_line": 2326,
+ "end_line": 2332,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<del>\n*foo*\n</del>\n",
+ "html": "<del>\n*foo*\n</del>\n",
+ "example": 136,
+ "start_line": 2341,
+ "end_line": 2349,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<del>\n\n*foo*\n\n</del>\n",
+ "html": "<del>\n<p><em>foo</em></p>\n</del>\n",
+ "example": 137,
+ "start_line": 2356,
+ "end_line": 2366,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<del>*foo*</del>\n",
+ "html": "<p><del><em>foo</em></del></p>\n",
+ "example": 138,
+ "start_line": 2374,
+ "end_line": 2378,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<pre language=\"haskell\"><code>\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n</code></pre>\nokay\n",
+ "html": "<pre language=\"haskell\"><code>\nimport Text.HTML.TagSoup\n\nmain :: IO ()\nmain = print $ parseTags tags\n</code></pre>\n<p>okay</p>\n",
+ "example": 139,
+ "start_line": 2390,
+ "end_line": 2406,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<script type=\"text/javascript\">\n// JavaScript example\n\ndocument.getElementById(\"demo\").innerHTML = \"Hello JavaScript!\";\n</script>\nokay\n",
+ "html": "<script type=\"text/javascript\">\n// JavaScript example\n\ndocument.getElementById(\"demo\").innerHTML = \"Hello JavaScript!\";\n</script>\n<p>okay</p>\n",
+ "example": 140,
+ "start_line": 2411,
+ "end_line": 2425,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<style\n type=\"text/css\">\nh1 {color:red;}\n\np {color:blue;}\n</style>\nokay\n",
+ "html": "<style\n type=\"text/css\">\nh1 {color:red;}\n\np {color:blue;}\n</style>\n<p>okay</p>\n",
+ "example": 141,
+ "start_line": 2430,
+ "end_line": 2446,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<style\n type=\"text/css\">\n\nfoo\n",
+ "html": "<style\n type=\"text/css\">\n\nfoo\n",
+ "example": 142,
+ "start_line": 2453,
+ "end_line": 2463,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "> <div>\n> foo\n\nbar\n",
+ "html": "<blockquote>\n<div>\nfoo\n</blockquote>\n<p>bar</p>\n",
+ "example": 143,
+ "start_line": 2466,
+ "end_line": 2477,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "- <div>\n- foo\n",
+ "html": "<ul>\n<li>\n<div>\n</li>\n<li>foo</li>\n</ul>\n",
+ "example": 144,
+ "start_line": 2480,
+ "end_line": 2490,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<style>p{color:red;}</style>\n*foo*\n",
+ "html": "<style>p{color:red;}</style>\n<p><em>foo</em></p>\n",
+ "example": 145,
+ "start_line": 2495,
+ "end_line": 2501,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<!-- foo -->*bar*\n*baz*\n",
+ "html": "<!-- foo -->*bar*\n<p><em>baz</em></p>\n",
+ "example": 146,
+ "start_line": 2504,
+ "end_line": 2510,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<script>\nfoo\n</script>1. *bar*\n",
+ "html": "<script>\nfoo\n</script>1. *bar*\n",
+ "example": 147,
+ "start_line": 2516,
+ "end_line": 2524,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<!-- Foo\n\nbar\n baz -->\nokay\n",
+ "html": "<!-- Foo\n\nbar\n baz -->\n<p>okay</p>\n",
+ "example": 148,
+ "start_line": 2529,
+ "end_line": 2541,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<?php\n\n echo '>';\n\n?>\nokay\n",
+ "html": "<?php\n\n echo '>';\n\n?>\n<p>okay</p>\n",
+ "example": 149,
+ "start_line": 2547,
+ "end_line": 2561,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<!DOCTYPE html>\n",
+ "html": "<!DOCTYPE html>\n",
+ "example": 150,
+ "start_line": 2566,
+ "end_line": 2570,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<![CDATA[\nfunction matchwo(a,b)\n{\n if (a < b && a < 0) then {\n return 1;\n\n } else {\n\n return 0;\n }\n}\n]]>\nokay\n",
+ "html": "<![CDATA[\nfunction matchwo(a,b)\n{\n if (a < b && a < 0) then {\n return 1;\n\n } else {\n\n return 0;\n }\n}\n]]>\n<p>okay</p>\n",
+ "example": 151,
+ "start_line": 2575,
+ "end_line": 2603,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": " <!-- foo -->\n\n <!-- foo -->\n",
+ "html": " <!-- foo -->\n<pre><code>&lt;!-- foo --&gt;\n</code></pre>\n",
+ "example": 152,
+ "start_line": 2608,
+ "end_line": 2616,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": " <div>\n\n <div>\n",
+ "html": " <div>\n<pre><code>&lt;div&gt;\n</code></pre>\n",
+ "example": 153,
+ "start_line": 2619,
+ "end_line": 2627,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "Foo\n<div>\nbar\n</div>\n",
+ "html": "<p>Foo</p>\n<div>\nbar\n</div>\n",
+ "example": 154,
+ "start_line": 2633,
+ "end_line": 2643,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\nbar\n</div>\n*foo*\n",
+ "html": "<div>\nbar\n</div>\n*foo*\n",
+ "example": 155,
+ "start_line": 2650,
+ "end_line": 2660,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "Foo\n<a href=\"bar\">\nbaz\n",
+ "html": "<p>Foo\n<a href=\"bar\">\nbaz</p>\n",
+ "example": 156,
+ "start_line": 2665,
+ "end_line": 2673,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\n\n*Emphasized* text.\n\n</div>\n",
+ "html": "<div>\n<p><em>Emphasized</em> text.</p>\n</div>\n",
+ "example": 157,
+ "start_line": 2706,
+ "end_line": 2716,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<div>\n*Emphasized* text.\n</div>\n",
+ "html": "<div>\n*Emphasized* text.\n</div>\n",
+ "example": 158,
+ "start_line": 2719,
+ "end_line": 2727,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table>\n\n<tr>\n\n<td>\nHi\n</td>\n\n</tr>\n\n</table>\n",
+ "html": "<table>\n<tr>\n<td>\nHi\n</td>\n</tr>\n</table>\n",
+ "example": 159,
+ "start_line": 2741,
+ "end_line": 2761,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "<table>\n\n <tr>\n\n <td>\n Hi\n </td>\n\n </tr>\n\n</table>\n",
+ "html": "<table>\n <tr>\n<pre><code>&lt;td&gt;\n Hi\n&lt;/td&gt;\n</code></pre>\n </tr>\n</table>\n",
+ "example": 160,
+ "start_line": 2768,
+ "end_line": 2789,
+ "section": "HTML blocks"
+ },
+ {
+ "markdown": "[foo]: /url \"title\"\n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 161,
+ "start_line": 2816,
+ "end_line": 2822,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": " [foo]: \n /url \n 'the title' \n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"the title\">foo</a></p>\n",
+ "example": 162,
+ "start_line": 2825,
+ "end_line": 2833,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[Foo*bar\\]]:my_(url) 'title (with parens)'\n\n[Foo*bar\\]]\n",
+ "html": "<p><a href=\"my_(url)\" title=\"title (with parens)\">Foo*bar]</a></p>\n",
+ "example": 163,
+ "start_line": 2836,
+ "end_line": 2842,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[Foo bar]:\n<my url>\n'title'\n\n[Foo bar]\n",
+ "html": "<p><a href=\"my%20url\" title=\"title\">Foo bar</a></p>\n",
+ "example": 164,
+ "start_line": 2845,
+ "end_line": 2853,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url '\ntitle\nline1\nline2\n'\n\n[foo]\n",
+ "html": "<p><a href=\"/url\" title=\"\ntitle\nline1\nline2\n\">foo</a></p>\n",
+ "example": 165,
+ "start_line": 2858,
+ "end_line": 2872,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url 'title\n\nwith blank line'\n\n[foo]\n",
+ "html": "<p>[foo]: /url 'title</p>\n<p>with blank line'</p>\n<p>[foo]</p>\n",
+ "example": 166,
+ "start_line": 2877,
+ "end_line": 2887,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]:\n/url\n\n[foo]\n",
+ "html": "<p><a href=\"/url\">foo</a></p>\n",
+ "example": 167,
+ "start_line": 2892,
+ "end_line": 2899,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]:\n\n[foo]\n",
+ "html": "<p>[foo]:</p>\n<p>[foo]</p>\n",
+ "example": 168,
+ "start_line": 2904,
+ "end_line": 2911,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: <>\n\n[foo]\n",
+ "html": "<p><a href=\"\">foo</a></p>\n",
+ "example": 169,
+ "start_line": 2916,
+ "end_line": 2922,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: <bar>(baz)\n\n[foo]\n",
+ "html": "<p>[foo]: <bar>(baz)</p>\n<p>[foo]</p>\n",
+ "example": 170,
+ "start_line": 2927,
+ "end_line": 2934,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\\bar\\*baz \"foo\\\"bar\\baz\"\n\n[foo]\n",
+ "html": "<p><a href=\"/url%5Cbar*baz\" title=\"foo&quot;bar\\baz\">foo</a></p>\n",
+ "example": 171,
+ "start_line": 2940,
+ "end_line": 2946,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: url\n",
+ "html": "<p><a href=\"url\">foo</a></p>\n",
+ "example": 172,
+ "start_line": 2951,
+ "end_line": 2957,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: first\n[foo]: second\n",
+ "html": "<p><a href=\"first\">foo</a></p>\n",
+ "example": 173,
+ "start_line": 2963,
+ "end_line": 2970,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[FOO]: /url\n\n[Foo]\n",
+ "html": "<p><a href=\"/url\">Foo</a></p>\n",
+ "example": 174,
+ "start_line": 2976,
+ "end_line": 2982,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[ΑΓΩ]: /φου\n\n[αγω]\n",
+ "html": "<p><a href=\"/%CF%86%CE%BF%CF%85\">αγω</a></p>\n",
+ "example": 175,
+ "start_line": 2985,
+ "end_line": 2991,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n",
+ "html": "",
+ "example": 176,
+ "start_line": 2997,
+ "end_line": 3000,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[\nfoo\n]: /url\nbar\n",
+ "html": "<p>bar</p>\n",
+ "example": 177,
+ "start_line": 3005,
+ "end_line": 3012,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url \"title\" ok\n",
+ "html": "<p>[foo]: /url &quot;title&quot; ok</p>\n",
+ "example": 178,
+ "start_line": 3018,
+ "end_line": 3022,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n\"title\" ok\n",
+ "html": "<p>&quot;title&quot; ok</p>\n",
+ "example": 179,
+ "start_line": 3027,
+ "end_line": 3032,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": " [foo]: /url \"title\"\n\n[foo]\n",
+ "html": "<pre><code>[foo]: /url &quot;title&quot;\n</code></pre>\n<p>[foo]</p>\n",
+ "example": 180,
+ "start_line": 3038,
+ "end_line": 3046,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "```\n[foo]: /url\n```\n\n[foo]\n",
+ "html": "<pre><code>[foo]: /url\n</code></pre>\n<p>[foo]</p>\n",
+ "example": 181,
+ "start_line": 3052,
+ "end_line": 3062,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "Foo\n[bar]: /baz\n\n[bar]\n",
+ "html": "<p>Foo\n[bar]: /baz</p>\n<p>[bar]</p>\n",
+ "example": 182,
+ "start_line": 3067,
+ "end_line": 3076,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "# [Foo]\n[foo]: /url\n> bar\n",
+ "html": "<h1><a href=\"/url\">Foo</a></h1>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 183,
+ "start_line": 3082,
+ "end_line": 3091,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\nbar\n===\n[foo]\n",
+ "html": "<h1>bar</h1>\n<p><a href=\"/url\">foo</a></p>\n",
+ "example": 184,
+ "start_line": 3093,
+ "end_line": 3101,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n===\n[foo]\n",
+ "html": "<p>===\n<a href=\"/url\">foo</a></p>\n",
+ "example": 185,
+ "start_line": 3103,
+ "end_line": 3110,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /foo-url \"foo\"\n[bar]: /bar-url\n \"bar\"\n[baz]: /baz-url\n\n[foo],\n[bar],\n[baz]\n",
+ "html": "<p><a href=\"/foo-url\" title=\"foo\">foo</a>,\n<a href=\"/bar-url\" title=\"bar\">bar</a>,\n<a href=\"/baz-url\">baz</a></p>\n",
+ "example": 186,
+ "start_line": 3116,
+ "end_line": 3129,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]\n\n> [foo]: /url\n",
+ "html": "<p><a href=\"/url\">foo</a></p>\n<blockquote>\n</blockquote>\n",
+ "example": 187,
+ "start_line": 3137,
+ "end_line": 3145,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "[foo]: /url\n",
+ "html": "",
+ "example": 188,
+ "start_line": 3154,
+ "end_line": 3157,
+ "section": "Link reference definitions"
+ },
+ {
+ "markdown": "aaa\n\nbbb\n",
+ "html": "<p>aaa</p>\n<p>bbb</p>\n",
+ "example": 189,
+ "start_line": 3171,
+ "end_line": 3178,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa\nbbb\n\nccc\nddd\n",
+ "html": "<p>aaa\nbbb</p>\n<p>ccc\nddd</p>\n",
+ "example": 190,
+ "start_line": 3183,
+ "end_line": 3194,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa\n\n\nbbb\n",
+ "html": "<p>aaa</p>\n<p>bbb</p>\n",
+ "example": 191,
+ "start_line": 3199,
+ "end_line": 3207,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " aaa\n bbb\n",
+ "html": "<p>aaa\nbbb</p>\n",
+ "example": 192,
+ "start_line": 3212,
+ "end_line": 3218,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa\n bbb\n ccc\n",
+ "html": "<p>aaa\nbbb\nccc</p>\n",
+ "example": 193,
+ "start_line": 3224,
+ "end_line": 3232,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " aaa\nbbb\n",
+ "html": "<p>aaa\nbbb</p>\n",
+ "example": 194,
+ "start_line": 3238,
+ "end_line": 3244,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " aaa\nbbb\n",
+ "html": "<pre><code>aaa\n</code></pre>\n<p>bbb</p>\n",
+ "example": 195,
+ "start_line": 3247,
+ "end_line": 3254,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": "aaa \nbbb \n",
+ "html": "<p>aaa<br />\nbbb</p>\n",
+ "example": 196,
+ "start_line": 3261,
+ "end_line": 3267,
+ "section": "Paragraphs"
+ },
+ {
+ "markdown": " \n\naaa\n \n\n# aaa\n\n \n",
+ "html": "<p>aaa</p>\n<h1>aaa</h1>\n",
+ "example": 197,
+ "start_line": 3278,
+ "end_line": 3290,
+ "section": "Blank lines"
+ },
+ {
+ "markdown": "> # Foo\n> bar\n> baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 198,
+ "start_line": 3344,
+ "end_line": 3354,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "># Foo\n>bar\n> baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 199,
+ "start_line": 3359,
+ "end_line": 3369,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": " > # Foo\n > bar\n > baz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 200,
+ "start_line": 3374,
+ "end_line": 3384,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": " > # Foo\n > bar\n > baz\n",
+ "html": "<pre><code>&gt; # Foo\n&gt; bar\n&gt; baz\n</code></pre>\n",
+ "example": 201,
+ "start_line": 3389,
+ "end_line": 3398,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> # Foo\n> bar\nbaz\n",
+ "html": "<blockquote>\n<h1>Foo</h1>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 202,
+ "start_line": 3404,
+ "end_line": 3414,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\nbaz\n> foo\n",
+ "html": "<blockquote>\n<p>bar\nbaz\nfoo</p>\n</blockquote>\n",
+ "example": 203,
+ "start_line": 3420,
+ "end_line": 3430,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n---\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<hr />\n",
+ "example": 204,
+ "start_line": 3444,
+ "end_line": 3452,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> - foo\n- bar\n",
+ "html": "<blockquote>\n<ul>\n<li>foo</li>\n</ul>\n</blockquote>\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 205,
+ "start_line": 3464,
+ "end_line": 3476,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n bar\n",
+ "html": "<blockquote>\n<pre><code>foo\n</code></pre>\n</blockquote>\n<pre><code>bar\n</code></pre>\n",
+ "example": 206,
+ "start_line": 3482,
+ "end_line": 3492,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> ```\nfoo\n```\n",
+ "html": "<blockquote>\n<pre><code></code></pre>\n</blockquote>\n<p>foo</p>\n<pre><code></code></pre>\n",
+ "example": 207,
+ "start_line": 3495,
+ "end_line": 3505,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n - bar\n",
+ "html": "<blockquote>\n<p>foo\n- bar</p>\n</blockquote>\n",
+ "example": 208,
+ "start_line": 3511,
+ "end_line": 3519,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">\n",
+ "html": "<blockquote>\n</blockquote>\n",
+ "example": 209,
+ "start_line": 3535,
+ "end_line": 3540,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">\n> \n> \n",
+ "html": "<blockquote>\n</blockquote>\n",
+ "example": 210,
+ "start_line": 3543,
+ "end_line": 3550,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">\n> foo\n> \n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n",
+ "example": 211,
+ "start_line": 3555,
+ "end_line": 3563,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n\n> bar\n",
+ "html": "<blockquote>\n<p>foo</p>\n</blockquote>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 212,
+ "start_line": 3568,
+ "end_line": 3579,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n> bar\n",
+ "html": "<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n",
+ "example": 213,
+ "start_line": 3590,
+ "end_line": 3598,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> foo\n>\n> bar\n",
+ "html": "<blockquote>\n<p>foo</p>\n<p>bar</p>\n</blockquote>\n",
+ "example": 214,
+ "start_line": 3603,
+ "end_line": 3612,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "foo\n> bar\n",
+ "html": "<p>foo</p>\n<blockquote>\n<p>bar</p>\n</blockquote>\n",
+ "example": 215,
+ "start_line": 3617,
+ "end_line": 3625,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> aaa\n***\n> bbb\n",
+ "html": "<blockquote>\n<p>aaa</p>\n</blockquote>\n<hr />\n<blockquote>\n<p>bbb</p>\n</blockquote>\n",
+ "example": 216,
+ "start_line": 3631,
+ "end_line": 3643,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\nbaz\n",
+ "html": "<blockquote>\n<p>bar\nbaz</p>\n</blockquote>\n",
+ "example": 217,
+ "start_line": 3649,
+ "end_line": 3657,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\n\nbaz\n",
+ "html": "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n",
+ "example": 218,
+ "start_line": 3660,
+ "end_line": 3669,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> bar\n>\nbaz\n",
+ "html": "<blockquote>\n<p>bar</p>\n</blockquote>\n<p>baz</p>\n",
+ "example": 219,
+ "start_line": 3672,
+ "end_line": 3681,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> > > foo\nbar\n",
+ "html": "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar</p>\n</blockquote>\n</blockquote>\n</blockquote>\n",
+ "example": 220,
+ "start_line": 3688,
+ "end_line": 3700,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": ">>> foo\n> bar\n>>baz\n",
+ "html": "<blockquote>\n<blockquote>\n<blockquote>\n<p>foo\nbar\nbaz</p>\n</blockquote>\n</blockquote>\n</blockquote>\n",
+ "example": 221,
+ "start_line": 3703,
+ "end_line": 3717,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "> code\n\n> not code\n",
+ "html": "<blockquote>\n<pre><code>code\n</code></pre>\n</blockquote>\n<blockquote>\n<p>not code</p>\n</blockquote>\n",
+ "example": 222,
+ "start_line": 3725,
+ "end_line": 3737,
+ "section": "Block quotes"
+ },
+ {
+ "markdown": "A paragraph\nwith two lines.\n\n indented code\n\n> A block quote.\n",
+ "html": "<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n",
+ "example": 223,
+ "start_line": 3779,
+ "end_line": 3794,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 224,
+ "start_line": 3801,
+ "end_line": 3820,
+ "section": "List items"
+ },
+ {
+ "markdown": "- one\n\n two\n",
+ "html": "<ul>\n<li>one</li>\n</ul>\n<p>two</p>\n",
+ "example": 225,
+ "start_line": 3834,
+ "end_line": 3843,
+ "section": "List items"
+ },
+ {
+ "markdown": "- one\n\n two\n",
+ "html": "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n",
+ "example": 226,
+ "start_line": 3846,
+ "end_line": 3857,
+ "section": "List items"
+ },
+ {
+ "markdown": " - one\n\n two\n",
+ "html": "<ul>\n<li>one</li>\n</ul>\n<pre><code> two\n</code></pre>\n",
+ "example": 227,
+ "start_line": 3860,
+ "end_line": 3870,
+ "section": "List items"
+ },
+ {
+ "markdown": " - one\n\n two\n",
+ "html": "<ul>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ul>\n",
+ "example": 228,
+ "start_line": 3873,
+ "end_line": 3884,
+ "section": "List items"
+ },
+ {
+ "markdown": " > > 1. one\n>>\n>> two\n",
+ "html": "<blockquote>\n<blockquote>\n<ol>\n<li>\n<p>one</p>\n<p>two</p>\n</li>\n</ol>\n</blockquote>\n</blockquote>\n",
+ "example": 229,
+ "start_line": 3895,
+ "end_line": 3910,
+ "section": "List items"
+ },
+ {
+ "markdown": ">>- one\n>>\n > > two\n",
+ "html": "<blockquote>\n<blockquote>\n<ul>\n<li>one</li>\n</ul>\n<p>two</p>\n</blockquote>\n</blockquote>\n",
+ "example": 230,
+ "start_line": 3922,
+ "end_line": 3935,
+ "section": "List items"
+ },
+ {
+ "markdown": "-one\n\n2.two\n",
+ "html": "<p>-one</p>\n<p>2.two</p>\n",
+ "example": 231,
+ "start_line": 3941,
+ "end_line": 3948,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 232,
+ "start_line": 3954,
+ "end_line": 3966,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. foo\n\n ```\n bar\n ```\n\n baz\n\n > bam\n",
+ "html": "<ol>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n<p>baz</p>\n<blockquote>\n<p>bam</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 233,
+ "start_line": 3971,
+ "end_line": 3993,
+ "section": "List items"
+ },
+ {
+ "markdown": "- Foo\n\n bar\n\n\n baz\n",
+ "html": "<ul>\n<li>\n<p>Foo</p>\n<pre><code>bar\n\n\nbaz\n</code></pre>\n</li>\n</ul>\n",
+ "example": 234,
+ "start_line": 3999,
+ "end_line": 4017,
+ "section": "List items"
+ },
+ {
+ "markdown": "123456789. ok\n",
+ "html": "<ol start=\"123456789\">\n<li>ok</li>\n</ol>\n",
+ "example": 235,
+ "start_line": 4021,
+ "end_line": 4027,
+ "section": "List items"
+ },
+ {
+ "markdown": "1234567890. not ok\n",
+ "html": "<p>1234567890. not ok</p>\n",
+ "example": 236,
+ "start_line": 4030,
+ "end_line": 4034,
+ "section": "List items"
+ },
+ {
+ "markdown": "0. ok\n",
+ "html": "<ol start=\"0\">\n<li>ok</li>\n</ol>\n",
+ "example": 237,
+ "start_line": 4039,
+ "end_line": 4045,
+ "section": "List items"
+ },
+ {
+ "markdown": "003. ok\n",
+ "html": "<ol start=\"3\">\n<li>ok</li>\n</ol>\n",
+ "example": 238,
+ "start_line": 4048,
+ "end_line": 4054,
+ "section": "List items"
+ },
+ {
+ "markdown": "-1. not ok\n",
+ "html": "<p>-1. not ok</p>\n",
+ "example": 239,
+ "start_line": 4059,
+ "end_line": 4063,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ul>\n",
+ "example": 240,
+ "start_line": 4082,
+ "end_line": 4094,
+ "section": "List items"
+ },
+ {
+ "markdown": " 10. foo\n\n bar\n",
+ "html": "<ol start=\"10\">\n<li>\n<p>foo</p>\n<pre><code>bar\n</code></pre>\n</li>\n</ol>\n",
+ "example": 241,
+ "start_line": 4099,
+ "end_line": 4111,
+ "section": "List items"
+ },
+ {
+ "markdown": " indented code\n\nparagraph\n\n more code\n",
+ "html": "<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n",
+ "example": 242,
+ "start_line": 4118,
+ "end_line": 4130,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. indented code\n\n paragraph\n\n more code\n",
+ "html": "<ol>\n<li>\n<pre><code>indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n",
+ "example": 243,
+ "start_line": 4133,
+ "end_line": 4149,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. indented code\n\n paragraph\n\n more code\n",
+ "html": "<ol>\n<li>\n<pre><code> indented code\n</code></pre>\n<p>paragraph</p>\n<pre><code>more code\n</code></pre>\n</li>\n</ol>\n",
+ "example": 244,
+ "start_line": 4155,
+ "end_line": 4171,
+ "section": "List items"
+ },
+ {
+ "markdown": " foo\n\nbar\n",
+ "html": "<p>foo</p>\n<p>bar</p>\n",
+ "example": 245,
+ "start_line": 4182,
+ "end_line": 4189,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n<p>bar</p>\n",
+ "example": 246,
+ "start_line": 4192,
+ "end_line": 4201,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n\n bar\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>bar</p>\n</li>\n</ul>\n",
+ "example": 247,
+ "start_line": 4209,
+ "end_line": 4220,
+ "section": "List items"
+ },
+ {
+ "markdown": "-\n foo\n-\n ```\n bar\n ```\n-\n baz\n",
+ "html": "<ul>\n<li>foo</li>\n<li>\n<pre><code>bar\n</code></pre>\n</li>\n<li>\n<pre><code>baz\n</code></pre>\n</li>\n</ul>\n",
+ "example": 248,
+ "start_line": 4237,
+ "end_line": 4258,
+ "section": "List items"
+ },
+ {
+ "markdown": "- \n foo\n",
+ "html": "<ul>\n<li>foo</li>\n</ul>\n",
+ "example": 249,
+ "start_line": 4263,
+ "end_line": 4270,
+ "section": "List items"
+ },
+ {
+ "markdown": "-\n\n foo\n",
+ "html": "<ul>\n<li></li>\n</ul>\n<p>foo</p>\n",
+ "example": 250,
+ "start_line": 4277,
+ "end_line": 4286,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n-\n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ul>\n",
+ "example": 251,
+ "start_line": 4291,
+ "end_line": 4301,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n- \n- bar\n",
+ "html": "<ul>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ul>\n",
+ "example": 252,
+ "start_line": 4306,
+ "end_line": 4316,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. foo\n2.\n3. bar\n",
+ "html": "<ol>\n<li>foo</li>\n<li></li>\n<li>bar</li>\n</ol>\n",
+ "example": 253,
+ "start_line": 4321,
+ "end_line": 4331,
+ "section": "List items"
+ },
+ {
+ "markdown": "*\n",
+ "html": "<ul>\n<li></li>\n</ul>\n",
+ "example": 254,
+ "start_line": 4336,
+ "end_line": 4342,
+ "section": "List items"
+ },
+ {
+ "markdown": "foo\n*\n\nfoo\n1.\n",
+ "html": "<p>foo\n*</p>\n<p>foo\n1.</p>\n",
+ "example": 255,
+ "start_line": 4346,
+ "end_line": 4357,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 256,
+ "start_line": 4368,
+ "end_line": 4387,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 257,
+ "start_line": 4392,
+ "end_line": 4411,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 258,
+ "start_line": 4416,
+ "end_line": 4435,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<pre><code>1. A paragraph\n with two lines.\n\n indented code\n\n &gt; A block quote.\n</code></pre>\n",
+ "example": 259,
+ "start_line": 4440,
+ "end_line": 4455,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\nwith two lines.\n\n indented code\n\n > A block quote.\n",
+ "html": "<ol>\n<li>\n<p>A paragraph\nwith two lines.</p>\n<pre><code>indented code\n</code></pre>\n<blockquote>\n<p>A block quote.</p>\n</blockquote>\n</li>\n</ol>\n",
+ "example": 260,
+ "start_line": 4470,
+ "end_line": 4489,
+ "section": "List items"
+ },
+ {
+ "markdown": " 1. A paragraph\n with two lines.\n",
+ "html": "<ol>\n<li>A paragraph\nwith two lines.</li>\n</ol>\n",
+ "example": 261,
+ "start_line": 4494,
+ "end_line": 4502,
+ "section": "List items"
+ },
+ {
+ "markdown": "> 1. > Blockquote\ncontinued here.\n",
+ "html": "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote\ncontinued here.</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n",
+ "example": 262,
+ "start_line": 4507,
+ "end_line": 4521,
+ "section": "List items"
+ },
+ {
+ "markdown": "> 1. > Blockquote\n> continued here.\n",
+ "html": "<blockquote>\n<ol>\n<li>\n<blockquote>\n<p>Blockquote\ncontinued here.</p>\n</blockquote>\n</li>\n</ol>\n</blockquote>\n",
+ "example": 263,
+ "start_line": 4524,
+ "end_line": 4538,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n - boo\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>baz\n<ul>\n<li>boo</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 264,
+ "start_line": 4552,
+ "end_line": 4573,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n - boo\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n<li>baz</li>\n<li>boo</li>\n</ul>\n",
+ "example": 265,
+ "start_line": 4578,
+ "end_line": 4590,
+ "section": "List items"
+ },
+ {
+ "markdown": "10) foo\n - bar\n",
+ "html": "<ol start=\"10\">\n<li>foo\n<ul>\n<li>bar</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 266,
+ "start_line": 4595,
+ "end_line": 4606,
+ "section": "List items"
+ },
+ {
+ "markdown": "10) foo\n - bar\n",
+ "html": "<ol start=\"10\">\n<li>foo</li>\n</ol>\n<ul>\n<li>bar</li>\n</ul>\n",
+ "example": 267,
+ "start_line": 4611,
+ "end_line": 4621,
+ "section": "List items"
+ },
+ {
+ "markdown": "- - foo\n",
+ "html": "<ul>\n<li>\n<ul>\n<li>foo</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 268,
+ "start_line": 4626,
+ "end_line": 4636,
+ "section": "List items"
+ },
+ {
+ "markdown": "1. - 2. foo\n",
+ "html": "<ol>\n<li>\n<ul>\n<li>\n<ol start=\"2\">\n<li>foo</li>\n</ol>\n</li>\n</ul>\n</li>\n</ol>\n",
+ "example": 269,
+ "start_line": 4639,
+ "end_line": 4653,
+ "section": "List items"
+ },
+ {
+ "markdown": "- # Foo\n- Bar\n ---\n baz\n",
+ "html": "<ul>\n<li>\n<h1>Foo</h1>\n</li>\n<li>\n<h2>Bar</h2>\nbaz</li>\n</ul>\n",
+ "example": 270,
+ "start_line": 4658,
+ "end_line": 4672,
+ "section": "List items"
+ },
+ {
+ "markdown": "- foo\n- bar\n+ baz\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<ul>\n<li>baz</li>\n</ul>\n",
+ "example": 271,
+ "start_line": 4894,
+ "end_line": 4906,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. foo\n2. bar\n3) baz\n",
+ "html": "<ol>\n<li>foo</li>\n<li>bar</li>\n</ol>\n<ol start=\"3\">\n<li>baz</li>\n</ol>\n",
+ "example": 272,
+ "start_line": 4909,
+ "end_line": 4921,
+ "section": "Lists"
+ },
+ {
+ "markdown": "Foo\n- bar\n- baz\n",
+ "html": "<p>Foo</p>\n<ul>\n<li>bar</li>\n<li>baz</li>\n</ul>\n",
+ "example": 273,
+ "start_line": 4928,
+ "end_line": 4938,
+ "section": "Lists"
+ },
+ {
+ "markdown": "The number of windows in my house is\n14. The number of doors is 6.\n",
+ "html": "<p>The number of windows in my house is\n14. The number of doors is 6.</p>\n",
+ "example": 274,
+ "start_line": 5005,
+ "end_line": 5011,
+ "section": "Lists"
+ },
+ {
+ "markdown": "The number of windows in my house is\n1. The number of doors is 6.\n",
+ "html": "<p>The number of windows in my house is</p>\n<ol>\n<li>The number of doors is 6.</li>\n</ol>\n",
+ "example": 275,
+ "start_line": 5015,
+ "end_line": 5023,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n\n- bar\n\n\n- baz\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n</li>\n<li>\n<p>bar</p>\n</li>\n<li>\n<p>baz</p>\n</li>\n</ul>\n",
+ "example": 276,
+ "start_line": 5029,
+ "end_line": 5048,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n - bar\n - baz\n\n\n bim\n",
+ "html": "<ul>\n<li>foo\n<ul>\n<li>bar\n<ul>\n<li>\n<p>baz</p>\n<p>bim</p>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 277,
+ "start_line": 5050,
+ "end_line": 5072,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n- bar\n\n<!-- -->\n\n- baz\n- bim\n",
+ "html": "<ul>\n<li>foo</li>\n<li>bar</li>\n</ul>\n<!-- -->\n<ul>\n<li>baz</li>\n<li>bim</li>\n</ul>\n",
+ "example": 278,
+ "start_line": 5080,
+ "end_line": 5098,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- foo\n\n notcode\n\n- foo\n\n<!-- -->\n\n code\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<p>notcode</p>\n</li>\n<li>\n<p>foo</p>\n</li>\n</ul>\n<!-- -->\n<pre><code>code\n</code></pre>\n",
+ "example": 279,
+ "start_line": 5101,
+ "end_line": 5124,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n - c\n - d\n - e\n - f\n- g\n",
+ "html": "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d</li>\n<li>e</li>\n<li>f</li>\n<li>g</li>\n</ul>\n",
+ "example": 280,
+ "start_line": 5132,
+ "end_line": 5150,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. a\n\n 2. b\n\n 3. c\n",
+ "html": "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ol>\n",
+ "example": 281,
+ "start_line": 5153,
+ "end_line": 5171,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n - c\n - d\n - e\n",
+ "html": "<ul>\n<li>a</li>\n<li>b</li>\n<li>c</li>\n<li>d\n- e</li>\n</ul>\n",
+ "example": 282,
+ "start_line": 5177,
+ "end_line": 5191,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. a\n\n 2. b\n\n 3. c\n",
+ "html": "<ol>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n</ol>\n<pre><code>3. c\n</code></pre>\n",
+ "example": 283,
+ "start_line": 5197,
+ "end_line": 5214,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- b\n\n- c\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>c</p>\n</li>\n</ul>\n",
+ "example": 284,
+ "start_line": 5220,
+ "end_line": 5237,
+ "section": "Lists"
+ },
+ {
+ "markdown": "* a\n*\n\n* c\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li></li>\n<li>\n<p>c</p>\n</li>\n</ul>\n",
+ "example": 285,
+ "start_line": 5242,
+ "end_line": 5257,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- b\n\n c\n- d\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n",
+ "example": 286,
+ "start_line": 5264,
+ "end_line": 5283,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- b\n\n [ref]: /url\n- d\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n</li>\n<li>\n<p>b</p>\n</li>\n<li>\n<p>d</p>\n</li>\n</ul>\n",
+ "example": 287,
+ "start_line": 5286,
+ "end_line": 5304,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n- ```\n b\n\n\n ```\n- c\n",
+ "html": "<ul>\n<li>a</li>\n<li>\n<pre><code>b\n\n\n</code></pre>\n</li>\n<li>c</li>\n</ul>\n",
+ "example": 288,
+ "start_line": 5309,
+ "end_line": 5328,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n\n c\n- d\n",
+ "html": "<ul>\n<li>a\n<ul>\n<li>\n<p>b</p>\n<p>c</p>\n</li>\n</ul>\n</li>\n<li>d</li>\n</ul>\n",
+ "example": 289,
+ "start_line": 5335,
+ "end_line": 5353,
+ "section": "Lists"
+ },
+ {
+ "markdown": "* a\n > b\n >\n* c\n",
+ "html": "<ul>\n<li>a\n<blockquote>\n<p>b</p>\n</blockquote>\n</li>\n<li>c</li>\n</ul>\n",
+ "example": 290,
+ "start_line": 5359,
+ "end_line": 5373,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n > b\n ```\n c\n ```\n- d\n",
+ "html": "<ul>\n<li>a\n<blockquote>\n<p>b</p>\n</blockquote>\n<pre><code>c\n</code></pre>\n</li>\n<li>d</li>\n</ul>\n",
+ "example": 291,
+ "start_line": 5379,
+ "end_line": 5397,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n",
+ "html": "<ul>\n<li>a</li>\n</ul>\n",
+ "example": 292,
+ "start_line": 5402,
+ "end_line": 5408,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n",
+ "html": "<ul>\n<li>a\n<ul>\n<li>b</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 293,
+ "start_line": 5411,
+ "end_line": 5422,
+ "section": "Lists"
+ },
+ {
+ "markdown": "1. ```\n foo\n ```\n\n bar\n",
+ "html": "<ol>\n<li>\n<pre><code>foo\n</code></pre>\n<p>bar</p>\n</li>\n</ol>\n",
+ "example": 294,
+ "start_line": 5428,
+ "end_line": 5442,
+ "section": "Lists"
+ },
+ {
+ "markdown": "* foo\n * bar\n\n baz\n",
+ "html": "<ul>\n<li>\n<p>foo</p>\n<ul>\n<li>bar</li>\n</ul>\n<p>baz</p>\n</li>\n</ul>\n",
+ "example": 295,
+ "start_line": 5447,
+ "end_line": 5462,
+ "section": "Lists"
+ },
+ {
+ "markdown": "- a\n - b\n - c\n\n- d\n - e\n - f\n",
+ "html": "<ul>\n<li>\n<p>a</p>\n<ul>\n<li>b</li>\n<li>c</li>\n</ul>\n</li>\n<li>\n<p>d</p>\n<ul>\n<li>e</li>\n<li>f</li>\n</ul>\n</li>\n</ul>\n",
+ "example": 296,
+ "start_line": 5465,
+ "end_line": 5490,
+ "section": "Lists"
+ },
+ {
+ "markdown": "`hi`lo`\n",
+ "html": "<p><code>hi</code>lo`</p>\n",
+ "example": 297,
+ "start_line": 5499,
+ "end_line": 5503,
+ "section": "Inlines"
+ },
+ {
+ "markdown": "\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\*\\+\\,\\-\\.\\/\\:\\;\\<\\=\\>\\?\\@\\[\\\\\\]\\^\\_\\`\\{\\|\\}\\~\n",
+ "html": "<p>!&quot;#$%&amp;'()*+,-./:;&lt;=&gt;?@[\\]^_`{|}~</p>\n",
+ "example": 298,
+ "start_line": 5513,
+ "end_line": 5517,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "\\\t\\A\\a\\ \\3\\φ\\«\n",
+ "html": "<p>\\\t\\A\\a\\ \\3\\φ\\«</p>\n",
+ "example": 299,
+ "start_line": 5523,
+ "end_line": 5527,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "\\*not emphasized*\n\\<br/> not a tag\n\\[not a link](/foo)\n\\`not code`\n1\\. not a list\n\\* not a list\n\\# not a heading\n\\[foo]: /url \"not a reference\"\n\\&ouml; not a character entity\n",
+ "html": "<p>*not emphasized*\n&lt;br/&gt; not a tag\n[not a link](/foo)\n`not code`\n1. not a list\n* not a list\n# not a heading\n[foo]: /url &quot;not a reference&quot;\n&amp;ouml; not a character entity</p>\n",
+ "example": 300,
+ "start_line": 5533,
+ "end_line": 5553,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "\\\\*emphasis*\n",
+ "html": "<p>\\<em>emphasis</em></p>\n",
+ "example": 301,
+ "start_line": 5558,
+ "end_line": 5562,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "foo\\\nbar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 302,
+ "start_line": 5567,
+ "end_line": 5573,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "`` \\[\\` ``\n",
+ "html": "<p><code>\\[\\`</code></p>\n",
+ "example": 303,
+ "start_line": 5579,
+ "end_line": 5583,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": " \\[\\]\n",
+ "html": "<pre><code>\\[\\]\n</code></pre>\n",
+ "example": 304,
+ "start_line": 5586,
+ "end_line": 5591,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "~~~\n\\[\\]\n~~~\n",
+ "html": "<pre><code>\\[\\]\n</code></pre>\n",
+ "example": 305,
+ "start_line": 5594,
+ "end_line": 5601,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "<http://example.com?find=\\*>\n",
+ "html": "<p><a href=\"http://example.com?find=%5C*\">http://example.com?find=\\*</a></p>\n",
+ "example": 306,
+ "start_line": 5604,
+ "end_line": 5608,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "<a href=\"/bar\\/)\">\n",
+ "html": "<a href=\"/bar\\/)\">\n",
+ "example": 307,
+ "start_line": 5611,
+ "end_line": 5615,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "[foo](/bar\\* \"ti\\*tle\")\n",
+ "html": "<p><a href=\"/bar*\" title=\"ti*tle\">foo</a></p>\n",
+ "example": 308,
+ "start_line": 5621,
+ "end_line": 5625,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /bar\\* \"ti\\*tle\"\n",
+ "html": "<p><a href=\"/bar*\" title=\"ti*tle\">foo</a></p>\n",
+ "example": 309,
+ "start_line": 5628,
+ "end_line": 5634,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "``` foo\\+bar\nfoo\n```\n",
+ "html": "<pre><code class=\"language-foo+bar\">foo\n</code></pre>\n",
+ "example": 310,
+ "start_line": 5637,
+ "end_line": 5644,
+ "section": "Backslash escapes"
+ },
+ {
+ "markdown": "&nbsp; &amp; &copy; &AElig; &Dcaron;\n&frac34; &HilbertSpace; &DifferentialD;\n&ClockwiseContourIntegral; &ngE;\n",
+ "html": "<p>  &amp; © Æ Ď\n¾ ℋ ⅆ\n∲ ≧̸</p>\n",
+ "example": 311,
+ "start_line": 5674,
+ "end_line": 5682,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&#35; &#1234; &#992; &#0;\n",
+ "html": "<p># Ӓ Ϡ �</p>\n",
+ "example": 312,
+ "start_line": 5693,
+ "end_line": 5697,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&#X22; &#XD06; &#xcab;\n",
+ "html": "<p>&quot; ആ ಫ</p>\n",
+ "example": 313,
+ "start_line": 5706,
+ "end_line": 5710,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&nbsp &x; &#; &#x;\n&#987654321;\n&#abcdef0;\n&ThisIsNotDefined; &hi?;\n",
+ "html": "<p>&amp;nbsp &amp;x; &amp;#; &amp;#x;\n&amp;#987654321;\n&amp;#abcdef0;\n&amp;ThisIsNotDefined; &amp;hi?;</p>\n",
+ "example": 314,
+ "start_line": 5715,
+ "end_line": 5725,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&copy\n",
+ "html": "<p>&amp;copy</p>\n",
+ "example": 315,
+ "start_line": 5732,
+ "end_line": 5736,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&MadeUpEntity;\n",
+ "html": "<p>&amp;MadeUpEntity;</p>\n",
+ "example": 316,
+ "start_line": 5742,
+ "end_line": 5746,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "<a href=\"&ouml;&ouml;.html\">\n",
+ "html": "<a href=\"&ouml;&ouml;.html\">\n",
+ "example": 317,
+ "start_line": 5753,
+ "end_line": 5757,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "[foo](/f&ouml;&ouml; \"f&ouml;&ouml;\")\n",
+ "html": "<p><a href=\"/f%C3%B6%C3%B6\" title=\"föö\">foo</a></p>\n",
+ "example": 318,
+ "start_line": 5760,
+ "end_line": 5764,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /f&ouml;&ouml; \"f&ouml;&ouml;\"\n",
+ "html": "<p><a href=\"/f%C3%B6%C3%B6\" title=\"föö\">foo</a></p>\n",
+ "example": 319,
+ "start_line": 5767,
+ "end_line": 5773,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "``` f&ouml;&ouml;\nfoo\n```\n",
+ "html": "<pre><code class=\"language-föö\">foo\n</code></pre>\n",
+ "example": 320,
+ "start_line": 5776,
+ "end_line": 5783,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "`f&ouml;&ouml;`\n",
+ "html": "<p><code>f&amp;ouml;&amp;ouml;</code></p>\n",
+ "example": 321,
+ "start_line": 5789,
+ "end_line": 5793,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": " f&ouml;f&ouml;\n",
+ "html": "<pre><code>f&amp;ouml;f&amp;ouml;\n</code></pre>\n",
+ "example": 322,
+ "start_line": 5796,
+ "end_line": 5801,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&#42;foo&#42;\n*foo*\n",
+ "html": "<p>*foo*\n<em>foo</em></p>\n",
+ "example": 323,
+ "start_line": 5808,
+ "end_line": 5814,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&#42; foo\n\n* foo\n",
+ "html": "<p>* foo</p>\n<ul>\n<li>foo</li>\n</ul>\n",
+ "example": 324,
+ "start_line": 5816,
+ "end_line": 5825,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "foo&#10;&#10;bar\n",
+ "html": "<p>foo\n\nbar</p>\n",
+ "example": 325,
+ "start_line": 5827,
+ "end_line": 5833,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "&#9;foo\n",
+ "html": "<p>\tfoo</p>\n",
+ "example": 326,
+ "start_line": 5835,
+ "end_line": 5839,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "[a](url &quot;tit&quot;)\n",
+ "html": "<p>[a](url &quot;tit&quot;)</p>\n",
+ "example": 327,
+ "start_line": 5842,
+ "end_line": 5846,
+ "section": "Entity and numeric character references"
+ },
+ {
+ "markdown": "`foo`\n",
+ "html": "<p><code>foo</code></p>\n",
+ "example": 328,
+ "start_line": 5870,
+ "end_line": 5874,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`` foo ` bar ``\n",
+ "html": "<p><code>foo ` bar</code></p>\n",
+ "example": 329,
+ "start_line": 5881,
+ "end_line": 5885,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` `` `\n",
+ "html": "<p><code>``</code></p>\n",
+ "example": 330,
+ "start_line": 5891,
+ "end_line": 5895,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` `` `\n",
+ "html": "<p><code> `` </code></p>\n",
+ "example": 331,
+ "start_line": 5899,
+ "end_line": 5903,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` a`\n",
+ "html": "<p><code> a</code></p>\n",
+ "example": 332,
+ "start_line": 5908,
+ "end_line": 5912,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` b `\n",
+ "html": "<p><code> b </code></p>\n",
+ "example": 333,
+ "start_line": 5917,
+ "end_line": 5921,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` `\n` `\n",
+ "html": "<p><code> </code>\n<code> </code></p>\n",
+ "example": 334,
+ "start_line": 5925,
+ "end_line": 5931,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "``\nfoo\nbar \nbaz\n``\n",
+ "html": "<p><code>foo bar baz</code></p>\n",
+ "example": 335,
+ "start_line": 5936,
+ "end_line": 5944,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "``\nfoo \n``\n",
+ "html": "<p><code>foo </code></p>\n",
+ "example": 336,
+ "start_line": 5946,
+ "end_line": 5952,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo bar \nbaz`\n",
+ "html": "<p><code>foo bar baz</code></p>\n",
+ "example": 337,
+ "start_line": 5957,
+ "end_line": 5962,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo\\`bar`\n",
+ "html": "<p><code>foo\\</code>bar`</p>\n",
+ "example": 338,
+ "start_line": 5974,
+ "end_line": 5978,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "``foo`bar``\n",
+ "html": "<p><code>foo`bar</code></p>\n",
+ "example": 339,
+ "start_line": 5985,
+ "end_line": 5989,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "` foo `` bar `\n",
+ "html": "<p><code>foo `` bar</code></p>\n",
+ "example": 340,
+ "start_line": 5991,
+ "end_line": 5995,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "*foo`*`\n",
+ "html": "<p>*foo<code>*</code></p>\n",
+ "example": 341,
+ "start_line": 6003,
+ "end_line": 6007,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "[not a `link](/foo`)\n",
+ "html": "<p>[not a <code>link](/foo</code>)</p>\n",
+ "example": 342,
+ "start_line": 6012,
+ "end_line": 6016,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`<a href=\"`\">`\n",
+ "html": "<p><code>&lt;a href=&quot;</code>&quot;&gt;`</p>\n",
+ "example": 343,
+ "start_line": 6022,
+ "end_line": 6026,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "<a href=\"`\">`\n",
+ "html": "<p><a href=\"`\">`</p>\n",
+ "example": 344,
+ "start_line": 6031,
+ "end_line": 6035,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`<http://foo.bar.`baz>`\n",
+ "html": "<p><code>&lt;http://foo.bar.</code>baz&gt;`</p>\n",
+ "example": 345,
+ "start_line": 6040,
+ "end_line": 6044,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "<http://foo.bar.`baz>`\n",
+ "html": "<p><a href=\"http://foo.bar.%60baz\">http://foo.bar.`baz</a>`</p>\n",
+ "example": 346,
+ "start_line": 6049,
+ "end_line": 6053,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "```foo``\n",
+ "html": "<p>```foo``</p>\n",
+ "example": 347,
+ "start_line": 6059,
+ "end_line": 6063,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo\n",
+ "html": "<p>`foo</p>\n",
+ "example": 348,
+ "start_line": 6066,
+ "end_line": 6070,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "`foo``bar``\n",
+ "html": "<p>`foo<code>bar</code></p>\n",
+ "example": 349,
+ "start_line": 6075,
+ "end_line": 6079,
+ "section": "Code spans"
+ },
+ {
+ "markdown": "*foo bar*\n",
+ "html": "<p><em>foo bar</em></p>\n",
+ "example": 350,
+ "start_line": 6292,
+ "end_line": 6296,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a * foo bar*\n",
+ "html": "<p>a * foo bar*</p>\n",
+ "example": 351,
+ "start_line": 6302,
+ "end_line": 6306,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a*\"foo\"*\n",
+ "html": "<p>a*&quot;foo&quot;*</p>\n",
+ "example": 352,
+ "start_line": 6313,
+ "end_line": 6317,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "* a *\n",
+ "html": "<p>* a *</p>\n",
+ "example": 353,
+ "start_line": 6322,
+ "end_line": 6326,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo*bar*\n",
+ "html": "<p>foo<em>bar</em></p>\n",
+ "example": 354,
+ "start_line": 6331,
+ "end_line": 6335,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "5*6*78\n",
+ "html": "<p>5<em>6</em>78</p>\n",
+ "example": 355,
+ "start_line": 6338,
+ "end_line": 6342,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo bar_\n",
+ "html": "<p><em>foo bar</em></p>\n",
+ "example": 356,
+ "start_line": 6347,
+ "end_line": 6351,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_ foo bar_\n",
+ "html": "<p>_ foo bar_</p>\n",
+ "example": 357,
+ "start_line": 6357,
+ "end_line": 6361,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a_\"foo\"_\n",
+ "html": "<p>a_&quot;foo&quot;_</p>\n",
+ "example": 358,
+ "start_line": 6367,
+ "end_line": 6371,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo_bar_\n",
+ "html": "<p>foo_bar_</p>\n",
+ "example": 359,
+ "start_line": 6376,
+ "end_line": 6380,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "5_6_78\n",
+ "html": "<p>5_6_78</p>\n",
+ "example": 360,
+ "start_line": 6383,
+ "end_line": 6387,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "пристаням_стремятся_\n",
+ "html": "<p>пристаням_стремятся_</p>\n",
+ "example": 361,
+ "start_line": 6390,
+ "end_line": 6394,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "aa_\"bb\"_cc\n",
+ "html": "<p>aa_&quot;bb&quot;_cc</p>\n",
+ "example": 362,
+ "start_line": 6400,
+ "end_line": 6404,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo-_(bar)_\n",
+ "html": "<p>foo-<em>(bar)</em></p>\n",
+ "example": 363,
+ "start_line": 6411,
+ "end_line": 6415,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo*\n",
+ "html": "<p>_foo*</p>\n",
+ "example": 364,
+ "start_line": 6423,
+ "end_line": 6427,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo bar *\n",
+ "html": "<p>*foo bar *</p>\n",
+ "example": 365,
+ "start_line": 6433,
+ "end_line": 6437,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo bar\n*\n",
+ "html": "<p>*foo bar\n*</p>\n",
+ "example": 366,
+ "start_line": 6442,
+ "end_line": 6448,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*(*foo)\n",
+ "html": "<p>*(*foo)</p>\n",
+ "example": 367,
+ "start_line": 6455,
+ "end_line": 6459,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*(*foo*)*\n",
+ "html": "<p><em>(<em>foo</em>)</em></p>\n",
+ "example": 368,
+ "start_line": 6465,
+ "end_line": 6469,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo*bar\n",
+ "html": "<p><em>foo</em>bar</p>\n",
+ "example": 369,
+ "start_line": 6474,
+ "end_line": 6478,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo bar _\n",
+ "html": "<p>_foo bar _</p>\n",
+ "example": 370,
+ "start_line": 6487,
+ "end_line": 6491,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(_foo)\n",
+ "html": "<p>_(_foo)</p>\n",
+ "example": 371,
+ "start_line": 6497,
+ "end_line": 6501,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(_foo_)_\n",
+ "html": "<p><em>(<em>foo</em>)</em></p>\n",
+ "example": 372,
+ "start_line": 6506,
+ "end_line": 6510,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo_bar\n",
+ "html": "<p>_foo_bar</p>\n",
+ "example": 373,
+ "start_line": 6515,
+ "end_line": 6519,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_пристаням_стремятся\n",
+ "html": "<p>_пристаням_стремятся</p>\n",
+ "example": 374,
+ "start_line": 6522,
+ "end_line": 6526,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo_bar_baz_\n",
+ "html": "<p><em>foo_bar_baz</em></p>\n",
+ "example": 375,
+ "start_line": 6529,
+ "end_line": 6533,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(bar)_.\n",
+ "html": "<p><em>(bar)</em>.</p>\n",
+ "example": 376,
+ "start_line": 6540,
+ "end_line": 6544,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo bar**\n",
+ "html": "<p><strong>foo bar</strong></p>\n",
+ "example": 377,
+ "start_line": 6549,
+ "end_line": 6553,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "** foo bar**\n",
+ "html": "<p>** foo bar**</p>\n",
+ "example": 378,
+ "start_line": 6559,
+ "end_line": 6563,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a**\"foo\"**\n",
+ "html": "<p>a**&quot;foo&quot;**</p>\n",
+ "example": 379,
+ "start_line": 6570,
+ "end_line": 6574,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo**bar**\n",
+ "html": "<p>foo<strong>bar</strong></p>\n",
+ "example": 380,
+ "start_line": 6579,
+ "end_line": 6583,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo bar__\n",
+ "html": "<p><strong>foo bar</strong></p>\n",
+ "example": 381,
+ "start_line": 6588,
+ "end_line": 6592,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__ foo bar__\n",
+ "html": "<p>__ foo bar__</p>\n",
+ "example": 382,
+ "start_line": 6598,
+ "end_line": 6602,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__\nfoo bar__\n",
+ "html": "<p>__\nfoo bar__</p>\n",
+ "example": 383,
+ "start_line": 6606,
+ "end_line": 6612,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "a__\"foo\"__\n",
+ "html": "<p>a__&quot;foo&quot;__</p>\n",
+ "example": 384,
+ "start_line": 6618,
+ "end_line": 6622,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo__bar__\n",
+ "html": "<p>foo__bar__</p>\n",
+ "example": 385,
+ "start_line": 6627,
+ "end_line": 6631,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "5__6__78\n",
+ "html": "<p>5__6__78</p>\n",
+ "example": 386,
+ "start_line": 6634,
+ "end_line": 6638,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "пристаням__стремятся__\n",
+ "html": "<p>пристаням__стремятся__</p>\n",
+ "example": 387,
+ "start_line": 6641,
+ "end_line": 6645,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo, __bar__, baz__\n",
+ "html": "<p><strong>foo, <strong>bar</strong>, baz</strong></p>\n",
+ "example": 388,
+ "start_line": 6648,
+ "end_line": 6652,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo-__(bar)__\n",
+ "html": "<p>foo-<strong>(bar)</strong></p>\n",
+ "example": 389,
+ "start_line": 6659,
+ "end_line": 6663,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo bar **\n",
+ "html": "<p>**foo bar **</p>\n",
+ "example": 390,
+ "start_line": 6672,
+ "end_line": 6676,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**(**foo)\n",
+ "html": "<p>**(**foo)</p>\n",
+ "example": 391,
+ "start_line": 6685,
+ "end_line": 6689,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*(**foo**)*\n",
+ "html": "<p><em>(<strong>foo</strong>)</em></p>\n",
+ "example": 392,
+ "start_line": 6695,
+ "end_line": 6699,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**Gomphocarpus (*Gomphocarpus physocarpus*, syn.\n*Asclepias physocarpa*)**\n",
+ "html": "<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.\n<em>Asclepias physocarpa</em>)</strong></p>\n",
+ "example": 393,
+ "start_line": 6702,
+ "end_line": 6708,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo \"*bar*\" foo**\n",
+ "html": "<p><strong>foo &quot;<em>bar</em>&quot; foo</strong></p>\n",
+ "example": 394,
+ "start_line": 6711,
+ "end_line": 6715,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo**bar\n",
+ "html": "<p><strong>foo</strong>bar</p>\n",
+ "example": 395,
+ "start_line": 6720,
+ "end_line": 6724,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo bar __\n",
+ "html": "<p>__foo bar __</p>\n",
+ "example": 396,
+ "start_line": 6732,
+ "end_line": 6736,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__(__foo)\n",
+ "html": "<p>__(__foo)</p>\n",
+ "example": 397,
+ "start_line": 6742,
+ "end_line": 6746,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_(__foo__)_\n",
+ "html": "<p><em>(<strong>foo</strong>)</em></p>\n",
+ "example": 398,
+ "start_line": 6752,
+ "end_line": 6756,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo__bar\n",
+ "html": "<p>__foo__bar</p>\n",
+ "example": 399,
+ "start_line": 6761,
+ "end_line": 6765,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__пристаням__стремятся\n",
+ "html": "<p>__пристаням__стремятся</p>\n",
+ "example": 400,
+ "start_line": 6768,
+ "end_line": 6772,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo__bar__baz__\n",
+ "html": "<p><strong>foo__bar__baz</strong></p>\n",
+ "example": 401,
+ "start_line": 6775,
+ "end_line": 6779,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__(bar)__.\n",
+ "html": "<p><strong>(bar)</strong>.</p>\n",
+ "example": 402,
+ "start_line": 6786,
+ "end_line": 6790,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo [bar](/url)*\n",
+ "html": "<p><em>foo <a href=\"/url\">bar</a></em></p>\n",
+ "example": 403,
+ "start_line": 6798,
+ "end_line": 6802,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo\nbar*\n",
+ "html": "<p><em>foo\nbar</em></p>\n",
+ "example": 404,
+ "start_line": 6805,
+ "end_line": 6811,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo __bar__ baz_\n",
+ "html": "<p><em>foo <strong>bar</strong> baz</em></p>\n",
+ "example": 405,
+ "start_line": 6817,
+ "end_line": 6821,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo _bar_ baz_\n",
+ "html": "<p><em>foo <em>bar</em> baz</em></p>\n",
+ "example": 406,
+ "start_line": 6824,
+ "end_line": 6828,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo_ bar_\n",
+ "html": "<p><em><em>foo</em> bar</em></p>\n",
+ "example": 407,
+ "start_line": 6831,
+ "end_line": 6835,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo *bar**\n",
+ "html": "<p><em>foo <em>bar</em></em></p>\n",
+ "example": 408,
+ "start_line": 6838,
+ "end_line": 6842,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo **bar** baz*\n",
+ "html": "<p><em>foo <strong>bar</strong> baz</em></p>\n",
+ "example": 409,
+ "start_line": 6845,
+ "end_line": 6849,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**bar**baz*\n",
+ "html": "<p><em>foo<strong>bar</strong>baz</em></p>\n",
+ "example": 410,
+ "start_line": 6851,
+ "end_line": 6855,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**bar*\n",
+ "html": "<p><em>foo**bar</em></p>\n",
+ "example": 411,
+ "start_line": 6875,
+ "end_line": 6879,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo** bar*\n",
+ "html": "<p><em><strong>foo</strong> bar</em></p>\n",
+ "example": 412,
+ "start_line": 6888,
+ "end_line": 6892,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo **bar***\n",
+ "html": "<p><em>foo <strong>bar</strong></em></p>\n",
+ "example": 413,
+ "start_line": 6895,
+ "end_line": 6899,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**bar***\n",
+ "html": "<p><em>foo<strong>bar</strong></em></p>\n",
+ "example": 414,
+ "start_line": 6902,
+ "end_line": 6906,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo***bar***baz\n",
+ "html": "<p>foo<em><strong>bar</strong></em>baz</p>\n",
+ "example": 415,
+ "start_line": 6913,
+ "end_line": 6917,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo******bar*********baz\n",
+ "html": "<p>foo<strong><strong><strong>bar</strong></strong></strong>***baz</p>\n",
+ "example": 416,
+ "start_line": 6919,
+ "end_line": 6923,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo **bar *baz* bim** bop*\n",
+ "html": "<p><em>foo <strong>bar <em>baz</em> bim</strong> bop</em></p>\n",
+ "example": 417,
+ "start_line": 6928,
+ "end_line": 6932,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo [*bar*](/url)*\n",
+ "html": "<p><em>foo <a href=\"/url\"><em>bar</em></a></em></p>\n",
+ "example": 418,
+ "start_line": 6935,
+ "end_line": 6939,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "** is not an empty emphasis\n",
+ "html": "<p>** is not an empty emphasis</p>\n",
+ "example": 419,
+ "start_line": 6944,
+ "end_line": 6948,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**** is not an empty strong emphasis\n",
+ "html": "<p>**** is not an empty strong emphasis</p>\n",
+ "example": 420,
+ "start_line": 6951,
+ "end_line": 6955,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo [bar](/url)**\n",
+ "html": "<p><strong>foo <a href=\"/url\">bar</a></strong></p>\n",
+ "example": 421,
+ "start_line": 6964,
+ "end_line": 6968,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo\nbar**\n",
+ "html": "<p><strong>foo\nbar</strong></p>\n",
+ "example": 422,
+ "start_line": 6971,
+ "end_line": 6977,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo _bar_ baz__\n",
+ "html": "<p><strong>foo <em>bar</em> baz</strong></p>\n",
+ "example": 423,
+ "start_line": 6983,
+ "end_line": 6987,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo __bar__ baz__\n",
+ "html": "<p><strong>foo <strong>bar</strong> baz</strong></p>\n",
+ "example": 424,
+ "start_line": 6990,
+ "end_line": 6994,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____foo__ bar__\n",
+ "html": "<p><strong><strong>foo</strong> bar</strong></p>\n",
+ "example": 425,
+ "start_line": 6997,
+ "end_line": 7001,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo **bar****\n",
+ "html": "<p><strong>foo <strong>bar</strong></strong></p>\n",
+ "example": 426,
+ "start_line": 7004,
+ "end_line": 7008,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo *bar* baz**\n",
+ "html": "<p><strong>foo <em>bar</em> baz</strong></p>\n",
+ "example": 427,
+ "start_line": 7011,
+ "end_line": 7015,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo*bar*baz**\n",
+ "html": "<p><strong>foo<em>bar</em>baz</strong></p>\n",
+ "example": 428,
+ "start_line": 7018,
+ "end_line": 7022,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo* bar**\n",
+ "html": "<p><strong><em>foo</em> bar</strong></p>\n",
+ "example": 429,
+ "start_line": 7025,
+ "end_line": 7029,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo *bar***\n",
+ "html": "<p><strong>foo <em>bar</em></strong></p>\n",
+ "example": 430,
+ "start_line": 7032,
+ "end_line": 7036,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo *bar **baz**\nbim* bop**\n",
+ "html": "<p><strong>foo <em>bar <strong>baz</strong>\nbim</em> bop</strong></p>\n",
+ "example": 431,
+ "start_line": 7041,
+ "end_line": 7047,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo [*bar*](/url)**\n",
+ "html": "<p><strong>foo <a href=\"/url\"><em>bar</em></a></strong></p>\n",
+ "example": 432,
+ "start_line": 7050,
+ "end_line": 7054,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__ is not an empty emphasis\n",
+ "html": "<p>__ is not an empty emphasis</p>\n",
+ "example": 433,
+ "start_line": 7059,
+ "end_line": 7063,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____ is not an empty strong emphasis\n",
+ "html": "<p>____ is not an empty strong emphasis</p>\n",
+ "example": 434,
+ "start_line": 7066,
+ "end_line": 7070,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo ***\n",
+ "html": "<p>foo ***</p>\n",
+ "example": 435,
+ "start_line": 7076,
+ "end_line": 7080,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo *\\**\n",
+ "html": "<p>foo <em>*</em></p>\n",
+ "example": 436,
+ "start_line": 7083,
+ "end_line": 7087,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo *_*\n",
+ "html": "<p>foo <em>_</em></p>\n",
+ "example": 437,
+ "start_line": 7090,
+ "end_line": 7094,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo *****\n",
+ "html": "<p>foo *****</p>\n",
+ "example": 438,
+ "start_line": 7097,
+ "end_line": 7101,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo **\\***\n",
+ "html": "<p>foo <strong>*</strong></p>\n",
+ "example": 439,
+ "start_line": 7104,
+ "end_line": 7108,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo **_**\n",
+ "html": "<p>foo <strong>_</strong></p>\n",
+ "example": 440,
+ "start_line": 7111,
+ "end_line": 7115,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo*\n",
+ "html": "<p>*<em>foo</em></p>\n",
+ "example": 441,
+ "start_line": 7122,
+ "end_line": 7126,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo**\n",
+ "html": "<p><em>foo</em>*</p>\n",
+ "example": 442,
+ "start_line": 7129,
+ "end_line": 7133,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo**\n",
+ "html": "<p>*<strong>foo</strong></p>\n",
+ "example": 443,
+ "start_line": 7136,
+ "end_line": 7140,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "****foo*\n",
+ "html": "<p>***<em>foo</em></p>\n",
+ "example": 444,
+ "start_line": 7143,
+ "end_line": 7147,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo***\n",
+ "html": "<p><strong>foo</strong>*</p>\n",
+ "example": 445,
+ "start_line": 7150,
+ "end_line": 7154,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo****\n",
+ "html": "<p><em>foo</em>***</p>\n",
+ "example": 446,
+ "start_line": 7157,
+ "end_line": 7161,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo ___\n",
+ "html": "<p>foo ___</p>\n",
+ "example": 447,
+ "start_line": 7167,
+ "end_line": 7171,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo _\\__\n",
+ "html": "<p>foo <em>_</em></p>\n",
+ "example": 448,
+ "start_line": 7174,
+ "end_line": 7178,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo _*_\n",
+ "html": "<p>foo <em>*</em></p>\n",
+ "example": 449,
+ "start_line": 7181,
+ "end_line": 7185,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo _____\n",
+ "html": "<p>foo _____</p>\n",
+ "example": 450,
+ "start_line": 7188,
+ "end_line": 7192,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo __\\___\n",
+ "html": "<p>foo <strong>_</strong></p>\n",
+ "example": 451,
+ "start_line": 7195,
+ "end_line": 7199,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "foo __*__\n",
+ "html": "<p>foo <strong>*</strong></p>\n",
+ "example": 452,
+ "start_line": 7202,
+ "end_line": 7206,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo_\n",
+ "html": "<p>_<em>foo</em></p>\n",
+ "example": 453,
+ "start_line": 7209,
+ "end_line": 7213,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo__\n",
+ "html": "<p><em>foo</em>_</p>\n",
+ "example": 454,
+ "start_line": 7220,
+ "end_line": 7224,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "___foo__\n",
+ "html": "<p>_<strong>foo</strong></p>\n",
+ "example": 455,
+ "start_line": 7227,
+ "end_line": 7231,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____foo_\n",
+ "html": "<p>___<em>foo</em></p>\n",
+ "example": 456,
+ "start_line": 7234,
+ "end_line": 7238,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo___\n",
+ "html": "<p><strong>foo</strong>_</p>\n",
+ "example": 457,
+ "start_line": 7241,
+ "end_line": 7245,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo____\n",
+ "html": "<p><em>foo</em>___</p>\n",
+ "example": 458,
+ "start_line": 7248,
+ "end_line": 7252,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo**\n",
+ "html": "<p><strong>foo</strong></p>\n",
+ "example": 459,
+ "start_line": 7258,
+ "end_line": 7262,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*_foo_*\n",
+ "html": "<p><em><em>foo</em></em></p>\n",
+ "example": 460,
+ "start_line": 7265,
+ "end_line": 7269,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__foo__\n",
+ "html": "<p><strong>foo</strong></p>\n",
+ "example": 461,
+ "start_line": 7272,
+ "end_line": 7276,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_*foo*_\n",
+ "html": "<p><em><em>foo</em></em></p>\n",
+ "example": 462,
+ "start_line": 7279,
+ "end_line": 7283,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "****foo****\n",
+ "html": "<p><strong><strong>foo</strong></strong></p>\n",
+ "example": 463,
+ "start_line": 7289,
+ "end_line": 7293,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "____foo____\n",
+ "html": "<p><strong><strong>foo</strong></strong></p>\n",
+ "example": 464,
+ "start_line": 7296,
+ "end_line": 7300,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "******foo******\n",
+ "html": "<p><strong><strong><strong>foo</strong></strong></strong></p>\n",
+ "example": 465,
+ "start_line": 7307,
+ "end_line": 7311,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "***foo***\n",
+ "html": "<p><em><strong>foo</strong></em></p>\n",
+ "example": 466,
+ "start_line": 7316,
+ "end_line": 7320,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_____foo_____\n",
+ "html": "<p><em><strong><strong>foo</strong></strong></em></p>\n",
+ "example": 467,
+ "start_line": 7323,
+ "end_line": 7327,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo _bar* baz_\n",
+ "html": "<p><em>foo _bar</em> baz_</p>\n",
+ "example": 468,
+ "start_line": 7332,
+ "end_line": 7336,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo __bar *baz bim__ bam*\n",
+ "html": "<p><em>foo <strong>bar *baz bim</strong> bam</em></p>\n",
+ "example": 469,
+ "start_line": 7339,
+ "end_line": 7343,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**foo **bar baz**\n",
+ "html": "<p>**foo <strong>bar baz</strong></p>\n",
+ "example": 470,
+ "start_line": 7348,
+ "end_line": 7352,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*foo *bar baz*\n",
+ "html": "<p>*foo <em>bar baz</em></p>\n",
+ "example": 471,
+ "start_line": 7355,
+ "end_line": 7359,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*[bar*](/url)\n",
+ "html": "<p>*<a href=\"/url\">bar*</a></p>\n",
+ "example": 472,
+ "start_line": 7364,
+ "end_line": 7368,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_foo [bar_](/url)\n",
+ "html": "<p>_foo <a href=\"/url\">bar_</a></p>\n",
+ "example": 473,
+ "start_line": 7371,
+ "end_line": 7375,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*<img src=\"foo\" title=\"*\"/>\n",
+ "html": "<p>*<img src=\"foo\" title=\"*\"/></p>\n",
+ "example": 474,
+ "start_line": 7378,
+ "end_line": 7382,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**<a href=\"**\">\n",
+ "html": "<p>**<a href=\"**\"></p>\n",
+ "example": 475,
+ "start_line": 7385,
+ "end_line": 7389,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__<a href=\"__\">\n",
+ "html": "<p>__<a href=\"__\"></p>\n",
+ "example": 476,
+ "start_line": 7392,
+ "end_line": 7396,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "*a `*`*\n",
+ "html": "<p><em>a <code>*</code></em></p>\n",
+ "example": 477,
+ "start_line": 7399,
+ "end_line": 7403,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "_a `_`_\n",
+ "html": "<p><em>a <code>_</code></em></p>\n",
+ "example": 478,
+ "start_line": 7406,
+ "end_line": 7410,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "**a<http://foo.bar/?q=**>\n",
+ "html": "<p>**a<a href=\"http://foo.bar/?q=**\">http://foo.bar/?q=**</a></p>\n",
+ "example": 479,
+ "start_line": 7413,
+ "end_line": 7417,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "__a<http://foo.bar/?q=__>\n",
+ "html": "<p>__a<a href=\"http://foo.bar/?q=__\">http://foo.bar/?q=__</a></p>\n",
+ "example": 480,
+ "start_line": 7420,
+ "end_line": 7424,
+ "section": "Emphasis and strong emphasis"
+ },
+ {
+ "markdown": "[link](/uri \"title\")\n",
+ "html": "<p><a href=\"/uri\" title=\"title\">link</a></p>\n",
+ "example": 481,
+ "start_line": 7503,
+ "end_line": 7507,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/uri)\n",
+ "html": "<p><a href=\"/uri\">link</a></p>\n",
+ "example": 482,
+ "start_line": 7512,
+ "end_line": 7516,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link]()\n",
+ "html": "<p><a href=\"\">link</a></p>\n",
+ "example": 483,
+ "start_line": 7521,
+ "end_line": 7525,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<>)\n",
+ "html": "<p><a href=\"\">link</a></p>\n",
+ "example": 484,
+ "start_line": 7528,
+ "end_line": 7532,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/my uri)\n",
+ "html": "<p>[link](/my uri)</p>\n",
+ "example": 485,
+ "start_line": 7537,
+ "end_line": 7541,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](</my uri>)\n",
+ "html": "<p><a href=\"/my%20uri\">link</a></p>\n",
+ "example": 486,
+ "start_line": 7543,
+ "end_line": 7547,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\nbar)\n",
+ "html": "<p>[link](foo\nbar)</p>\n",
+ "example": 487,
+ "start_line": 7552,
+ "end_line": 7558,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<foo\nbar>)\n",
+ "html": "<p>[link](<foo\nbar>)</p>\n",
+ "example": 488,
+ "start_line": 7560,
+ "end_line": 7566,
+ "section": "Links"
+ },
+ {
+ "markdown": "[a](<b)c>)\n",
+ "html": "<p><a href=\"b)c\">a</a></p>\n",
+ "example": 489,
+ "start_line": 7571,
+ "end_line": 7575,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<foo\\>)\n",
+ "html": "<p>[link](&lt;foo&gt;)</p>\n",
+ "example": 490,
+ "start_line": 7579,
+ "end_line": 7583,
+ "section": "Links"
+ },
+ {
+ "markdown": "[a](<b)c\n[a](<b)c>\n[a](<b>c)\n",
+ "html": "<p>[a](&lt;b)c\n[a](&lt;b)c&gt;\n[a](<b>c)</p>\n",
+ "example": 491,
+ "start_line": 7588,
+ "end_line": 7596,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](\\(foo\\))\n",
+ "html": "<p><a href=\"(foo)\">link</a></p>\n",
+ "example": 492,
+ "start_line": 7600,
+ "end_line": 7604,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo(and(bar)))\n",
+ "html": "<p><a href=\"foo(and(bar))\">link</a></p>\n",
+ "example": 493,
+ "start_line": 7609,
+ "end_line": 7613,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\\(and\\(bar\\))\n",
+ "html": "<p><a href=\"foo(and(bar)\">link</a></p>\n",
+ "example": 494,
+ "start_line": 7618,
+ "end_line": 7622,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](<foo(and(bar)>)\n",
+ "html": "<p><a href=\"foo(and(bar)\">link</a></p>\n",
+ "example": 495,
+ "start_line": 7625,
+ "end_line": 7629,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\\)\\:)\n",
+ "html": "<p><a href=\"foo):\">link</a></p>\n",
+ "example": 496,
+ "start_line": 7635,
+ "end_line": 7639,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](#fragment)\n\n[link](http://example.com#fragment)\n\n[link](http://example.com?foo=3#frag)\n",
+ "html": "<p><a href=\"#fragment\">link</a></p>\n<p><a href=\"http://example.com#fragment\">link</a></p>\n<p><a href=\"http://example.com?foo=3#frag\">link</a></p>\n",
+ "example": 497,
+ "start_line": 7644,
+ "end_line": 7654,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo\\bar)\n",
+ "html": "<p><a href=\"foo%5Cbar\">link</a></p>\n",
+ "example": 498,
+ "start_line": 7660,
+ "end_line": 7664,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](foo%20b&auml;)\n",
+ "html": "<p><a href=\"foo%20b%C3%A4\">link</a></p>\n",
+ "example": 499,
+ "start_line": 7676,
+ "end_line": 7680,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](\"title\")\n",
+ "html": "<p><a href=\"%22title%22\">link</a></p>\n",
+ "example": 500,
+ "start_line": 7687,
+ "end_line": 7691,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title\")\n[link](/url 'title')\n[link](/url (title))\n",
+ "html": "<p><a href=\"/url\" title=\"title\">link</a>\n<a href=\"/url\" title=\"title\">link</a>\n<a href=\"/url\" title=\"title\">link</a></p>\n",
+ "example": 501,
+ "start_line": 7696,
+ "end_line": 7704,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title \\\"&quot;\")\n",
+ "html": "<p><a href=\"/url\" title=\"title &quot;&quot;\">link</a></p>\n",
+ "example": 502,
+ "start_line": 7710,
+ "end_line": 7714,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title\")\n",
+ "html": "<p><a href=\"/url%C2%A0%22title%22\">link</a></p>\n",
+ "example": 503,
+ "start_line": 7720,
+ "end_line": 7724,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url \"title \"and\" title\")\n",
+ "html": "<p>[link](/url &quot;title &quot;and&quot; title&quot;)</p>\n",
+ "example": 504,
+ "start_line": 7729,
+ "end_line": 7733,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link](/url 'title \"and\" title')\n",
+ "html": "<p><a href=\"/url\" title=\"title &quot;and&quot; title\">link</a></p>\n",
+ "example": 505,
+ "start_line": 7738,
+ "end_line": 7742,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link]( /uri\n \"title\" )\n",
+ "html": "<p><a href=\"/uri\" title=\"title\">link</a></p>\n",
+ "example": 506,
+ "start_line": 7762,
+ "end_line": 7767,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link] (/uri)\n",
+ "html": "<p>[link] (/uri)</p>\n",
+ "example": 507,
+ "start_line": 7773,
+ "end_line": 7777,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link [foo [bar]]](/uri)\n",
+ "html": "<p><a href=\"/uri\">link [foo [bar]]</a></p>\n",
+ "example": 508,
+ "start_line": 7783,
+ "end_line": 7787,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link] bar](/uri)\n",
+ "html": "<p>[link] bar](/uri)</p>\n",
+ "example": 509,
+ "start_line": 7790,
+ "end_line": 7794,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link [bar](/uri)\n",
+ "html": "<p>[link <a href=\"/uri\">bar</a></p>\n",
+ "example": 510,
+ "start_line": 7797,
+ "end_line": 7801,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link \\[bar](/uri)\n",
+ "html": "<p><a href=\"/uri\">link [bar</a></p>\n",
+ "example": 511,
+ "start_line": 7804,
+ "end_line": 7808,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link *foo **bar** `#`*](/uri)\n",
+ "html": "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>\n",
+ "example": 512,
+ "start_line": 7813,
+ "end_line": 7817,
+ "section": "Links"
+ },
+ {
+ "markdown": "[![moon](moon.jpg)](/uri)\n",
+ "html": "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>\n",
+ "example": 513,
+ "start_line": 7820,
+ "end_line": 7824,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo [bar](/uri)](/uri)\n",
+ "html": "<p>[foo <a href=\"/uri\">bar</a>](/uri)</p>\n",
+ "example": 514,
+ "start_line": 7829,
+ "end_line": 7833,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *[bar [baz](/uri)](/uri)*](/uri)\n",
+ "html": "<p>[foo <em>[bar <a href=\"/uri\">baz</a>](/uri)</em>](/uri)</p>\n",
+ "example": 515,
+ "start_line": 7836,
+ "end_line": 7840,
+ "section": "Links"
+ },
+ {
+ "markdown": "![[[foo](uri1)](uri2)](uri3)\n",
+ "html": "<p><img src=\"uri3\" alt=\"[foo](uri2)\" /></p>\n",
+ "example": 516,
+ "start_line": 7843,
+ "end_line": 7847,
+ "section": "Links"
+ },
+ {
+ "markdown": "*[foo*](/uri)\n",
+ "html": "<p>*<a href=\"/uri\">foo*</a></p>\n",
+ "example": 517,
+ "start_line": 7853,
+ "end_line": 7857,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *bar](baz*)\n",
+ "html": "<p><a href=\"baz*\">foo *bar</a></p>\n",
+ "example": 518,
+ "start_line": 7860,
+ "end_line": 7864,
+ "section": "Links"
+ },
+ {
+ "markdown": "*foo [bar* baz]\n",
+ "html": "<p><em>foo [bar</em> baz]</p>\n",
+ "example": 519,
+ "start_line": 7870,
+ "end_line": 7874,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo <bar attr=\"](baz)\">\n",
+ "html": "<p>[foo <bar attr=\"](baz)\"></p>\n",
+ "example": 520,
+ "start_line": 7880,
+ "end_line": 7884,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo`](/uri)`\n",
+ "html": "<p>[foo<code>](/uri)</code></p>\n",
+ "example": 521,
+ "start_line": 7887,
+ "end_line": 7891,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo<http://example.com/?search=](uri)>\n",
+ "html": "<p>[foo<a href=\"http://example.com/?search=%5D(uri)\">http://example.com/?search=](uri)</a></p>\n",
+ "example": 522,
+ "start_line": 7894,
+ "end_line": 7898,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 523,
+ "start_line": 7932,
+ "end_line": 7938,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link [foo [bar]]][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link [foo [bar]]</a></p>\n",
+ "example": 524,
+ "start_line": 7947,
+ "end_line": 7953,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link \\[bar][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link [bar</a></p>\n",
+ "example": 525,
+ "start_line": 7956,
+ "end_line": 7962,
+ "section": "Links"
+ },
+ {
+ "markdown": "[link *foo **bar** `#`*][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>\n",
+ "example": 526,
+ "start_line": 7967,
+ "end_line": 7973,
+ "section": "Links"
+ },
+ {
+ "markdown": "[![moon](moon.jpg)][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>\n",
+ "example": 527,
+ "start_line": 7976,
+ "end_line": 7982,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo [bar](/uri)][ref]\n\n[ref]: /uri\n",
+ "html": "<p>[foo <a href=\"/uri\">bar</a>]<a href=\"/uri\">ref</a></p>\n",
+ "example": 528,
+ "start_line": 7987,
+ "end_line": 7993,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *bar [baz][ref]*][ref]\n\n[ref]: /uri\n",
+ "html": "<p>[foo <em>bar <a href=\"/uri\">baz</a></em>]<a href=\"/uri\">ref</a></p>\n",
+ "example": 529,
+ "start_line": 7996,
+ "end_line": 8002,
+ "section": "Links"
+ },
+ {
+ "markdown": "*[foo*][ref]\n\n[ref]: /uri\n",
+ "html": "<p>*<a href=\"/uri\">foo*</a></p>\n",
+ "example": 530,
+ "start_line": 8011,
+ "end_line": 8017,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo *bar][ref]\n\n[ref]: /uri\n",
+ "html": "<p><a href=\"/uri\">foo *bar</a></p>\n",
+ "example": 531,
+ "start_line": 8020,
+ "end_line": 8026,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo <bar attr=\"][ref]\">\n\n[ref]: /uri\n",
+ "html": "<p>[foo <bar attr=\"][ref]\"></p>\n",
+ "example": 532,
+ "start_line": 8032,
+ "end_line": 8038,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo`][ref]`\n\n[ref]: /uri\n",
+ "html": "<p>[foo<code>][ref]</code></p>\n",
+ "example": 533,
+ "start_line": 8041,
+ "end_line": 8047,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo<http://example.com/?search=][ref]>\n\n[ref]: /uri\n",
+ "html": "<p>[foo<a href=\"http://example.com/?search=%5D%5Bref%5D\">http://example.com/?search=][ref]</a></p>\n",
+ "example": 534,
+ "start_line": 8050,
+ "end_line": 8056,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][BaR]\n\n[bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 535,
+ "start_line": 8061,
+ "end_line": 8067,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Толпой][Толпой] is a Russian word.\n\n[ТОЛПОЙ]: /url\n",
+ "html": "<p><a href=\"/url\">Толпой</a> is a Russian word.</p>\n",
+ "example": 536,
+ "start_line": 8072,
+ "end_line": 8078,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Foo\n bar]: /url\n\n[Baz][Foo bar]\n",
+ "html": "<p><a href=\"/url\">Baz</a></p>\n",
+ "example": 537,
+ "start_line": 8084,
+ "end_line": 8091,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo] [bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p>[foo] <a href=\"/url\" title=\"title\">bar</a></p>\n",
+ "example": 538,
+ "start_line": 8097,
+ "end_line": 8103,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]\n[bar]\n\n[bar]: /url \"title\"\n",
+ "html": "<p>[foo]\n<a href=\"/url\" title=\"title\">bar</a></p>\n",
+ "example": 539,
+ "start_line": 8106,
+ "end_line": 8114,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]\n",
+ "html": "<p><a href=\"/url1\">bar</a></p>\n",
+ "example": 540,
+ "start_line": 8147,
+ "end_line": 8155,
+ "section": "Links"
+ },
+ {
+ "markdown": "[bar][foo\\!]\n\n[foo!]: /url\n",
+ "html": "<p>[bar][foo!]</p>\n",
+ "example": 541,
+ "start_line": 8162,
+ "end_line": 8168,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][ref[]\n\n[ref[]: /uri\n",
+ "html": "<p>[foo][ref[]</p>\n<p>[ref[]: /uri</p>\n",
+ "example": 542,
+ "start_line": 8174,
+ "end_line": 8181,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][ref[bar]]\n\n[ref[bar]]: /uri\n",
+ "html": "<p>[foo][ref[bar]]</p>\n<p>[ref[bar]]: /uri</p>\n",
+ "example": 543,
+ "start_line": 8184,
+ "end_line": 8191,
+ "section": "Links"
+ },
+ {
+ "markdown": "[[[foo]]]\n\n[[[foo]]]: /url\n",
+ "html": "<p>[[[foo]]]</p>\n<p>[[[foo]]]: /url</p>\n",
+ "example": 544,
+ "start_line": 8194,
+ "end_line": 8201,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][ref\\[]\n\n[ref\\[]: /uri\n",
+ "html": "<p><a href=\"/uri\">foo</a></p>\n",
+ "example": 545,
+ "start_line": 8204,
+ "end_line": 8210,
+ "section": "Links"
+ },
+ {
+ "markdown": "[bar\\\\]: /uri\n\n[bar\\\\]\n",
+ "html": "<p><a href=\"/uri\">bar\\</a></p>\n",
+ "example": 546,
+ "start_line": 8215,
+ "end_line": 8221,
+ "section": "Links"
+ },
+ {
+ "markdown": "[]\n\n[]: /uri\n",
+ "html": "<p>[]</p>\n<p>[]: /uri</p>\n",
+ "example": 547,
+ "start_line": 8226,
+ "end_line": 8233,
+ "section": "Links"
+ },
+ {
+ "markdown": "[\n ]\n\n[\n ]: /uri\n",
+ "html": "<p>[\n]</p>\n<p>[\n]: /uri</p>\n",
+ "example": 548,
+ "start_line": 8236,
+ "end_line": 8247,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 549,
+ "start_line": 8259,
+ "end_line": 8265,
+ "section": "Links"
+ },
+ {
+ "markdown": "[*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>\n",
+ "example": 550,
+ "start_line": 8268,
+ "end_line": 8274,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">Foo</a></p>\n",
+ "example": 551,
+ "start_line": 8279,
+ "end_line": 8285,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo] \n[]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a>\n[]</p>\n",
+ "example": 552,
+ "start_line": 8292,
+ "end_line": 8300,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 553,
+ "start_line": 8312,
+ "end_line": 8318,
+ "section": "Links"
+ },
+ {
+ "markdown": "[*foo* bar]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>\n",
+ "example": 554,
+ "start_line": 8321,
+ "end_line": 8327,
+ "section": "Links"
+ },
+ {
+ "markdown": "[[*foo* bar]]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p>[<a href=\"/url\" title=\"title\"><em>foo</em> bar</a>]</p>\n",
+ "example": 555,
+ "start_line": 8330,
+ "end_line": 8336,
+ "section": "Links"
+ },
+ {
+ "markdown": "[[bar [foo]\n\n[foo]: /url\n",
+ "html": "<p>[[bar <a href=\"/url\">foo</a></p>\n",
+ "example": 556,
+ "start_line": 8339,
+ "end_line": 8345,
+ "section": "Links"
+ },
+ {
+ "markdown": "[Foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><a href=\"/url\" title=\"title\">Foo</a></p>\n",
+ "example": 557,
+ "start_line": 8350,
+ "end_line": 8356,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo] bar\n\n[foo]: /url\n",
+ "html": "<p><a href=\"/url\">foo</a> bar</p>\n",
+ "example": 558,
+ "start_line": 8361,
+ "end_line": 8367,
+ "section": "Links"
+ },
+ {
+ "markdown": "\\[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>[foo]</p>\n",
+ "example": 559,
+ "start_line": 8373,
+ "end_line": 8379,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo*]: /url\n\n*[foo*]\n",
+ "html": "<p>*<a href=\"/url\">foo*</a></p>\n",
+ "example": 560,
+ "start_line": 8385,
+ "end_line": 8391,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar]\n\n[foo]: /url1\n[bar]: /url2\n",
+ "html": "<p><a href=\"/url2\">foo</a></p>\n",
+ "example": 561,
+ "start_line": 8397,
+ "end_line": 8404,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][]\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"/url1\">foo</a></p>\n",
+ "example": 562,
+ "start_line": 8406,
+ "end_line": 8412,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo]()\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"\">foo</a></p>\n",
+ "example": 563,
+ "start_line": 8416,
+ "end_line": 8422,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo](not a link)\n\n[foo]: /url1\n",
+ "html": "<p><a href=\"/url1\">foo</a>(not a link)</p>\n",
+ "example": 564,
+ "start_line": 8424,
+ "end_line": 8430,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url\n",
+ "html": "<p>[foo]<a href=\"/url\">bar</a></p>\n",
+ "example": 565,
+ "start_line": 8435,
+ "end_line": 8441,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[bar]: /url2\n",
+ "html": "<p><a href=\"/url2\">foo</a><a href=\"/url1\">baz</a></p>\n",
+ "example": 566,
+ "start_line": 8447,
+ "end_line": 8454,
+ "section": "Links"
+ },
+ {
+ "markdown": "[foo][bar][baz]\n\n[baz]: /url1\n[foo]: /url2\n",
+ "html": "<p>[foo]<a href=\"/url1\">bar</a></p>\n",
+ "example": 567,
+ "start_line": 8460,
+ "end_line": 8467,
+ "section": "Links"
+ },
+ {
+ "markdown": "![foo](/url \"title\")\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 568,
+ "start_line": 8483,
+ "end_line": 8487,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo *bar*]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train &amp; tracks\" /></p>\n",
+ "example": 569,
+ "start_line": 8490,
+ "end_line": 8496,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo ![bar](/url)](/url2)\n",
+ "html": "<p><img src=\"/url2\" alt=\"foo bar\" /></p>\n",
+ "example": 570,
+ "start_line": 8499,
+ "end_line": 8503,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo [bar](/url)](/url2)\n",
+ "html": "<p><img src=\"/url2\" alt=\"foo bar\" /></p>\n",
+ "example": 571,
+ "start_line": 8506,
+ "end_line": 8510,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo *bar*][]\n\n[foo *bar*]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train &amp; tracks\" /></p>\n",
+ "example": 572,
+ "start_line": 8520,
+ "end_line": 8526,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo *bar*][foobar]\n\n[FOOBAR]: train.jpg \"train & tracks\"\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo bar\" title=\"train &amp; tracks\" /></p>\n",
+ "example": 573,
+ "start_line": 8529,
+ "end_line": 8535,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo](train.jpg)\n",
+ "html": "<p><img src=\"train.jpg\" alt=\"foo\" /></p>\n",
+ "example": 574,
+ "start_line": 8538,
+ "end_line": 8542,
+ "section": "Images"
+ },
+ {
+ "markdown": "My ![foo bar](/path/to/train.jpg \"title\" )\n",
+ "html": "<p>My <img src=\"/path/to/train.jpg\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 575,
+ "start_line": 8545,
+ "end_line": 8549,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo](<url>)\n",
+ "html": "<p><img src=\"url\" alt=\"foo\" /></p>\n",
+ "example": 576,
+ "start_line": 8552,
+ "end_line": 8556,
+ "section": "Images"
+ },
+ {
+ "markdown": "![](/url)\n",
+ "html": "<p><img src=\"/url\" alt=\"\" /></p>\n",
+ "example": 577,
+ "start_line": 8559,
+ "end_line": 8563,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo][bar]\n\n[bar]: /url\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" /></p>\n",
+ "example": 578,
+ "start_line": 8568,
+ "end_line": 8574,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo][bar]\n\n[BAR]: /url\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" /></p>\n",
+ "example": 579,
+ "start_line": 8577,
+ "end_line": 8583,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 580,
+ "start_line": 8588,
+ "end_line": 8594,
+ "section": "Images"
+ },
+ {
+ "markdown": "![*foo* bar][]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 581,
+ "start_line": 8597,
+ "end_line": 8603,
+ "section": "Images"
+ },
+ {
+ "markdown": "![Foo][]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"Foo\" title=\"title\" /></p>\n",
+ "example": 582,
+ "start_line": 8608,
+ "end_line": 8614,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo] \n[]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" />\n[]</p>\n",
+ "example": 583,
+ "start_line": 8620,
+ "end_line": 8628,
+ "section": "Images"
+ },
+ {
+ "markdown": "![foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo\" title=\"title\" /></p>\n",
+ "example": 584,
+ "start_line": 8633,
+ "end_line": 8639,
+ "section": "Images"
+ },
+ {
+ "markdown": "![*foo* bar]\n\n[*foo* bar]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"foo bar\" title=\"title\" /></p>\n",
+ "example": 585,
+ "start_line": 8642,
+ "end_line": 8648,
+ "section": "Images"
+ },
+ {
+ "markdown": "![[foo]]\n\n[[foo]]: /url \"title\"\n",
+ "html": "<p>![[foo]]</p>\n<p>[[foo]]: /url &quot;title&quot;</p>\n",
+ "example": 586,
+ "start_line": 8653,
+ "end_line": 8660,
+ "section": "Images"
+ },
+ {
+ "markdown": "![Foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p><img src=\"/url\" alt=\"Foo\" title=\"title\" /></p>\n",
+ "example": 587,
+ "start_line": 8665,
+ "end_line": 8671,
+ "section": "Images"
+ },
+ {
+ "markdown": "!\\[foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>![foo]</p>\n",
+ "example": 588,
+ "start_line": 8677,
+ "end_line": 8683,
+ "section": "Images"
+ },
+ {
+ "markdown": "\\![foo]\n\n[foo]: /url \"title\"\n",
+ "html": "<p>!<a href=\"/url\" title=\"title\">foo</a></p>\n",
+ "example": 589,
+ "start_line": 8689,
+ "end_line": 8695,
+ "section": "Images"
+ },
+ {
+ "markdown": "<http://foo.bar.baz>\n",
+ "html": "<p><a href=\"http://foo.bar.baz\">http://foo.bar.baz</a></p>\n",
+ "example": 590,
+ "start_line": 8722,
+ "end_line": 8726,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<http://foo.bar.baz/test?q=hello&id=22&boolean>\n",
+ "html": "<p><a href=\"http://foo.bar.baz/test?q=hello&amp;id=22&amp;boolean\">http://foo.bar.baz/test?q=hello&amp;id=22&amp;boolean</a></p>\n",
+ "example": 591,
+ "start_line": 8729,
+ "end_line": 8733,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<irc://foo.bar:2233/baz>\n",
+ "html": "<p><a href=\"irc://foo.bar:2233/baz\">irc://foo.bar:2233/baz</a></p>\n",
+ "example": 592,
+ "start_line": 8736,
+ "end_line": 8740,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<MAILTO:FOO@BAR.BAZ>\n",
+ "html": "<p><a href=\"MAILTO:FOO@BAR.BAZ\">MAILTO:FOO@BAR.BAZ</a></p>\n",
+ "example": 593,
+ "start_line": 8745,
+ "end_line": 8749,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<a+b+c:d>\n",
+ "html": "<p><a href=\"a+b+c:d\">a+b+c:d</a></p>\n",
+ "example": 594,
+ "start_line": 8757,
+ "end_line": 8761,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<made-up-scheme://foo,bar>\n",
+ "html": "<p><a href=\"made-up-scheme://foo,bar\">made-up-scheme://foo,bar</a></p>\n",
+ "example": 595,
+ "start_line": 8764,
+ "end_line": 8768,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<http://../>\n",
+ "html": "<p><a href=\"http://../\">http://../</a></p>\n",
+ "example": 596,
+ "start_line": 8771,
+ "end_line": 8775,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<localhost:5001/foo>\n",
+ "html": "<p><a href=\"localhost:5001/foo\">localhost:5001/foo</a></p>\n",
+ "example": 597,
+ "start_line": 8778,
+ "end_line": 8782,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<http://foo.bar/baz bim>\n",
+ "html": "<p>&lt;http://foo.bar/baz bim&gt;</p>\n",
+ "example": 598,
+ "start_line": 8787,
+ "end_line": 8791,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<http://example.com/\\[\\>\n",
+ "html": "<p><a href=\"http://example.com/%5C%5B%5C\">http://example.com/\\[\\</a></p>\n",
+ "example": 599,
+ "start_line": 8796,
+ "end_line": 8800,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo@bar.example.com>\n",
+ "html": "<p><a href=\"mailto:foo@bar.example.com\">foo@bar.example.com</a></p>\n",
+ "example": 600,
+ "start_line": 8818,
+ "end_line": 8822,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo+special@Bar.baz-bar0.com>\n",
+ "html": "<p><a href=\"mailto:foo+special@Bar.baz-bar0.com\">foo+special@Bar.baz-bar0.com</a></p>\n",
+ "example": 601,
+ "start_line": 8825,
+ "end_line": 8829,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo\\+@bar.example.com>\n",
+ "html": "<p>&lt;foo+@bar.example.com&gt;</p>\n",
+ "example": 602,
+ "start_line": 8834,
+ "end_line": 8838,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<>\n",
+ "html": "<p>&lt;&gt;</p>\n",
+ "example": 603,
+ "start_line": 8843,
+ "end_line": 8847,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "< http://foo.bar >\n",
+ "html": "<p>&lt; http://foo.bar &gt;</p>\n",
+ "example": 604,
+ "start_line": 8850,
+ "end_line": 8854,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<m:abc>\n",
+ "html": "<p>&lt;m:abc&gt;</p>\n",
+ "example": 605,
+ "start_line": 8857,
+ "end_line": 8861,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<foo.bar.baz>\n",
+ "html": "<p>&lt;foo.bar.baz&gt;</p>\n",
+ "example": 606,
+ "start_line": 8864,
+ "end_line": 8868,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "http://example.com\n",
+ "html": "<p>http://example.com</p>\n",
+ "example": 607,
+ "start_line": 8871,
+ "end_line": 8875,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "foo@bar.example.com\n",
+ "html": "<p>foo@bar.example.com</p>\n",
+ "example": 608,
+ "start_line": 8878,
+ "end_line": 8882,
+ "section": "Autolinks"
+ },
+ {
+ "markdown": "<a><bab><c2c>\n",
+ "html": "<p><a><bab><c2c></p>\n",
+ "example": 609,
+ "start_line": 8960,
+ "end_line": 8964,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a/><b2/>\n",
+ "html": "<p><a/><b2/></p>\n",
+ "example": 610,
+ "start_line": 8969,
+ "end_line": 8973,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a /><b2\ndata=\"foo\" >\n",
+ "html": "<p><a /><b2\ndata=\"foo\" ></p>\n",
+ "example": 611,
+ "start_line": 8978,
+ "end_line": 8984,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a foo=\"bar\" bam = 'baz <em>\"</em>'\n_boolean zoop:33=zoop:33 />\n",
+ "html": "<p><a foo=\"bar\" bam = 'baz <em>\"</em>'\n_boolean zoop:33=zoop:33 /></p>\n",
+ "example": 612,
+ "start_line": 8989,
+ "end_line": 8995,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "Foo <responsive-image src=\"foo.jpg\" />\n",
+ "html": "<p>Foo <responsive-image src=\"foo.jpg\" /></p>\n",
+ "example": 613,
+ "start_line": 9000,
+ "end_line": 9004,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<33> <__>\n",
+ "html": "<p>&lt;33&gt; &lt;__&gt;</p>\n",
+ "example": 614,
+ "start_line": 9009,
+ "end_line": 9013,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a h*#ref=\"hi\">\n",
+ "html": "<p>&lt;a h*#ref=&quot;hi&quot;&gt;</p>\n",
+ "example": 615,
+ "start_line": 9018,
+ "end_line": 9022,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a href=\"hi'> <a href=hi'>\n",
+ "html": "<p>&lt;a href=&quot;hi'&gt; &lt;a href=hi'&gt;</p>\n",
+ "example": 616,
+ "start_line": 9027,
+ "end_line": 9031,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "< a><\nfoo><bar/ >\n<foo bar=baz\nbim!bop />\n",
+ "html": "<p>&lt; a&gt;&lt;\nfoo&gt;&lt;bar/ &gt;\n&lt;foo bar=baz\nbim!bop /&gt;</p>\n",
+ "example": 617,
+ "start_line": 9036,
+ "end_line": 9046,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a href='bar'title=title>\n",
+ "html": "<p>&lt;a href='bar'title=title&gt;</p>\n",
+ "example": 618,
+ "start_line": 9051,
+ "end_line": 9055,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "</a></foo >\n",
+ "html": "<p></a></foo ></p>\n",
+ "example": 619,
+ "start_line": 9060,
+ "end_line": 9064,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "</a href=\"foo\">\n",
+ "html": "<p>&lt;/a href=&quot;foo&quot;&gt;</p>\n",
+ "example": 620,
+ "start_line": 9069,
+ "end_line": 9073,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!-- this is a\ncomment - with hyphen -->\n",
+ "html": "<p>foo <!-- this is a\ncomment - with hyphen --></p>\n",
+ "example": 621,
+ "start_line": 9078,
+ "end_line": 9084,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!-- not a comment -- two hyphens -->\n",
+ "html": "<p>foo &lt;!-- not a comment -- two hyphens --&gt;</p>\n",
+ "example": 622,
+ "start_line": 9087,
+ "end_line": 9091,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!--> foo -->\n\nfoo <!-- foo--->\n",
+ "html": "<p>foo &lt;!--&gt; foo --&gt;</p>\n<p>foo &lt;!-- foo---&gt;</p>\n",
+ "example": 623,
+ "start_line": 9096,
+ "end_line": 9103,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <?php echo $a; ?>\n",
+ "html": "<p>foo <?php echo $a; ?></p>\n",
+ "example": 624,
+ "start_line": 9108,
+ "end_line": 9112,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <!ELEMENT br EMPTY>\n",
+ "html": "<p>foo <!ELEMENT br EMPTY></p>\n",
+ "example": 625,
+ "start_line": 9117,
+ "end_line": 9121,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <![CDATA[>&<]]>\n",
+ "html": "<p>foo <![CDATA[>&<]]></p>\n",
+ "example": 626,
+ "start_line": 9126,
+ "end_line": 9130,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <a href=\"&ouml;\">\n",
+ "html": "<p>foo <a href=\"&ouml;\"></p>\n",
+ "example": 627,
+ "start_line": 9136,
+ "end_line": 9140,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo <a href=\"\\*\">\n",
+ "html": "<p>foo <a href=\"\\*\"></p>\n",
+ "example": 628,
+ "start_line": 9145,
+ "end_line": 9149,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "<a href=\"\\\"\">\n",
+ "html": "<p>&lt;a href=&quot;&quot;&quot;&gt;</p>\n",
+ "example": 629,
+ "start_line": 9152,
+ "end_line": 9156,
+ "section": "Raw HTML"
+ },
+ {
+ "markdown": "foo \nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 630,
+ "start_line": 9166,
+ "end_line": 9172,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\\\nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 631,
+ "start_line": 9178,
+ "end_line": 9184,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo \nbaz\n",
+ "html": "<p>foo<br />\nbaz</p>\n",
+ "example": 632,
+ "start_line": 9189,
+ "end_line": 9195,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo \n bar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 633,
+ "start_line": 9200,
+ "end_line": 9206,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\\\n bar\n",
+ "html": "<p>foo<br />\nbar</p>\n",
+ "example": 634,
+ "start_line": 9209,
+ "end_line": 9215,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "*foo \nbar*\n",
+ "html": "<p><em>foo<br />\nbar</em></p>\n",
+ "example": 635,
+ "start_line": 9221,
+ "end_line": 9227,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "*foo\\\nbar*\n",
+ "html": "<p><em>foo<br />\nbar</em></p>\n",
+ "example": 636,
+ "start_line": 9230,
+ "end_line": 9236,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "`code \nspan`\n",
+ "html": "<p><code>code span</code></p>\n",
+ "example": 637,
+ "start_line": 9241,
+ "end_line": 9246,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "`code\\\nspan`\n",
+ "html": "<p><code>code\\ span</code></p>\n",
+ "example": 638,
+ "start_line": 9249,
+ "end_line": 9254,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "<a href=\"foo \nbar\">\n",
+ "html": "<p><a href=\"foo \nbar\"></p>\n",
+ "example": 639,
+ "start_line": 9259,
+ "end_line": 9265,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "<a href=\"foo\\\nbar\">\n",
+ "html": "<p><a href=\"foo\\\nbar\"></p>\n",
+ "example": 640,
+ "start_line": 9268,
+ "end_line": 9274,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\\\n",
+ "html": "<p>foo\\</p>\n",
+ "example": 641,
+ "start_line": 9281,
+ "end_line": 9285,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo \n",
+ "html": "<p>foo</p>\n",
+ "example": 642,
+ "start_line": 9288,
+ "end_line": 9292,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "### foo\\\n",
+ "html": "<h3>foo\\</h3>\n",
+ "example": 643,
+ "start_line": 9295,
+ "end_line": 9299,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "### foo \n",
+ "html": "<h3>foo</h3>\n",
+ "example": 644,
+ "start_line": 9302,
+ "end_line": 9306,
+ "section": "Hard line breaks"
+ },
+ {
+ "markdown": "foo\nbaz\n",
+ "html": "<p>foo\nbaz</p>\n",
+ "example": 645,
+ "start_line": 9317,
+ "end_line": 9323,
+ "section": "Soft line breaks"
+ },
+ {
+ "markdown": "foo \n baz\n",
+ "html": "<p>foo\nbaz</p>\n",
+ "example": 646,
+ "start_line": 9329,
+ "end_line": 9335,
+ "section": "Soft line breaks"
+ },
+ {
+ "markdown": "hello $.;'there\n",
+ "html": "<p>hello $.;'there</p>\n",
+ "example": 647,
+ "start_line": 9349,
+ "end_line": 9353,
+ "section": "Textual content"
+ },
+ {
+ "markdown": "Foo χρῆν\n",
+ "html": "<p>Foo χρῆν</p>\n",
+ "example": 648,
+ "start_line": 9356,
+ "end_line": 9360,
+ "section": "Textual content"
+ },
+ {
+ "markdown": "Multiple spaces\n",
+ "html": "<p>Multiple spaces</p>\n",
+ "example": 649,
+ "start_line": 9365,
+ "end_line": 9369,
+ "section": "Textual content"
+ }
+] \ No newline at end of file
diff --git a/tests/test_commonmark/spec.sh b/tests/test_commonmark/spec.sh
new file mode 100755
index 0000000..034fd86
--- /dev/null
+++ b/tests/test_commonmark/spec.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+set -e
+
+REPO="https://github.com/commonmark/CommonMark.git"
+VERSION="0.29"
+
+function main {
+ echo "Cloning from repo: $REPO..."
+ git clone --quiet $REPO
+
+ echo "Using version $VERSION..."
+ cd "CommonMark"
+ git checkout --quiet $VERSION
+
+ echo "Dumping tests file..."
+ python3 "test/spec_tests.py" --dump-tests > "../commonmark.json"
+
+ echo "Cleaning up..."
+ cd ..
+ rm -rf CommonMark
+
+ echo "Done."
+}
+
+main
diff --git a/tests/test_commonmark/test_commonmark.py b/tests/test_commonmark/test_commonmark.py
new file mode 100644
index 0000000..3cee1eb
--- /dev/null
+++ b/tests/test_commonmark/test_commonmark.py
@@ -0,0 +1,44 @@
+"""In this module tests are run against the full test set,
+provided by https://github.com/commonmark/CommonMark.git.
+"""
+import json
+import os
+
+import pytest
+from markdown_it.renderer import RendererHTML
+
+from myst_parser.config.main import MdParserConfig
+from myst_parser.parsers.mdit import create_md_parser
+
+with open(
+ os.path.join(os.path.dirname(__file__), "commonmark.json"), encoding="utf8"
+) as fin:
+ tests = json.load(fin)
+
+
+@pytest.mark.parametrize("entry", tests)
+def test_commonmark(entry):
+ if entry["example"] == 14:
+ # This is just a test that +++ are not parsed as thematic breaks
+ pytest.skip("Expects '+++' to be unconverted (not block break).")
+ if entry["example"] in [66, 68]:
+ # Front matter is supported by numerous Markdown flavours,
+ # but not strictly CommonMark,
+ # see: https://talk.commonmark.org/t/metadata-in-documents/721/86
+ pytest.skip(
+ "Thematic breaks on the first line conflict with front matter syntax"
+ )
+ test_case = entry["markdown"]
+ md = create_md_parser(MdParserConfig(), RendererHTML)
+ output = md.render(test_case)
+
+ if entry["example"] == 593:
+ # this doesn't have any bearing on the output
+ output = output.replace("mailto", "MAILTO")
+ if entry["example"] in [187, 209, 210]:
+ # this doesn't have any bearing on the output
+ output = output.replace(
+ "<blockquote></blockquote>", "<blockquote>\n</blockquote>"
+ )
+
+ assert output == entry["html"]
diff --git a/tests/test_docutils.py b/tests/test_docutils.py
new file mode 100644
index 0000000..f6f5d36
--- /dev/null
+++ b/tests/test_docutils.py
@@ -0,0 +1,116 @@
+import io
+from dataclasses import dataclass, field, fields
+from textwrap import dedent
+
+import pytest
+from docutils import VersionInfo, __version_info__
+from typing_extensions import Literal
+
+from myst_parser.mdit_to_docutils.base import make_document
+from myst_parser.parsers.docutils_ import (
+ Parser,
+ attr_to_optparse_option,
+ cli_html,
+ cli_html5,
+ cli_latex,
+ cli_pseudoxml,
+ cli_xml,
+)
+
+
+def test_attr_to_optparse_option():
+ @dataclass
+ class Config:
+ name: Literal["a"] = field(default="default")
+
+ output = attr_to_optparse_option(fields(Config)[0], "default")
+ assert len(output) == 3
+
+
+def test_parser():
+ """Test calling `Parser.parse` directly."""
+ parser = Parser()
+ document = make_document(parser_cls=Parser)
+ parser.parse("something", document)
+ assert (
+ document.pformat().strip()
+ == '<document source="notset">\n <paragraph>\n something'
+ )
+
+
+def test_cli_html(monkeypatch, capsys):
+ monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
+ cli_html([])
+ captured = capsys.readouterr()
+ assert not captured.err
+ assert "text" in captured.out
+
+
+def test_cli_html5(monkeypatch, capsys):
+ monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
+ cli_html5([])
+ captured = capsys.readouterr()
+ assert not captured.err
+ assert "text" in captured.out
+
+
+def test_cli_latex(monkeypatch, capsys):
+ monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
+ cli_latex([])
+ captured = capsys.readouterr()
+ assert not captured.err
+ assert "text" in captured.out
+
+
+def test_cli_xml(monkeypatch, capsys):
+ monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
+ cli_xml([])
+ captured = capsys.readouterr()
+ assert not captured.err
+ assert "text" in captured.out
+
+
+def test_cli_pseudoxml(monkeypatch, capsys):
+ monkeypatch.setattr("sys.stdin", io.TextIOWrapper(io.BytesIO(b"text")))
+ cli_pseudoxml([])
+ captured = capsys.readouterr()
+ assert not captured.err
+ assert "text" in captured.out
+
+
+def test_help_text():
+ """Test retrieving settings help text."""
+ from docutils.frontend import OptionParser
+
+ stream = io.StringIO()
+ OptionParser(components=(Parser,)).print_help(stream)
+ assert "MyST options" in stream.getvalue()
+
+
+@pytest.mark.skipif(
+ __version_info__ < VersionInfo(0, 17, 0, "final", 0, True),
+ reason="parser option added in docutils 0.17",
+)
+def test_include_from_rst(tmp_path):
+ """Test including a MyST file from within an RST file."""
+ from docutils.parsers.rst import Parser as RSTParser
+
+ include_path = tmp_path.joinpath("include.md")
+ include_path.write_text("# Title")
+
+ parser = RSTParser()
+ document = make_document(parser_cls=RSTParser)
+ parser.parse(
+ f".. include:: {include_path}\n :parser: myst_parser.docutils_", document
+ )
+ assert (
+ document.pformat().strip()
+ == dedent(
+ """\
+ <document source="notset">
+ <section ids="title" names="title">
+ <title>
+ Title
+ """
+ ).strip()
+ )
diff --git a/tests/test_html/html_ast.md b/tests/test_html/html_ast.md
new file mode 100644
index 0000000..0abbdee
--- /dev/null
+++ b/tests/test_html/html_ast.md
@@ -0,0 +1,124 @@
+tags
+.
+<html>
+<head>
+<title class="a b" other="x">Title of the document</title>
+</head>
+<body>
+The content of the document......
+</body>
+</html>
+.
+Root('')
+Tag('html')
+Data('\n')
+Tag('head')
+Data('\n')
+Tag('title', {'class': 'a b', 'other': 'x'})
+Data('Title of the docu...')
+Data('\n')
+Data('\n')
+Tag('body')
+Data('\nThe content of t...')
+Data('\n')
+Data('\n')
+.
+
+un-closed tags
+.
+<div class="a">
+<div class="b">
+.
+Root('')
+Tag('div', {'class': 'a'})
+Data('\n')
+Tag('div', {'class': 'b'})
+Data('\n')
+.
+
+xtag
+.
+<img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600"/>
+.
+Root('')
+XTag('img', {'src': 'img_girl.jpg', 'alt': 'Girl in a jacket', 'width': '500', 'height': '600'})
+Data('\n')
+.
+
+data
+.
+a
+.
+Root('')
+Data('a\n')
+.
+
+declaration
+.
+<!DOCTYPE html>
+.
+Root('')
+Declaration('DOCTYPE html')
+Data('\n')
+.
+
+process information
+.
+<?xml-stylesheet ?>
+.
+Root('')
+Pi('xml-stylesheet ?')
+Data('\n')
+.
+
+entities
+.
+&amp;
+
+&#123;
+.
+Root('')
+Entity('amp')
+Data('\n\n')
+Char('123')
+Data('\n')
+.
+
+comments
+.
+<!--This is a comment. Comments are not displayed in the browser
+-->
+.
+Root('')
+Comment('This is a comment...')
+Data('\n')
+.
+
+admonition
+.
+<div class="admonition tip alert alert-warning">
+<div class="admonition-title" style="font-weight: bold;">Tip</div>
+parameter allows to get a deterministic results even if we
+use some random process (i.e. data shuffling).
+</div>
+.
+Root('')
+Tag('div', {'class': 'admonition tip alert alert-warning'})
+Data('\n')
+Tag('div', {'class': 'admonition-title', 'style': 'font-weight: bold;'})
+Data('Tip')
+Data('\nparameter allows...')
+Data('\n')
+.
+
+image
+.
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="300px">
+.
+Root('')
+VoidTag('img', {'src': 'img/fun-fish.png', 'alt': 'fishy', 'class': 'bg-primary mb-1', 'width': '200px'})
+Data('\n')
+VoidTag('img', {'src': 'img/fun-fish.png', 'alt': 'fishy', 'class': 'bg-primary mb-1', 'width': '300px'})
+Data('\n')
+.
diff --git a/tests/test_html/html_round_trip.md b/tests/test_html/html_round_trip.md
new file mode 100644
index 0000000..c5ceb5c
--- /dev/null
+++ b/tests/test_html/html_round_trip.md
@@ -0,0 +1,87 @@
+tags
+.
+<html>
+<head>
+<title class="a b" other="x">Title of the document</title>
+</head>
+<body>
+The content of the document......
+</body>
+</html>
+.
+<html>
+<head>
+<title class="a b" other="x">Title of the document</title>
+</head>
+<body>
+The content of the document......
+</body>
+</html>
+.
+
+un-closed tags
+.
+<div class="a">
+<div class="b">
+.
+<div class="a">
+<div class="b">
+</div></div>
+.
+
+xtag
+.
+ <img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600"/>
+.
+ <img src="img_girl.jpg" alt="Girl in a jacket" width="500" height="600"/>
+.
+
+data
+.
+a
+.
+a
+.
+
+declaration
+.
+<!DOCTYPE html>
+.
+<!DOCTYPE html>
+.
+
+process information
+.
+<?xml-stylesheet ?>
+.
+<?xml-stylesheet ?>
+.
+
+entities
+.
+&amp;
+
+&#123;
+.
+&amp;
+
+&#123;
+.
+
+comments
+.
+<!--This is a comment. Comments are not displayed in the browser
+-->
+.
+<!--This is a comment. Comments are not displayed in the browser
+-->
+.
+
+image
+.
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="300px">
+.
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+<img src="img/fun-fish.png" alt="fishy" class="bg-primary mb-1" width="300px">
+.
diff --git a/tests/test_html/html_to_nodes.md b/tests/test_html/html_to_nodes.md
new file mode 100644
index 0000000..e118eb9
--- /dev/null
+++ b/tests/test_html/html_to_nodes.md
@@ -0,0 +1,199 @@
+empty
+.
+
+.
+<container>
+ <raw format="html" xml:space="preserve">
+.
+
+text
+.
+abc
+.
+<container>
+ <raw format="html" xml:space="preserve">
+ abc
+.
+
+normal HTML
+.
+<div></div>
+.
+<container>
+ <raw format="html" xml:space="preserve">
+ <div></div>
+.
+
+image no src
+.
+<img>
+.
+<container>
+ <system_message>
+ <paragraph>
+ error
+.
+
+image
+.
+<img src="a">
+.
+<container>
+ <Element first="a" name="image" position="0">
+.
+
+image unknown attribute
+.
+<img src="a" other="b">
+.
+<container>
+ <Element first="a" name="image" position="0">
+.
+
+image known attributes
+.
+<img src="a" height="200px" class="a b" name="b" align="left">
+.
+<container>
+ <Element first="a" name="image" position="0">
+ :align: left
+ :class: a b
+ :height: 200px
+ :name: b
+.
+
+multiple images
+.
+<img src="a">
+<img src="b">
+.
+<container>
+ <Element first="a" name="image" position="0">
+ <Element first="b" name="image" position="0">
+.
+
+admonition no close
+.
+<div class="admonition">
+.
+<container>
+ <Element first="Note" name="admonition" position="0">
+ :class: admonition
+.
+
+admonition
+.
+<div class="admonition">
+</div>
+.
+<container>
+ <Element first="Note" name="admonition" position="0">
+ :class: admonition
+.
+
+admonition attributes
+.
+<div class="admonition tip" name="aname">
+</div>
+.
+<container>
+ <Element first="Note" name="admonition" position="0">
+ :class: admonition tip
+ :name: aname
+.
+
+admonition div-title
+.
+<div class="admonition tip">
+<div class="title">*Hallo*</div>
+.
+<container>
+ <Element first="*Hallo*" name="admonition" position="0">
+ :class: admonition tip
+.
+
+admonition p-title
+.
+<div class="admonition tip">
+<p class="title">*Hallo*</p>
+.
+<container>
+ <Element first="*Hallo*" name="admonition" position="0">
+ :class: admonition tip
+.
+
+admonition title+content
+.
+<div class="admonition">
+<div class="title">*Hallo*</div>
+content
+</div>
+.
+<container>
+ <Element first="*Hallo*" name="admonition" position="0">
+ :class: admonition
+
+ content
+.
+
+admonition multiple
+.
+<div class="admonition">
+<div class="title">first</div>
+content 1
+</div>
+<div class="admonition">
+<div class="title">second</div>
+content 2
+</div>
+.
+<container>
+ <Element first="first" name="admonition" position="0">
+ :class: admonition
+
+ content 1
+ <Element first="second" name="admonition" position="0">
+ :class: admonition
+
+ content 2
+.
+
+admonition with paragraphs
+.
+<div class="admonition">
+<p>paragraph 1</p>
+<p>paragraph 2</p>
+</div>
+.
+<container>
+ <Element first="Note" name="admonition" position="0">
+ :class: admonition
+
+ paragraph 1
+
+ paragraph 2
+.
+
+nested
+.
+<div class="admonition">
+<p>Some **content**</p>
+ <div class="admonition tip">
+ <div class="title">A *title*</div>
+ <p>Paragraph 1</p>
+ <p>Paragraph 2</p>
+ </div>
+</div>
+.
+<container>
+ <Element first="Note" name="admonition" position="0">
+ :class: admonition
+
+ Some **content**
+
+ <div class="admonition tip">
+ <div class="title">A *title*</div>
+ <p>Paragraph 1</p>
+ <p>Paragraph 2</p>
+ </div>
+.
diff --git a/tests/test_html/test_html_to_nodes.py b/tests/test_html/test_html_to_nodes.py
new file mode 100644
index 0000000..207a627
--- /dev/null
+++ b/tests/test_html/test_html_to_nodes.py
@@ -0,0 +1,35 @@
+from pathlib import Path
+from unittest.mock import Mock
+
+import pytest
+from docutils import nodes
+
+from myst_parser.config.main import MdParserConfig
+from myst_parser.mdit_to_docutils.html_to_nodes import html_to_nodes
+
+FIXTURE_PATH = Path(__file__).parent
+
+
+@pytest.fixture()
+def mock_renderer():
+ def _run_directive(name: str, first_line: str, content: str, position: int):
+ node = nodes.Element(name=name, first=first_line, position=position)
+ node += nodes.Text(content)
+ return [node]
+
+ return Mock(
+ md_config=MdParserConfig(enable_extensions=["html_image", "html_admonition"]),
+ document={"source": "source"},
+ reporter=Mock(
+ warning=Mock(return_value=nodes.system_message("warning")),
+ error=Mock(return_value=nodes.system_message("error")),
+ ),
+ run_directive=_run_directive,
+ )
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "html_to_nodes.md")
+def test_html_to_nodes(file_params, mock_renderer):
+ output = nodes.container()
+ output += html_to_nodes(file_params.content, line_number=0, renderer=mock_renderer)
+ file_params.assert_expected(output.pformat(), rstrip=True)
diff --git a/tests/test_html/test_parse_html.py b/tests/test_html/test_parse_html.py
new file mode 100644
index 0000000..3b4cdc1
--- /dev/null
+++ b/tests/test_html/test_parse_html.py
@@ -0,0 +1,41 @@
+from pathlib import Path
+
+import pytest
+
+from myst_parser.parsers.parse_html import tokenize_html
+
+FIXTURE_PATH = Path(__file__).parent
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "html_ast.md")
+def test_html_ast(file_params):
+ tokens = "\n".join(
+ repr(t) for t in tokenize_html(file_params.content).walk(include_self=True)
+ )
+ file_params.assert_expected(tokens, rstrip=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "html_round_trip.md")
+def test_html_round_trip(file_params):
+ ast = tokenize_html(file_params.content)
+ file_params.assert_expected(str(ast), rstrip=True)
+
+
+def test_render_overrides():
+ text = "<div><abc></abc></div>"
+ ast = tokenize_html(text)
+
+ def _render_abc(element, *args, **kwargs):
+ return "hallo"
+
+ output = ast.render(tag_overrides={"abc": _render_abc})
+ assert output == "<div>hallo</div>"
+
+
+def test_ast_find():
+ text = (
+ '<div class="a"><div class="c"><x/><y>z</y><div class="a b"></div></div></div>'
+ )
+ ast = tokenize_html(text)
+ found = list(ast.find("div", classes=["a"]))
+ assert [e.attrs.classes for e in found] == [["a"], ["a", "b"]]
diff --git a/tests/test_renderers/fixtures/amsmath.md b/tests/test_renderers/fixtures/amsmath.md
new file mode 100644
index 0000000..fe3713e
--- /dev/null
+++ b/tests/test_renderers/fixtures/amsmath.md
@@ -0,0 +1,52 @@
+Single Line:
+.
+\begin{equation} a \end{equation}
+.
+<document source="<src>/index.md">
+ <target ids="equation-mock-uuid">
+ <math_block classes="amsmath" docname="index" label="mock-uuid" nowrap="True" number="1" xml:space="preserve">
+ \begin{equation} a \end{equation}
+.
+
+Multi Line:
+.
+\begin{equation}
+a
+\end{equation}
+.
+<document source="<src>/index.md">
+ <target ids="equation-mock-uuid">
+ <math_block classes="amsmath" docname="index" label="mock-uuid" nowrap="True" number="1" xml:space="preserve">
+ \begin{equation}
+ a
+ \end{equation}
+.
+
+Multi Line no number:
+.
+\begin{equation*}
+a
+\end{equation*}
+.
+<document source="<src>/index.md">
+ <math_block classes="amsmath" nowrap="True" number="True" xml:space="preserve">
+ \begin{equation*}
+ a
+ \end{equation*}
+.
+
+In list:
+.
+- \begin{equation}
+ a = 1
+ \end{equation}
+.
+<document source="<src>/index.md">
+ <bullet_list bullet="-">
+ <list_item>
+ <target ids="equation-mock-uuid">
+ <math_block classes="amsmath" docname="index" label="mock-uuid" nowrap="True" number="1" xml:space="preserve">
+ \begin{equation}
+ a = 1
+ \end{equation}
+.
diff --git a/tests/test_renderers/fixtures/containers.md b/tests/test_renderers/fixtures/containers.md
new file mode 100644
index 0000000..f67bea8
--- /dev/null
+++ b/tests/test_renderers/fixtures/containers.md
@@ -0,0 +1,31 @@
+Basic note:
+.
+::: {note}
+*hallo*
+:::
+.
+<document source="<src>/index.md">
+ <note>
+ <paragraph>
+ <emphasis>
+ hallo
+.
+
+Admonition with options:
+.
+::: {admonition} A **title**
+:class: other
+
+*hallo*
+:::
+.
+<document source="<src>/index.md">
+ <admonition classes="other">
+ <title>
+ A
+ <strong>
+ title
+ <paragraph>
+ <emphasis>
+ hallo
+.
diff --git a/tests/test_renderers/fixtures/definition_lists.md b/tests/test_renderers/fixtures/definition_lists.md
new file mode 100644
index 0000000..c064fa2
--- /dev/null
+++ b/tests/test_renderers/fixtures/definition_lists.md
@@ -0,0 +1,56 @@
+Simple:
+.
+Term **1**
+
+: Definition *1*
+
+ second paragraph
+
+Term 2
+ ~ Definition 2a
+ ~ Definition 2b
+
+Term 3
+ : code block
+
+ : > quote
+
+ : other
+.
+<document source="<src>/index.md">
+ <definition_list classes="simple myst">
+ <definition_list_item>
+ <term>
+ Term
+ <strong>
+ 1
+ <definition>
+ <paragraph>
+ Definition
+ <emphasis>
+ 1
+ <paragraph>
+ second paragraph
+ <definition_list_item>
+ <term>
+ Term 2
+ <definition>
+ <paragraph>
+ Definition 2a
+ <definition>
+ <paragraph>
+ Definition 2b
+ <definition_list_item>
+ <term>
+ Term 3
+ <definition>
+ <literal_block language="none" xml:space="preserve">
+ code block
+ <definition>
+ <block_quote>
+ <paragraph>
+ quote
+ <definition>
+ <paragraph>
+ other
+.
diff --git a/tests/test_renderers/fixtures/directive_options.md b/tests/test_renderers/fixtures/directive_options.md
new file mode 100644
index 0000000..b9ae64a
--- /dev/null
+++ b/tests/test_renderers/fixtures/directive_options.md
@@ -0,0 +1,159 @@
+Test Directive 1:
+.
+```{restructuredtext-test-directive}
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content: None
+.
+
+Test Directive 2:
+.
+```{restructuredtext-test-directive}
+foo
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content:
+ <literal_block xml:space="preserve">
+ foo
+.
+
+Test Directive 3:
+.
+```{restructuredtext-test-directive} foo
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=['foo'], options={}, content: None
+.
+
+Test Directive 4:
+.
+```{restructuredtext-test-directive} foo
+bar
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=['foo'], options={}, content:
+ <literal_block xml:space="preserve">
+ bar
+.
+
+Test Directive 5:
+.
+```{restructuredtext-test-directive} foo bar
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=['foo bar'], options={}, content: None
+.
+
+Test Directive 6:
+.
+```{restructuredtext-test-directive} foo bar
+baz
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=['foo bar'], options={}, content:
+ <literal_block xml:space="preserve">
+ baz
+.
+
+Test Directive 7:
+.
+```{restructuredtext-test-directive}
+
+foo
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content:
+ <literal_block xml:space="preserve">
+ foo
+.
+
+Test Directive Options 1:
+.
+```{restructuredtext-test-directive}
+---
+option1: a
+option2: b
+---
+foo
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=[], options={'option1': 'a', 'option2': 'b'}, content:
+ <literal_block xml:space="preserve">
+ foo
+.
+
+Test Directive Options 2:
+.
+```{restructuredtext-test-directive}
+:option1: a
+:option2: b
+foo
+```
+.
+<document source="<src>/index.md">
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=[], options={'option1': 'a', 'option2': 'b'}, content:
+ <literal_block xml:space="preserve">
+ foo
+.
+
+Test Directive Options Error:
+.
+```{restructuredtext-test-directive}
+:option1
+:option2: b
+foo
+```
+.
+<document source="<src>/index.md">
+ <system_message level="3" line="1" source="<src>/index.md" type="ERROR">
+ <paragraph>
+ Directive 'restructuredtext-test-directive': Invalid options YAML: mapping values are not allowed here
+ in "<unicode string>", line 2, column 8:
+ option2: b
+ ^
+ <literal_block xml:space="preserve">
+ :option1
+ :option2: b
+ foo
+.
+
+Unknown Directive:
+.
+```{unknown}
+```
+.
+<document source="<src>/index.md">
+ <system_message level="3" line="1" source="<src>/index.md" type="ERROR">
+ <paragraph>
+ Unknown directive type "unknown".
+ <system_message level="1" line="1" source="<src>/index.md" type="INFO">
+ <paragraph>
+ No directive entry for "unknown" in module "docutils.parsers.rst.languages.en".
+ Trying "unknown" as canonical directive name.
+.
diff --git a/tests/test_renderers/fixtures/directive_parsing.txt b/tests/test_renderers/fixtures/directive_parsing.txt
new file mode 100644
index 0000000..35878bc
--- /dev/null
+++ b/tests/test_renderers/fixtures/directive_parsing.txt
@@ -0,0 +1,141 @@
+note: content in first line only
+.
+```{note} a
+```
+.
+arguments: []
+body:
+- a
+content_offset: 0
+options: {}
+.
+
+note: content in body only
+.
+```{note}
+a
+```
+.
+arguments: []
+body:
+- a
+content_offset: 0
+options: {}
+.
+
+note: content after option
+.
+```{note}
+:class: name
+a
+```
+.
+arguments: []
+body:
+- a
+content_offset: 1
+options:
+ class:
+ - name
+.
+
+note: content after option with new line
+.
+```{note}
+:class: name
+
+a
+```
+.
+arguments: []
+body:
+- a
+content_offset: 2
+options:
+ class:
+ - name
+.
+
+note: content after yaml option
+.
+```{note}
+---
+class: name
+---
+a
+```
+.
+arguments: []
+body:
+- a
+content_offset: 3
+options:
+ class:
+ - name
+.
+
+note: content in first line and body
+.
+```{note} first line
+:class: tip
+
+body line
+```
+.
+arguments: []
+body:
+- first line
+- ''
+- body line
+content_offset: 1
+options:
+ class:
+ - tip
+.
+
+admonition: no options, no new line
+.
+```{admonition} first line
+body line
+```
+.
+arguments:
+- first line
+body:
+- body line
+content_offset: 0
+options: {}
+.
+
+admonition: no options, new line
+.
+```{admonition} first line
+
+body line
+```
+.
+arguments:
+- first line
+body:
+- body line
+content_offset: 1
+options: {}
+.
+
+admonition: with options
+.
+```{admonition} first line
+:class: tip
+
+body line
+```
+.
+arguments:
+- first line
+body:
+- body line
+content_offset: 2
+options:
+ class:
+ - tip
+.
diff --git a/tests/test_renderers/fixtures/docutil_directives.md b/tests/test_renderers/fixtures/docutil_directives.md
new file mode 100644
index 0000000..e15bf2e
--- /dev/null
+++ b/tests/test_renderers/fixtures/docutil_directives.md
@@ -0,0 +1,436 @@
+--------------------------------
+[attention] (`docutils.parsers.rst.directives.admonitions.Attention`):
+.
+```{attention}
+
+a
+```
+.
+<document source="notset">
+ <attention>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[caution] (`docutils.parsers.rst.directives.admonitions.Caution`):
+.
+```{caution}
+
+a
+```
+.
+<document source="notset">
+ <caution>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[danger] (`docutils.parsers.rst.directives.admonitions.Danger`):
+.
+```{danger}
+
+a
+```
+.
+<document source="notset">
+ <danger>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[error] (`docutils.parsers.rst.directives.admonitions.Error`):
+.
+```{error}
+
+a
+```
+.
+<document source="notset">
+ <error>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[important] (`docutils.parsers.rst.directives.admonitions.Important`):
+.
+```{important}
+
+a
+```
+.
+<document source="notset">
+ <important>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[note] (`docutils.parsers.rst.directives.admonitions.Note`):
+.
+```{note}
+
+a
+```
+.
+<document source="notset">
+ <note>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[tip] (`docutils.parsers.rst.directives.admonitions.Tip`):
+.
+```{tip}
+
+a
+```
+.
+<document source="notset">
+ <tip>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[hint] (`docutils.parsers.rst.directives.admonitions.Hint`):
+.
+```{hint}
+
+a
+```
+.
+<document source="notset">
+ <hint>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[warning] (`docutils.parsers.rst.directives.admonitions.Warning`):
+.
+```{warning}
+
+a
+```
+.
+<document source="notset">
+ <warning>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[admonition] (`docutils.parsers.rst.directives.admonitions.Admonition`):
+.
+```{admonition} myclass
+
+a
+```
+.
+<document source="notset">
+ <admonition classes="admonition-myclass">
+ <title>
+ myclass
+ <paragraph>
+ a
+.
+
+--------------------------------
+[sidebar] (`docutils.parsers.rst.directives.body.Sidebar`):
+.
+```{sidebar} sidebar title
+
+a
+```
+.
+<document source="notset">
+ <sidebar>
+ <title>
+ sidebar title
+ <paragraph>
+ a
+.
+
+--------------------------------
+[topic] (`docutils.parsers.rst.directives.body.Topic`):
+.
+```{topic} Topic Title
+
+a
+```
+.
+<document source="notset">
+ <topic>
+ <title>
+ Topic Title
+ <paragraph>
+ a
+.
+
+--------------------------------
+[line-block] (`docutils.parsers.rst.directives.body.LineBlock`) SKIP: MockingError: MockState has not yet implemented attribute 'nest_line_block_lines'
+.
+```{line-block}
+
+
+```
+.
+<document source="notset">
+.
+
+--------------------------------
+[parsed-literal] (`docutils.parsers.rst.directives.body.ParsedLiteral`):
+.
+```{parsed-literal}
+
+a
+```
+.
+<document source="notset">
+ <literal_block xml:space="preserve">
+ a
+.
+
+--------------------------------
+[rubric] (`docutils.parsers.rst.directives.body.Rubric`):
+.
+```{rubric} Rubric Title
+```
+.
+<document source="notset">
+ <rubric>
+ Rubric Title
+.
+
+--------------------------------
+[epigraph] (`docutils.parsers.rst.directives.body.Epigraph`):
+.
+```{epigraph}
+
+a
+
+-- attribution
+```
+.
+<document source="notset">
+ <block_quote classes="epigraph">
+ <paragraph>
+ a
+ <attribution>
+ attribution
+.
+
+--------------------------------
+[highlights] (`docutils.parsers.rst.directives.body.Highlights`):
+.
+```{highlights}
+
+a
+
+-- attribution
+```
+.
+<document source="notset">
+ <block_quote classes="highlights">
+ <paragraph>
+ a
+ <attribution>
+ attribution
+.
+
+--------------------------------
+[pull-quote] (`docutils.parsers.rst.directives.body.PullQuote`):
+.
+```{pull-quote}
+
+a
+
+-- attribution
+```
+.
+<document source="notset">
+ <block_quote classes="pull-quote">
+ <paragraph>
+ a
+ <attribution>
+ attribution
+.
+
+--------------------------------
+[compound] (`docutils.parsers.rst.directives.body.Compound`):
+.
+```{compound}
+
+a
+```
+.
+<document source="notset">
+ <compound>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[container] (`docutils.parsers.rst.directives.body.Container`):
+.
+```{container}
+
+a
+```
+.
+<document source="notset">
+ <container>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[image] (`docutils.parsers.rst.directives.images.Image`):
+.
+```{image} path/to/image
+:alt: abc
+:name: name
+```
+.
+<document source="notset">
+ <image alt="abc" ids="name" names="name" uri="path/to/image">
+.
+
+--------------------------------
+[raw] (`docutils.parsers.rst.directives.misc.Raw`):
+.
+```{raw} raw
+
+a
+```
+.
+<document source="notset">
+ <raw format="raw" xml:space="preserve">
+ a
+.
+
+--------------------------------
+[class] (`docutils.parsers.rst.directives.misc.Class`):
+.
+```{class} myclass
+
+a
+```
+.
+<document source="notset">
+ <paragraph classes="myclass">
+ a
+.
+
+--------------------------------
+[role] (`docutils.parsers.rst.directives.misc.Role`) + raw (`docutils.parsers.rst.roles.raw_role`):
+.
+```{role} raw-latex(raw)
+:format: latex
+```
+
+{raw-latex}`\tag{content}`
+.
+<document source="notset">
+ <paragraph>
+ <raw classes="raw-latex" format="latex" xml:space="preserve">
+ \tag{content}
+.
+
+--------------------------------
+[title] (`docutils.parsers.rst.directives.misc.Title`):
+.
+```{title} title
+```
+.
+<document source="notset" title="title">
+.
+
+--------------------------------
+[restructuredtext-test-directive] (`docutils.parsers.rst.directives.misc.TestDirective`):
+.
+```{restructuredtext-test-directive}
+```
+.
+<document source="notset">
+ <system_message level="1" line="1" source="notset" type="INFO">
+ <paragraph>
+ Directive processed. Type="restructuredtext-test-directive", arguments=[], options={}, content: None
+.
+
+--------------------------------
+[contents] (`docutils.parsers.rst.directives.parts.Contents`):
+.
+```{contents} Contents
+```
+.
+<document source="notset">
+ <topic classes="contents" ids="contents" names="contents">
+ <title>
+ Contents
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.parts.Contents
+ .details:
+.
+
+--------------------------------
+[sectnum] (`docutils.parsers.rst.directives.parts.Sectnum`):
+.
+```{sectnum}
+```
+.
+<document source="notset">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.parts.SectNum
+ .details:
+.
+
+--------------------------------
+[header] (`docutils.parsers.rst.directives.parts.Header`):
+.
+```{header}
+
+a
+```
+.
+<document source="notset">
+ <decoration>
+ <header>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[footer] (`docutils.parsers.rst.directives.parts.Footer`):
+.
+```{footer}
+
+a
+```
+.
+<document source="notset">
+ <decoration>
+ <footer>
+ <paragraph>
+ a
+.
+
+--------------------------------
+[target-notes] (`docutils.parsers.rst.directives.references.TargetNotes`):
+.
+```{target-notes}
+```
+.
+<document source="notset">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.references.TargetNotes
+ .details:
+.
diff --git a/tests/test_renderers/fixtures/docutil_roles.md b/tests/test_renderers/fixtures/docutil_roles.md
new file mode 100644
index 0000000..51fa48d
--- /dev/null
+++ b/tests/test_renderers/fixtures/docutil_roles.md
@@ -0,0 +1,131 @@
+--------------------------------
+[abbreviation] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{abbreviation}`a`
+.
+<document source="notset">
+ <paragraph>
+ <abbreviation>
+ a
+.
+
+--------------------------------
+[acronym] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{acronym}`a`
+.
+<document source="notset">
+ <paragraph>
+ <acronym>
+ a
+.
+
+--------------------------------
+[emphasis] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{emphasis}`a`
+.
+<document source="notset">
+ <paragraph>
+ <emphasis>
+ a
+.
+
+--------------------------------
+[literal] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{literal}`a`
+.
+<document source="notset">
+ <paragraph>
+ <literal>
+ a
+.
+
+--------------------------------
+[strong] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{strong}`a`
+.
+<document source="notset">
+ <paragraph>
+ <strong>
+ a
+.
+
+--------------------------------
+[subscript] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{subscript}`a`
+.
+<document source="notset">
+ <paragraph>
+ <subscript>
+ a
+.
+
+--------------------------------
+[superscript] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{superscript}`a`
+.
+<document source="notset">
+ <paragraph>
+ <superscript>
+ a
+.
+
+--------------------------------
+[title-reference] (`docutils.parsers.rst.roles.GenericRole`):
+.
+{title-reference}`t`
+.
+<document source="notset">
+ <paragraph>
+ <title_reference>
+ t
+.
+
+--------------------------------
+[pep-reference] (`docutils.parsers.rst.roles.pep_reference_role`):
+.
+{pep-reference}`0`
+.
+<document source="notset">
+ <paragraph>
+ <reference refuri="https://peps.python.org/pep-0000">
+ PEP 0
+.
+
+--------------------------------
+[rfc-reference] (`docutils.parsers.rst.roles.rfc_reference_role`):
+.
+{rfc-reference}`1`
+.
+<document source="notset">
+ <paragraph>
+ <reference refuri="https://tools.ietf.org/html/rfc1.html">
+ RFC 1
+.
+
+--------------------------------
+[code] (`docutils.parsers.rst.roles.code_role`):
+.
+{code}`a`
+.
+<document source="notset">
+ <paragraph>
+ <literal classes="code">
+ a
+.
+
+--------------------------------
+[math] (`docutils.parsers.rst.roles.math_role`):
+.
+{math}`a`
+.
+<document source="notset">
+ <paragraph>
+ <math>
+ a
+.
diff --git a/tests/test_renderers/fixtures/docutil_syntax_elements.md b/tests/test_renderers/fixtures/docutil_syntax_elements.md
new file mode 100644
index 0000000..9b59f3a
--- /dev/null
+++ b/tests/test_renderers/fixtures/docutil_syntax_elements.md
@@ -0,0 +1,794 @@
+Raw
+.
+foo
+.
+<document source="notset">
+ <paragraph>
+ foo
+.
+
+Hard-break
+.
+foo\
+bar
+.
+<document source="notset">
+ <paragraph>
+ foo
+ <raw format="html" xml:space="preserve">
+ <br />
+ <raw format="latex" xml:space="preserve">
+ \\
+ bar
+.
+
+Strong:
+.
+**foo**
+.
+<document source="notset">
+ <paragraph>
+ <strong>
+ foo
+.
+
+Emphasis
+.
+*foo*
+.
+<document source="notset">
+ <paragraph>
+ <emphasis>
+ foo
+.
+
+Escaped Emphasis:
+.
+\*foo*
+.
+<document source="notset">
+ <paragraph>
+ *foo*
+.
+
+Mixed Inline
+.
+a *b* **c** `abc` \\*
+.
+<document source="notset">
+ <paragraph>
+ a
+ <emphasis>
+ b
+
+ <strong>
+ c
+
+ <literal>
+ abc
+ \*
+.
+
+Inline Code:
+.
+`foo`
+.
+<document source="notset">
+ <paragraph>
+ <literal>
+ foo
+.
+
+Heading:
+.
+# foo
+.
+<document source="notset">
+ <section ids="foo" names="foo">
+ <title>
+ foo
+.
+
+Heading Levels:
+.
+# a
+## b
+### c
+# d
+.
+<document source="notset">
+ <section ids="a" names="a">
+ <title>
+ a
+ <section ids="b" names="b">
+ <title>
+ b
+ <section ids="c" names="c">
+ <title>
+ c
+ <section ids="d" names="d">
+ <title>
+ d
+.
+
+Block Code:
+.
+ foo
+.
+<document source="notset">
+ <literal_block classes="code" xml:space="preserve">
+ foo
+.
+
+Fenced Code:
+.
+```sh
+foo
+```
+.
+<document source="notset">
+ <literal_block classes="code sh" xml:space="preserve">
+ foo
+.
+
+Fenced Code no language:
+.
+```
+foo
+```
+.
+<document source="notset">
+ <literal_block classes="code" xml:space="preserve">
+ foo
+.
+
+Fenced Code no language with trailing whitespace:
+.
+```
+foo
+```
+.
+<document source="notset">
+ <literal_block classes="code" xml:space="preserve">
+ foo
+.
+
+Image empty:
+.
+![]()
+.
+<document source="notset">
+ <paragraph>
+ <image alt="" uri="">
+.
+
+Image with alt and title:
+.
+![alt](src "title")
+.
+<document source="notset">
+ <paragraph>
+ <image alt="alt" title="title" uri="src">
+.
+
+Image with escapable html:
+.
+![alt](http://www.google<>.com)
+.
+<document source="notset">
+ <paragraph>
+ <image alt="alt" uri="http://www.google%3C%3E.com">
+.
+
+Block Quote:
+.
+> *foo*
+.
+<document source="notset">
+ <block_quote>
+ <paragraph>
+ <emphasis>
+ foo
+.
+
+Bullet List:
+.
+- *foo*
+* bar
+.
+<document source="notset">
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ <emphasis>
+ foo
+ <bullet_list bullet="*">
+ <list_item>
+ <paragraph>
+ bar
+.
+
+Nested Bullets
+.
+- a
+ - b
+ - c
+ - d
+.
+<document source="notset">
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ a
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ b
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ c
+ <list_item>
+ <paragraph>
+ d
+.
+
+Enumerated List:
+.
+1. *foo*
+
+1) bar
+
+para
+
+10. starting
+11. enumerator
+.
+<document source="notset">
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ <emphasis>
+ foo
+ <enumerated_list enumtype="arabic" prefix="" suffix=")">
+ <list_item>
+ <paragraph>
+ bar
+ <paragraph>
+ para
+ <enumerated_list enumtype="arabic" prefix="" start="10" suffix=".">
+ <list_item>
+ <paragraph>
+ starting
+ <list_item>
+ <paragraph>
+ enumerator
+.
+
+Nested Enumrated List:
+.
+1. a
+2. b
+ 1. c
+.
+<document source="notset">
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ a
+ <list_item>
+ <paragraph>
+ b
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ c
+.
+
+Sphinx Role containing backtick:
+.
+{code}``a=1{`}``
+.
+<document source="notset">
+ <paragraph>
+ <literal classes="code">
+ a=1{`}
+.
+
+Target:
+.
+(target)=
+.
+<document source="notset">
+ <target ids="target" names="target">
+.
+
+Target with whitespace:
+.
+(target with space)=
+.
+<document source="notset">
+ <target ids="target-with-space" names="target\ with\ space">
+.
+
+Referencing:
+.
+(target)=
+
+Title
+=====
+
+[alt1](target)
+
+[](target2)
+
+[alt2](https://www.google.com)
+
+[alt3](#target3)
+.
+<document source="notset">
+ <target ids="target" names="target">
+ <section ids="title" names="title">
+ <title>
+ Title
+ <paragraph>
+ <reference refname="target">
+ alt1
+ <paragraph>
+ <reference refname="target2">
+ <paragraph>
+ <reference refuri="https://www.google.com">
+ alt2
+ <paragraph>
+ <reference refname="#target3">
+ alt3
+.
+
+Comments:
+.
+line 1
+% a comment
+line 2
+.
+<document source="notset">
+ <paragraph>
+ line 1
+ <comment xml:space="preserve">
+ a comment
+ <paragraph>
+ line 2
+.
+
+Block Break:
+.
++++ string
+.
+<document source="notset">
+ <comment classes="block_break" xml:space="preserve">
+ string
+.
+
+Link Reference:
+.
+[name][key]
+
+[key]: https://www.google.com "a title"
+.
+<document source="notset">
+ <paragraph>
+ <reference refuri="https://www.google.com" title="a title">
+ name
+.
+
+Link Reference short version:
+.
+[name]
+
+[name]: https://www.google.com "a title"
+.
+<document source="notset">
+ <paragraph>
+ <reference refuri="https://www.google.com" title="a title">
+ name
+.
+
+Block Quotes:
+.
+```{epigraph}
+a b*c*
+
+-- a**b**
+```
+.
+<document source="notset">
+ <block_quote classes="epigraph">
+ <paragraph>
+ a b
+ <emphasis>
+ c
+ <attribution>
+ a
+ <strong>
+ b
+.
+
+Link Definition in directive:
+.
+```{note}
+[a]
+```
+
+[a]: link
+.
+<document source="notset">
+ <note>
+ <paragraph>
+ <reference refname="link">
+ a
+.
+
+Link Definition in nested directives:
+.
+```{note}
+[ref1]: link
+```
+
+```{note}
+[ref1]
+[ref2]
+```
+
+```{note}
+[ref2]: link
+```
+.
+<document source="notset">
+ <note>
+ <note>
+ <paragraph>
+ <reference refname="link">
+ ref1
+
+ [ref2]
+ <note>
+.
+
+Footnotes:
+.
+[^a]
+
+[^a]: footnote*text*
+.
+<document source="notset">
+ <paragraph>
+ <footnote_reference auto="1" ids="id1" refname="a">
+ <transition classes="footnotes">
+ <footnote auto="1" ids="a" names="a">
+ <paragraph>
+ footnote
+ <emphasis>
+ text
+.
+
+Footnotes nested blocks:
+.
+[^a]
+
+[^a]: footnote*text*
+
+ abc
+xyz
+
+ > a
+
+ - b
+
+ c
+
+finish
+.
+<document source="notset">
+ <paragraph>
+ <footnote_reference auto="1" ids="id1" refname="a">
+ <paragraph>
+ finish
+ <transition classes="footnotes">
+ <footnote auto="1" ids="a" names="a">
+ <paragraph>
+ footnote
+ <emphasis>
+ text
+ <paragraph>
+ abc
+
+ xyz
+ <block_quote>
+ <paragraph>
+ a
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ b
+ <paragraph>
+ c
+.
+
+Front Matter:
+.
+---
+a: 1
+b: foo
+c:
+ d: 2
+---
+.
+<document source="notset">
+ <field_list>
+ <field>
+ <field_name>
+ a
+ <field_body>
+ <paragraph>
+ <literal>
+ 1
+ <field>
+ <field_name>
+ b
+ <field_body>
+ <paragraph>
+ <literal>
+ foo
+ <field>
+ <field_name>
+ c
+ <field_body>
+ <paragraph>
+ <literal>
+ {"d": 2}
+.
+
+Front Matter Biblio:
+.
+---
+author: Chris Sewell
+authors: Chris Sewell, Chris Hodgraf
+organization: EPFL
+address: |
+ 1 Cedar Park Close
+ Thundersley
+ Essex
+contact: <https://example.com>
+version: 1.0
+revision: 1.1
+status: good
+date: 2/12/1985
+copyright: MIT
+dedication: |
+ To my *homies*
+abstract:
+ Something something **dark** side
+other: Something else
+---
+.
+<document source="notset">
+ <field_list>
+ <field>
+ <field_name>
+ author
+ <field_body>
+ <paragraph>
+ Chris Sewell
+ <field>
+ <field_name>
+ authors
+ <field_body>
+ <paragraph>
+ Chris Sewell, Chris Hodgraf
+ <field>
+ <field_name>
+ organization
+ <field_body>
+ <paragraph>
+ EPFL
+ <field>
+ <field_name>
+ address
+ <field_body>
+ <paragraph>
+ 1 Cedar Park Close
+
+ Thundersley
+
+ Essex
+
+ <field>
+ <field_name>
+ contact
+ <field_body>
+ <paragraph>
+ <reference refuri="https://example.com">
+ https://example.com
+ <field>
+ <field_name>
+ version
+ <field_body>
+ <paragraph>
+ 1.0
+ <field>
+ <field_name>
+ revision
+ <field_body>
+ <paragraph>
+ 1.1
+ <field>
+ <field_name>
+ status
+ <field_body>
+ <paragraph>
+ good
+ <field>
+ <field_name>
+ date
+ <field_body>
+ <paragraph>
+ 2/12/1985
+ <field>
+ <field_name>
+ copyright
+ <field_body>
+ <paragraph>
+ MIT
+ <field>
+ <field_name>
+ dedication
+ <field_body>
+ <paragraph>
+ To my
+ <emphasis>
+ homies
+
+ <field>
+ <field_name>
+ abstract
+ <field_body>
+ <paragraph>
+ Something something
+ <strong>
+ dark
+ side
+ <field>
+ <field_name>
+ other
+ <field_body>
+ <paragraph>
+ <literal>
+ Something else
+.
+
+Front Matter Bad Yaml:
+.
+---
+a: {
+---
+.
+<document source="notset">
+ <system_message level="2" line="1" source="notset" type="WARNING">
+ <paragraph>
+ Malformed YAML [myst.topmatter]
+.
+
+Front Matter HTML Meta
+.
+---
+myst:
+ html_meta:
+ keywords: Sphinx, documentation, builder
+ description lang=en: An amusing story
+ description lang=fr: Un histoire amusant
+ http-equiv=Content-Type: text/html; charset=ISO-8859-1
+---
+.
+<document source="notset">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="Sphinx, documentation, builder" name="keywords">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="An amusing story" lang="en" name="description">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="Un histoire amusant" lang="fr" name="description">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type">
+.
+
+Full Test:
+.
+---
+a: 1
+---
+
+(target)=
+# header 1
+## sub header 1
+
+a *b* **c** `abc`
+
+## sub header 2
+
+x y [a](http://www.xyz.com) z
+
+---
+
+# header 2
+
+```::python {a=1}
+a = 1
+```
+
+[](target)
+.
+<document source="notset">
+ <field_list>
+ <field>
+ <field_name>
+ a
+ <field_body>
+ <paragraph>
+ <literal>
+ 1
+ <target ids="target" names="target">
+ <section ids="header-1" names="header\ 1">
+ <title>
+ header 1
+ <section ids="sub-header-1" names="sub\ header\ 1">
+ <title>
+ sub header 1
+ <paragraph>
+ a
+ <emphasis>
+ b
+
+ <strong>
+ c
+
+ <literal>
+ abc
+ <section ids="sub-header-2" names="sub\ header\ 2">
+ <title>
+ sub header 2
+ <paragraph>
+ x y
+ <reference refuri="http://www.xyz.com">
+ a
+ z
+ <transition>
+ <section ids="header-2" names="header\ 2">
+ <title>
+ header 2
+ <literal_block classes="code ::python" xml:space="preserve">
+ a = 1
+ <paragraph>
+ <reference refname="target">
+.
diff --git a/tests/test_renderers/fixtures/docutil_syntax_extensions.txt b/tests/test_renderers/fixtures/docutil_syntax_extensions.txt
new file mode 100644
index 0000000..5efcb68
--- /dev/null
+++ b/tests/test_renderers/fixtures/docutil_syntax_extensions.txt
@@ -0,0 +1,139 @@
+[dollarmath] --myst-enable-extensions=dollarmath
+.
+$foo$
+
+a $foo
+bar$ b
+
+$$foo$$
+
+$$
+a = 1
+$$
+.
+<document source="<string>">
+ <paragraph>
+ <math>
+ foo
+ <paragraph>
+ a
+ <math>
+ foo
+ bar
+ b
+ <math_block nowrap="False" number="True" xml:space="preserve">
+ foo
+ <math_block nowrap="False" number="True" xml:space="preserve">
+
+ a = 1
+.
+
+[amsmath] --myst-enable-extensions=amsmath
+.
+\begin{equation} a \end{equation}
+
+\begin{equation}
+a
+\end{equation}
+
+\begin{equation*}
+a
+\end{equation*}
+.
+<document source="<string>">
+ <math_block classes="amsmath" nowrap="True" numbered="True" xml:space="preserve">
+ \begin{equation} a \end{equation}
+ <math_block classes="amsmath" nowrap="True" numbered="True" xml:space="preserve">
+ \begin{equation}
+ a
+ \end{equation}
+ <math_block classes="amsmath" nowrap="True" xml:space="preserve">
+ \begin{equation*}
+ a
+ \end{equation*}
+.
+
+[deflist] --myst-enable-extensions=deflist
+.
+term
+: definition
+.
+<document source="<string>">
+ <definition_list classes="simple myst">
+ <definition_list_item>
+ <term>
+ term
+ <definition>
+ <paragraph>
+ definition
+.
+
+[fieldlist] --myst-enable-extensions=fieldlist
+.
+:name: value
+.
+<document source="<string>">
+ <docinfo>
+ <field classes="name">
+ <field_name>
+ name
+ <field_body>
+ <paragraph>
+ value
+.
+
+[colon_fence] --myst-enable-extensions=colon_fence
+.
+:::{note}
+content
+:::
+.
+<document source="<string>">
+ <note>
+ <paragraph>
+ content
+.
+
+[replacements] --myst-enable-extensions=replacements
+.
+(c) (C) (r) (R) (tm) (TM) (p) (P) +- ...
+.
+<document source="<string>">
+ <paragraph>
+ © © ® ® ™ ™ § § ± …
+.
+
+[strikethrough] --myst-enable-extensions=strikethrough
+.
+~~foo~~
+.
+<document source="<string>">
+ <paragraph>
+ <system_message level="2" line="1" source="<string>" type="WARNING">
+ <paragraph>
+ Strikethrough is currently only supported in HTML output [myst.strikethrough]
+ <raw format="html" xml:space="preserve">
+ <s>
+ foo
+ <raw format="html" xml:space="preserve">
+ </s>
+.
+
+[tasklist] --myst-enable-extensions=tasklist
+.
+- [ ] foo
+- [x] bar
+.
+<document source="<string>">
+ <bullet_list bullet="-" classes="contains-task-list">
+ <list_item classes="task-list-item">
+ <paragraph>
+ <raw format="html" xml:space="preserve">
+ <input class="task-list-item-checkbox" disabled="disabled" type="checkbox">
+ foo
+ <list_item classes="task-list-item">
+ <paragraph>
+ <raw format="html" xml:space="preserve">
+ <input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox">
+ bar
+.
diff --git a/tests/test_renderers/fixtures/dollarmath.md b/tests/test_renderers/fixtures/dollarmath.md
new file mode 100644
index 0000000..e3e5fd2
--- /dev/null
+++ b/tests/test_renderers/fixtures/dollarmath.md
@@ -0,0 +1,75 @@
+Inline Math:
+.
+$foo$
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <math>
+ foo
+.
+
+Inline Math, multi-line:
+.
+a $foo
+bar$ b
+.
+<document source="<src>/index.md">
+ <paragraph>
+ a
+ <math>
+ foo
+ bar
+ b
+.
+
+Inline Math, multi-line with line break (invalid):
+.
+a $foo
+
+bar$ b
+.
+<document source="<src>/index.md">
+ <paragraph>
+ a $foo
+ <paragraph>
+ bar$ b
+.
+
+Math Block:
+.
+$$foo$$
+.
+<document source="<src>/index.md">
+ <math_block nowrap="False" number="True" xml:space="preserve">
+ foo
+.
+
+Math Block With Equation Label:
+.
+$$foo$$ (abc)
+.
+<document source="<src>/index.md">
+ <target ids="equation-abc">
+ <math_block docname="index" label="abc" nowrap="False" number="1" xml:space="preserve">
+ foo
+.
+
+Math Block multiple:
+.
+$$
+a = 1
+$$
+
+$$
+b = 2
+$$ (a)
+.
+<document source="<src>/index.md">
+ <math_block nowrap="False" number="True" xml:space="preserve">
+
+ a = 1
+ <target ids="equation-a">
+ <math_block docname="index" label="a" nowrap="False" number="1" xml:space="preserve">
+
+ b = 2
+.
diff --git a/tests/test_renderers/fixtures/eval_rst.md b/tests/test_renderers/fixtures/eval_rst.md
new file mode 100644
index 0000000..9f21bd1
--- /dev/null
+++ b/tests/test_renderers/fixtures/eval_rst.md
@@ -0,0 +1,23 @@
+eval-rst link
+.
+```{eval-rst}
+`MyST Parser <https://myst-parser.readthedocs.io/>`_
+```
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <reference name="MyST Parser" refuri="https://myst-parser.readthedocs.io/">
+ MyST Parser
+ <target ids="myst-parser" names="myst\ parser" refuri="https://myst-parser.readthedocs.io/">
+.
+
+eval-rst bold
+.
+```{eval-rst}
+**bold**
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <strong>
+ bold
+.
diff --git a/tests/test_renderers/fixtures/mock_include.md b/tests/test_renderers/fixtures/mock_include.md
new file mode 100644
index 0000000..32d779e
--- /dev/null
+++ b/tests/test_renderers/fixtures/mock_include.md
@@ -0,0 +1,62 @@
+Basic Include:
+.
+```{include} other.md
+```
+.
+<document source="tmpdir/test.md">
+ <paragraph>
+ a
+
+ b
+
+ c
+.
+
+Include with Front Matter (should be ignored):
+.
+```{include} fmatter.md
+```
+.
+<document source="tmpdir/test.md">
+ <paragraph>
+ b
+.
+
+Include Literal:
+.
+```{include} other.md
+:literal: True
+```
+.
+<document source="tmpdir/test.md">
+ <literal_block source="tmpdir/other.md" xml:space="preserve">
+ a
+ b
+ c
+.
+
+Include Literal, line range:
+.
+```{include} other.md
+:literal: True
+:start-line: 1
+:end-line: 2
+```
+.
+<document source="tmpdir/test.md">
+ <literal_block source="tmpdir/other.md" xml:space="preserve">
+ b
+.
+
+Include code:
+.
+```{include} other.md
+:code: md
+```
+.
+<document source="tmpdir/test.md">
+ <literal_block classes="code md" source="tmpdir/other.md" xml:space="preserve">
+ a
+ b
+ c
+.
diff --git a/tests/test_renderers/fixtures/mock_include_errors.md b/tests/test_renderers/fixtures/mock_include_errors.md
new file mode 100644
index 0000000..1e5c2e9
--- /dev/null
+++ b/tests/test_renderers/fixtures/mock_include_errors.md
@@ -0,0 +1,24 @@
+Missing path:
+.
+```{include}
+```
+.
+tmpdir/test.md:1: (ERROR/3) Directive 'include': 1 argument(s) required, 0 supplied
+.
+
+Non-existent path:
+.
+```{include} other.md
+```
+.
+tmpdir/test.md:1: (SEVERE/4) Directive "include": error reading file: tmpdir/other.md
+[Errno 2] No such file or directory: 'tmpdir/other.md'.
+.
+
+Error in include file:
+.
+```{include} bad.md
+```
+.
+tmpdir/bad.md:2: (ERROR/3) Unknown interpreted text role "a".
+.
diff --git a/tests/test_renderers/fixtures/myst-config.txt b/tests/test_renderers/fixtures/myst-config.txt
new file mode 100644
index 0000000..668895a
--- /dev/null
+++ b/tests/test_renderers/fixtures/myst-config.txt
@@ -0,0 +1,176 @@
+[title-to-header] --myst-title-to-header="yes"
+.
+---
+title: "The title *nested syntax*"
+---
+
+# Other header
+.
+<document source="<string>">
+ <docinfo>
+ <field classes="title">
+ <field_name>
+ title
+ <field_body>
+ <paragraph>
+ <literal>
+ The title *nested syntax*
+ <section ids="the-title-nested-syntax" names="the\ title\ nested\ syntax">
+ <title>
+ The title
+ <emphasis>
+ nested syntax
+ <section ids="other-header" names="other\ header">
+ <title>
+ Other header
+.
+
+[linkify] --myst-enable-extensions=linkify
+.
+www.example.com
+.
+<document source="<string>">
+ <paragraph>
+ <reference refuri="http://www.example.com">
+ www.example.com
+.
+
+[gfm-strikethrough] --myst-gfm-only="yes"
+.
+~~strike~~
+.
+<document source="<string>">
+ <paragraph>
+ <system_message level="2" line="1" source="<string>" type="WARNING">
+ <paragraph>
+ Strikethrough is currently only supported in HTML output [myst.strikethrough]
+ <raw format="html" xml:space="preserve">
+ <s>
+ strike
+ <raw format="html" xml:space="preserve">
+ </s>
+
+<string>:1: (WARNING/2) Strikethrough is currently only supported in HTML output [myst.strikethrough]
+.
+
+[gfm-disallowed-html] --myst-gfm-only="yes"
+.
+<strong> <title> <style> <em>
+
+<blockquote>
+ <xmp> is disallowed. <XMP> is also disallowed.
+</blockquote>
+.
+<document source="<string>">
+ <paragraph>
+ <raw format="html" xml:space="preserve">
+ <strong>
+
+ <raw format="html" xml:space="preserve">
+ &lt;title>
+
+ <raw format="html" xml:space="preserve">
+ &lt;style>
+
+ <raw format="html" xml:space="preserve">
+ <em>
+ <raw format="html" xml:space="preserve">
+ <blockquote>
+ &lt;xmp> is disallowed. &lt;XMP> is also disallowed.
+ </blockquote>
+.
+
+[gfm-autolink] --myst-gfm-only="yes"
+.
+www.commonmark.org
+
+Visit www.commonmark.org/help for more information.
+
+www.google.com/search?q=Markup+(business)
+
+www.google.com/search?q=Markup+(business)))
+
+(www.google.com/search?q=Markup+(business))
+
+(www.google.com/search?q=Markup+(business)
+
+www.google.com/search?q=(business))+ok
+
+www.google.com/search?q=commonmark&hl=en
+
+www.google.com/search?q=commonmark&hl;
+
+www.commonmark.org/he<lp
+.
+<document source="<string>">
+ <paragraph>
+ <reference refuri="http://www.commonmark.org">
+ www.commonmark.org
+ <paragraph>
+ Visit
+ <reference refuri="http://www.commonmark.org/help">
+ www.commonmark.org/help
+ for more information.
+ <paragraph>
+ <reference refuri="http://www.google.com/search?q=Markup+(business)">
+ www.google.com/search?q=Markup+(business)
+ <paragraph>
+ <reference refuri="http://www.google.com/search?q=Markup+(business)">
+ www.google.com/search?q=Markup+(business)
+ ))
+ <paragraph>
+ (
+ <reference refuri="http://www.google.com/search?q=Markup+(business)">
+ www.google.com/search?q=Markup+(business)
+ )
+ <paragraph>
+ (
+ <reference refuri="http://www.google.com/search?q=Markup+(business)">
+ www.google.com/search?q=Markup+(business)
+ <paragraph>
+ <reference refuri="http://www.google.com/search?q=(business)">
+ www.google.com/search?q=(business)
+ )+ok
+ <paragraph>
+ <reference refuri="http://www.google.com/search?q=commonmark&amp;hl=en">
+ www.google.com/search?q=commonmark&hl=en
+ <paragraph>
+ <reference refuri="http://www.google.com/search?q=commonmark&amp;hl">
+ www.google.com/search?q=commonmark&hl
+ ;
+ <paragraph>
+ <reference refuri="http://www.commonmark.org/he">
+ www.commonmark.org/he
+ <lp
+.
+
+[attrs_image] --myst-enable-extensions=attrs_image
+.
+![a](b){#id .a width="100%" align=center height=20px}{.b}
+.
+<document source="<string>">
+ <paragraph>
+ <image align="center" alt="a" classes="a b" height="20px" ids="id" names="id" uri="b" width="100%">
+.
+
+[attrs_image_warnings] --myst-enable-extensions=attrs_image
+.
+![a](b){width=1x height=2x align=other }
+.
+<document source="<string>">
+ <paragraph>
+ <system_message level="2" line="1" source="<string>" type="WARNING">
+ <paragraph>
+ Invalid width value for image: '1x' [myst.image]
+ <system_message level="2" line="1" source="<string>" type="WARNING">
+ <paragraph>
+ Invalid height value for image: '2x' [myst.image]
+ <system_message level="2" line="1" source="<string>" type="WARNING">
+ <paragraph>
+ Invalid align value for image: 'other' [myst.image]
+ <image alt="a" uri="b">
+
+<string>:1: (WARNING/2) Invalid width value for image: '1x' [myst.image]
+<string>:1: (WARNING/2) Invalid height value for image: '2x' [myst.image]
+<string>:1: (WARNING/2) Invalid align value for image: 'other' [myst.image]
+.
diff --git a/tests/test_renderers/fixtures/reporter_warnings.md b/tests/test_renderers/fixtures/reporter_warnings.md
new file mode 100644
index 0000000..e9998b9
--- /dev/null
+++ b/tests/test_renderers/fixtures/reporter_warnings.md
@@ -0,0 +1,180 @@
+Duplicate Reference definitions:
+.
+[a]: b
+[a]: c
+.
+<string>:2: (WARNING/2) Duplicate reference definition: A [myst.ref]
+.
+
+Missing Reference:
+.
+[a](b)
+.
+<string>:1: (ERROR/3) Unknown target name: "b".
+.
+
+Unknown role:
+.
+abc
+
+{xyz}`a`
+.
+<string>:3: (ERROR/3) Unknown interpreted text role "xyz".
+.
+
+Unknown directive:
+.
+
+```{xyz}
+```
+.
+<string>:2: (ERROR/3) Unknown directive type "xyz".
+.
+
+Bad Front Matter:
+.
+---
+a: {
+---
+.
+<string>:1: (WARNING/2) Malformed YAML [myst.topmatter]
+.
+
+Unknown Front Matter myst key:
+.
+---
+myst:
+ unknown: true
+---
+.
+<string>:1: (WARNING/2) Unknown field: unknown [myst.topmatter]
+.
+
+Invalid Front Matter myst key:
+.
+---
+myst:
+ title_to_header: 1
+ url_schemes: [1]
+ substitutions:
+ key: []
+---
+.
+<string>:1: (WARNING/2) 'title_to_header' must be of type <class 'bool'> (got 1 that is a <class 'int'>). [myst.topmatter]
+<string>:1: (WARNING/2) 'url_schemes[0]' must be of type <class 'str'> (got 1 that is a <class 'int'>). [myst.topmatter]
+<string>:1: (WARNING/2) 'substitutions['key']' must be of type (<class 'str'>, <class 'int'>, <class 'float'>) (got [] that is a <class 'list'>). [myst.topmatter]
+.
+
+Bad HTML Meta
+.
+---
+myst:
+ html_meta:
+ name noequals: value
+
+---
+.
+<string>:: (ERROR/3) Error parsing meta tag attribute "name noequals": no '=' in noequals.
+.
+
+Directive parsing error:
+.
+
+```{class}
+```
+.
+<string>:2: (ERROR/3) Directive 'class': 1 argument(s) required, 0 supplied
+.
+
+Directive run error:
+.
+
+```{date}
+x
+```
+.
+<string>:2: (ERROR/3) Invalid context: the "date" directive can only be used within a substitution definition.
+.
+
+Do not start headings at H1:
+.
+## title 1
+.
+<string>:1: (WARNING/2) Document headings start at H2, not H1 [myst.header]
+.
+
+Non-consecutive headings:
+.
+# title 1
+### title 3
+.
+<string>:2: (WARNING/2) Non-consecutive header level increase; H1 to H3 [myst.header]
+.
+
+multiple footnote definitions
+.
+[^a]
+
+[^a]: definition 1
+[^a]: definition 2
+.
+<string>:: (WARNING/2) Multiple footnote definitions found for label: 'a' [myst.footnote]
+.
+
+Warnings in eval-rst
+.
+some external
+
+lines
+
+```{eval-rst}
+some internal
+
+lines
+
+.. unknown:: some text
+
+:unknown:`a`
+```
+.
+<string>:10: (ERROR/3) Unknown directive type "unknown".
+
+.. unknown:: some text
+
+<string>:12: (ERROR/3) Unknown interpreted text role "unknown".
+.
+
+bad-option-value
+.
+```{note}
+:class: [1]
+```
+.
+<string>:1: (ERROR/3) Directive 'note': option "class" value not string (enclose with ""): [1]
+
+:class: [1]
+
+.
+
+header nested in admonition
+.
+```{note}
+# Header
+```
+.
+<string>:2: (WARNING/2) Disallowed nested header found, converting to rubric [myst.nested_header]
+.
+
+nested parse warning
+.
+Paragraph
+
+```{note}
+:class: abc
+:name: xyz
+
+{unknown}`a`
+```
+.
+<string>:7: (ERROR/3) Unknown interpreted text role "unknown".
+.
diff --git a/tests/test_renderers/fixtures/sphinx_directives.md b/tests/test_renderers/fixtures/sphinx_directives.md
new file mode 100644
index 0000000..2b92457
--- /dev/null
+++ b/tests/test_renderers/fixtures/sphinx_directives.md
@@ -0,0 +1,441 @@
+default-role (`sphinx.directives.DefaultRole`):
+.
+```{default-role}
+```
+.
+<document source="<src>/index.md">
+.
+
+default-domain (`sphinx.directives.DefaultDomain`):
+.
+```{default-domain} mydomain
+```
+.
+<document source="<src>/index.md">
+.
+
+object (`sphinx.directives.ObjectDescription`):
+.
+```{object} something
+```
+.
+<document source="<src>/index.md">
+ <index entries="">
+ <desc classes="object" desctype="object" domain="" noindex="False" objtype="object">
+ <desc_signature classes="sig sig-object">
+ <desc_name classes="sig-name descname" xml:space="preserve">
+ something
+ <desc_content>
+.
+
+highlight (`sphinx.directives.code.Highlight`):
+.
+```{highlight} something
+```
+.
+<document source="<src>/index.md">
+ <highlightlang force="False" lang="something" linenothreshold="9223372036854775807">
+.
+
+code-block (`sphinx.directives.code.CodeBlock`):
+.
+```{code-block}
+:dedent:
+
+ a=1
+```
+.
+<document source="<src>/index.md">
+ <literal_block force="False" highlight_args="{}" language="default" xml:space="preserve">
+ a=1
+.
+
+sourcecode (`sphinx.directives.code.CodeBlock`):
+.
+```{sourcecode}
+```
+.
+<document source="<src>/index.md">
+ <literal_block force="False" highlight_args="{}" language="default" xml:space="preserve">
+.
+
+SKIP: Tested in sphinx builds
+.
+```{literalinclude} /path/to/file
+```
+.
+<document source="notset">
+ <system_message level="2" line="1" source="notset" type="WARNING">
+ <paragraph>
+ Include file '/srcdir/path/to/file' not found or reading it failed
+.
+
+toctree (`sphinx.directives.other.TocTree`):
+.
+```{toctree}
+```
+.
+<document source="<src>/index.md">
+ <compound classes="toctree-wrapper">
+ <toctree caption="True" entries="" glob="False" hidden="False" includefiles="" includehidden="False" maxdepth="-1" numbered="0" parent="index" titlesonly="False">
+.
+
+sectionauthor (`sphinx.directives.other.Author`):
+.
+```{sectionauthor} bob geldof
+```
+.
+<document source="<src>/index.md">
+.
+
+moduleauthor (`sphinx.directives.other.Author`):
+.
+```{moduleauthor} ringo starr
+```
+.
+<document source="<src>/index.md">
+.
+
+codeauthor (`sphinx.directives.other.Author`):
+.
+```{codeauthor} paul mcartney
+```
+.
+<document source="<src>/index.md">
+.
+
+index (`sphinx.directives.other.Index`):
+.
+```{index} something
+```
+.
+<document source="<src>/index.md">
+ <index entries="('single',\ 'something',\ 'index-0',\ '',\ None)" inline="False">
+ <target ids="index-0">
+.
+
+seealso (`sphinx.directives.other.SeeAlso`):
+.
+```{seealso}
+
+a
+```
+.
+<document source="<src>/index.md">
+ <seealso>
+ <paragraph>
+ a
+.
+
+tabularcolumns (`sphinx.directives.other.TabularColumns`):
+.
+```{tabularcolumns} spec
+```
+.
+<document source="<src>/index.md">
+ <tabular_col_spec spec="spec">
+.
+
+centered (`sphinx.directives.other.Centered`):
+.
+```{centered} text
+```
+.
+<document source="<src>/index.md">
+ <centered>
+ text
+.
+
+acks (`sphinx.directives.other.Acks`):
+.
+```{acks}
+
+- name
+```
+.
+<document source="<src>/index.md">
+ <acks>
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ name
+.
+
+hlist (`sphinx.directives.other.HList`):
+.
+```{hlist}
+
+- item
+```
+.
+<document source="<src>/index.md">
+ <hlist ncolumns="2">
+ <hlistcol>
+ <bullet_list>
+ <list_item>
+ <paragraph>
+ item
+ <hlistcol>
+ <bullet_list>
+.
+
+only (`sphinx.directives.other.Only`):
+.
+```{only} expr
+```
+.
+<document source="<src>/index.md">
+ <only expr="expr">
+.
+
+SKIP: Tested in sphinx builds
+.
+```{include} path/to/include
+```
+.
+<document source="notset">
+.
+
+figure (`sphinx.directives.patches.Figure`):
+.
+```{figure} path/to/figure
+
+*caption*
+
+legend
+```
+.
+<document source="<src>/index.md">
+ <figure>
+ <image uri="path/to/figure">
+ <caption>
+ <emphasis>
+ caption
+ <legend>
+ <paragraph>
+ legend
+.
+
+SKIP: MockingError: MockState has not yet implemented attribute 'nested_list_parse'
+.
+```{meta}
+foo
+```
+.
+<document source="notset">
+.
+
+table (`sphinx.directives.patches.RSTTable`):
+.
+```{table} *title*
+:name: name
+
+| a | b |
+|---|---|
+| 1 | 2 |
+```
+.
+<document source="<src>/index.md">
+ <table classes="colwidths-auto" ids="name" names="name">
+ <title>
+ <emphasis>
+ title
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ a
+ <entry>
+ <paragraph>
+ b
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ 1
+ <entry>
+ <paragraph>
+ 2
+.
+
+csv-table (`sphinx.directives.patches.CSVTable`):
+.
+```{csv-table}
+
+"Albatross", 2.99, "On a stick!"
+```
+.
+<document source="<src>/index.md">
+ <table>
+ <tgroup cols="3">
+ <colspec colwidth="33">
+ <colspec colwidth="33">
+ <colspec colwidth="33">
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ Albatross
+ <entry>
+ <paragraph>
+ 2.99
+ <entry>
+ <paragraph>
+ On a stick!
+.
+
+list-table (`sphinx.directives.patches.ListTable`):
+.
+```{list-table}
+
+* - item
+```
+.
+<document source="<src>/index.md">
+ <table>
+ <tgroup cols="1">
+ <colspec colwidth="100">
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ item
+.
+
+code (`sphinx.directives.patches.Code`):
+.
+```{code} python
+
+a
+```
+.
+<document source="<src>/index.md">
+ <literal_block force="False" highlight_args="{}" language="python" xml:space="preserve">
+ a
+.
+
+math (`sphinx.directives.patches.MathDirective`):
+.
+```{math}
+```
+.
+<document source="<src>/index.md">
+ <math_block docname="index" label="True" nowrap="False" number="True" xml:space="preserve">
+.
+
+deprecated (`sphinx.domains.changeset.VersionChange`):
+.
+```{deprecated} 0.3
+```
+.
+<document source="<src>/index.md">
+ <versionmodified type="deprecated" version="0.3">
+ <paragraph translatable="False">
+ <inline classes="versionmodified deprecated">
+ Deprecated since version 0.3.
+.
+
+versionadded (`sphinx.domains.changeset.VersionChange`):
+.
+```{versionadded} 0.2
+```
+.
+<document source="<src>/index.md">
+ <versionmodified type="versionadded" version="0.2">
+ <paragraph translatable="False">
+ <inline classes="versionmodified added">
+ New in version 0.2.
+.
+
+versionchanged (`sphinx.domains.changeset.VersionChange`):
+.
+```{versionchanged} 0.1
+```
+.
+<document source="<src>/index.md">
+ <versionmodified type="versionchanged" version="0.1">
+ <paragraph translatable="False">
+ <inline classes="versionmodified changed">
+ Changed in version 0.1.
+.
+
+glossary (`sphinx.domains.std.Glossary`):
+.
+```{glossary}
+
+term 1 : A
+term 2 : B
+ Definition of both terms.
+```
+.
+<document source="<src>/index.md">
+ <glossary>
+ <definition_list classes="glossary">
+ <definition_list_item>
+ <term ids="term-term-1">
+ term 1
+ <index entries="('single',\ 'term\ 1',\ 'term-term-1',\ 'main',\ 'A')">
+ <term ids="term-term-2">
+ term 2
+ <index entries="('single',\ 'term\ 2',\ 'term-term-2',\ 'main',\ 'B')">
+ <definition>
+ <paragraph>
+ Definition of both terms.
+.
+
+SPHINX4-SKIP productionlist (`sphinx.domains.std.ProductionList`):
+.
+```{productionlist} try_stmt: try1_stmt | try2_stmt
+```
+.
+<document source="<src>/index.md">
+ <productionlist>
+ <production ids="grammar-token-try_stmt" tokenname="try_stmt" xml:space="preserve">
+ try1_stmt | try2_stmt
+.
+
+cmdoption (`sphinx.domains.std.Cmdoption`):
+.
+```{cmdoption} a
+```
+.
+<document source="<src>/index.md">
+ <index entries="('pair',\ 'command\ line\ option;\ a',\ 'cmdoption-arg-a',\ '',\ None)">
+ <desc classes="std cmdoption" desctype="cmdoption" domain="std" noindex="False" objtype="cmdoption">
+ <desc_signature allnames="a" classes="sig sig-object" ids="cmdoption-arg-a">
+ <desc_name classes="sig-name descname" xml:space="preserve">
+ a
+ <desc_addname classes="sig-prename descclassname" xml:space="preserve">
+ <desc_content>
+.
+
+rst:directive (`sphinx.domains.rst.ReSTDirective`):
+.
+```{rst:directive} a
+```
+.
+<document source="<src>/index.md">
+ <index entries="('single',\ 'a\ (directive)',\ 'directive-a',\ '',\ None)">
+ <desc classes="rst directive" desctype="directive" domain="rst" noindex="False" objtype="directive">
+ <desc_signature classes="sig sig-object" ids="directive-a">
+ <desc_name classes="sig-name descname" xml:space="preserve">
+ .. a::
+ <desc_content>
+.
+
+SPHINX4-SKIP rst:directive:option (`sphinx.domains.rst.ReSTDirectiveOption`):
+.
+```{rst:directive:option} a
+```
+.
+<document source="<src>/index.md">
+ <index entries="('single',\ ':a:\ (directive\ option)',\ 'directive-option-a',\ '',\ 'A')">
+ <desc classes="rst directive:option" desctype="directive:option" domain="rst" noindex="False" objtype="directive:option">
+ <desc_signature classes="sig sig-object" ids="directive-option-a">
+ <desc_name classes="sig-name descname" xml:space="preserve">
+ :a:
+ <desc_content>
+.
diff --git a/tests/test_renderers/fixtures/sphinx_roles.md b/tests/test_renderers/fixtures/sphinx_roles.md
new file mode 100644
index 0000000..37683db
--- /dev/null
+++ b/tests/test_renderers/fixtures/sphinx_roles.md
@@ -0,0 +1,632 @@
+c:func (`sphinx.domains.c.CXRefRole`):
+.
+{c:func}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
+ <literal classes="xref c c-func">
+ a()
+.
+
+c:member (`sphinx.domains.c.CObject`):
+.
+{c:member}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="member" refwarn="False">
+ <literal classes="xref c c-member">
+ a
+.
+
+c:macro (`sphinx.domains.c.CObject`):
+.
+{c:macro}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="macro" refwarn="False">
+ <literal classes="xref c c-macro">
+ a
+.
+
+c:data (`sphinx.domains.c.CXRefRole`):
+.
+{c:data}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="data" refwarn="False">
+ <literal classes="xref c c-data">
+ a
+.
+
+c:type (`sphinx.domains.c.CObject`):
+.
+{c:type}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="c" refexplicit="False" reftarget="a" reftype="type" refwarn="False">
+ <literal classes="xref c c-type">
+ a
+.
+
+cpp:any (`sphinx.domains.cpp.CPPXRefRole`):
+.
+{cpp:any}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="any" refwarn="False">
+ <literal classes="xref cpp cpp-any">
+ a
+.
+
+cpp:class (`sphinx.domains.cpp.CPPClassObject`):
+.
+{cpp:class}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="class" refwarn="False">
+ <literal classes="xref cpp cpp-class">
+ a
+.
+
+cpp:struct (`sphinx.domains.cpp.CPPClassObject`):
+.
+{cpp:struct}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="struct" refwarn="False">
+ <literal classes="xref cpp cpp-struct">
+ a
+.
+
+cpp:union (`sphinx.domains.cpp.CPPUnionObject`):
+.
+{cpp:union}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="union" refwarn="False">
+ <literal classes="xref cpp cpp-union">
+ a
+.
+
+cpp:func (`sphinx.domains.cpp.CPPXRefRole`):
+.
+{cpp:func}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
+ <literal classes="xref cpp cpp-func">
+ a()
+.
+
+cpp:member (`sphinx.domains.cpp.CPPMemberObject`):
+.
+{cpp:member}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="member" refwarn="False">
+ <literal classes="xref cpp cpp-member">
+ a
+.
+
+cpp:var (`sphinx.domains.cpp.CPPMemberObject`):
+.
+{cpp:var}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="var" refwarn="False">
+ <literal classes="xref cpp cpp-var">
+ a
+.
+
+cpp:type (`sphinx.domains.cpp.CPPTypeObject`):
+.
+{cpp:type}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="type" refwarn="False">
+ <literal classes="xref cpp cpp-type">
+ a
+.
+
+cpp:concept (`sphinx.domains.cpp.CPPConceptObject`):
+.
+{cpp:concept}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="concept" refwarn="False">
+ <literal classes="xref cpp cpp-concept">
+ a
+.
+
+cpp:enum (`sphinx.domains.cpp.CPPEnumObject`):
+.
+{cpp:enum}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="enum" refwarn="False">
+ <literal classes="xref cpp cpp-enum">
+ a
+.
+
+cpp:enumerator (`sphinx.domains.cpp.CPPEnumeratorObject`):
+.
+{cpp:enumerator}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="cpp" refexplicit="False" reftarget="a" reftype="enumerator" refwarn="False">
+ <literal classes="xref cpp cpp-enumerator">
+ a
+.
+
+SKIP cpp:expr (`sphinx.domains.cpp.CPPExprRole`):
+.
+{cpp:expr}`a`
+.
+<document source="notset">
+ <paragraph>
+ <desc_inline classes="cpp-expr sig sig-inline cpp">
+ <pending_xref classname="True" cpp:parent_key="<sphinx.domains.cpp.LookupKey object at 0x7f948a6a73d0>" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
+ <desc_sig_name classes="n">
+ a
+.
+
+SKIP cpp:texpr (`sphinx.domains.cpp.CPPExprRole`):
+.
+{cpp:texpr}`a`
+.
+<document source="notset">
+ <paragraph>
+ <desc_inline classes="cpp-texpr sig sig-inline cpp">
+ <pending_xref classname="True" cpp:parent_key="<sphinx.domains.cpp.LookupKey object at 0x7fac40b5f950>" modname="True" refdomain="cpp" reftarget="a" reftype="identifier">
+ <desc_sig_name classes="n">
+ a
+.
+
+js:func (`sphinx.domains.javascript.JSXRefRole`):
+.
+{js:func}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
+ <literal classes="xref js js-func">
+ a()
+.
+
+js:meth (`sphinx.domains.javascript.JSXRefRole`):
+.
+{js:meth}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="meth" refwarn="False">
+ <literal classes="xref js js-meth">
+ a()
+.
+
+js:class (`sphinx.domains.javascript.JSConstructor`):
+.
+{js:class}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="class" refwarn="False">
+ <literal classes="xref js js-class">
+ a()
+.
+
+js:data (`sphinx.domains.javascript.JSObject`):
+.
+{js:data}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="data" refwarn="False">
+ <literal classes="xref js js-data">
+ a
+.
+
+js:attr (`sphinx.domains.javascript.JSXRefRole`):
+.
+{js:attr}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="attr" refwarn="False">
+ <literal classes="xref js js-attr">
+ a
+.
+
+js:mod (`sphinx.domains.javascript.JSXRefRole`):
+.
+{js:mod}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref js:module="True" js:object="True" refdoc="index" refdomain="js" refexplicit="False" reftarget="a" reftype="mod" refwarn="False">
+ <literal classes="xref js js-mod">
+ a
+.
+
+eq (`sphinx.domains.math.MathReferenceRole`):
+.
+{eq}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="math" refexplicit="False" reftarget="a" reftype="eq" refwarn="True">
+ <literal classes="xref eq">
+ a
+.
+
+math:numref (`sphinx.domains.math.MathReferenceRole`):
+.
+{math:numref}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="math" refexplicit="False" reftarget="a" reftype="numref" refwarn="False">
+ <literal classes="xref math math-numref">
+ a
+.
+
+py:data (`sphinx.domains.python.PyVariable`):
+.
+{py:data}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="data" refwarn="False">
+ <literal classes="xref py py-data">
+ a
+.
+
+py:exc (`sphinx.domains.python.PyXRefRole`):
+.
+{py:exc}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="exc" refwarn="False">
+ <literal classes="xref py py-exc">
+ a
+.
+
+py:func (`sphinx.domains.python.PyXRefRole`):
+.
+{py:func}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="func" refwarn="False">
+ <literal classes="xref py py-func">
+ a()
+.
+
+py:class (`sphinx.domains.python.PyClasslike`):
+.
+{py:class}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="class" refwarn="False">
+ <literal classes="xref py py-class">
+ a
+.
+
+py:const (`sphinx.domains.python.PyXRefRole`):
+.
+{py:const}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="const" refwarn="False">
+ <literal classes="xref py py-const">
+ a
+.
+
+py:attr (`sphinx.domains.python.PyXRefRole`):
+.
+{py:attr}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="attr" refwarn="False">
+ <literal classes="xref py py-attr">
+ a
+.
+
+py:meth (`sphinx.domains.python.PyXRefRole`):
+.
+{py:meth}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="meth" refwarn="False">
+ <literal classes="xref py py-meth">
+ a()
+.
+
+py:mod (`sphinx.domains.python.PyXRefRole`):
+.
+{py:mod}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="mod" refwarn="False">
+ <literal classes="xref py py-mod">
+ a
+.
+
+py:obj (`sphinx.domains.python.PyXRefRole`):
+.
+{py:obj}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdoc="index" refdomain="py" refexplicit="False" reftarget="a" reftype="obj" refwarn="False">
+ <literal classes="xref py py-obj">
+ a
+.
+
+rst:role (`sphinx.domains.rst.ReSTRole`):
+.
+{rst:role}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="rst" refexplicit="False" reftarget="a" reftype="role" refwarn="False">
+ <literal classes="xref rst rst-role">
+ a
+.
+
+program (`sphinx.domains.std.Program`):
+.
+{program}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <literal_strong classes="program">
+ a
+.
+
+option (`sphinx.domains.std.Cmdoption`):
+.
+{option}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="option" refwarn="True" std:program="True">
+ <literal classes="xref std std-option">
+ a
+.
+
+envvar (`sphinx.domains.std.EnvVarXRefRole`):
+.
+{envvar}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <index entries="('single',\ 'a',\ 'index-0',\ '',\ None) ('single',\ 'environment\ variable;\ a',\ 'index-0',\ '',\ None)">
+ <target ids="index-0">
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="envvar" refwarn="False">
+ <literal classes="xref std std-envvar">
+ a
+.
+
+index (`sphinx.roles.Index`):
+.
+{index}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <index entries="('single',\ 'a',\ 'index-0',\ '',\ None)">
+ <target ids="index-0">
+ a
+.
+
+download (`sphinx.roles.XRefRole`):
+.
+{download}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <download_reference refdoc="index" refdomain="" refexplicit="False" reftarget="a" reftype="download" refwarn="False">
+ <literal classes="xref download">
+ a
+.
+
+any (`sphinx.roles.AnyXRefRole`):
+.
+{any}`a <alt text>`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="" refexplicit="True" reftarget="alt text" reftype="any" refwarn="True">
+ <literal classes="xref any">
+ a
+.
+
+pep (`sphinx.roles.PEP`):
+.
+{pep}`1`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <index entries="('single',\ 'Python\ Enhancement\ Proposals;\ PEP\ 1',\ 'index-0',\ '',\ None)">
+ <target ids="index-0">
+ <reference classes="pep" internal="False" refuri="https://peps.python.org/pep-0001/">
+ <strong>
+ PEP 1
+.
+
+rfc (`sphinx.roles.RFC`):
+.
+{rfc}`1`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <index entries="('single',\ 'RFC;\ RFC\ 1',\ 'index-0',\ '',\ None)">
+ <target ids="index-0">
+ <reference classes="rfc" internal="False" refuri="https://datatracker.ietf.org/doc/html/rfc1.html">
+ <strong>
+ RFC 1
+.
+
+guilabel (`sphinx.roles.GUILabel`):
+.
+{guilabel}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <inline classes="guilabel" rawtext=":guilabel:`a`">
+ a
+.
+
+menuselection (`sphinx.roles.MenuSelection`):
+.
+{menuselection}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <inline classes="menuselection" rawtext=":menuselection:`a`">
+ a
+.
+
+file (`sphinx.roles.EmphasizedLiteral`):
+.
+{file}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <literal classes="file" role="file">
+ a
+.
+
+samp (`sphinx.roles.EmphasizedLiteral`):
+.
+{samp}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <literal classes="samp" role="samp">
+ a
+.
+
+SKIP: Non-deterministic output
+.
+{abbr}`a`
+.
+<document source="notset">
+ <paragraph>
+ <abbreviation class="<function class_option at 0x1079fb830>>
+ a
+.
+
+rst:dir (`sphinx.roles.XRefRole`):
+.
+{rst:dir}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="rst" refexplicit="False" reftarget="a" reftype="dir" refwarn="False">
+ <literal classes="xref rst rst-dir">
+ a
+.
+
+token (`sphinx.roles.XRefRole`):
+.
+{token}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="token" refwarn="False">
+ <literal classes="xref std std-token">
+ a
+.
+
+term (`sphinx.roles.XRefRole`):
+.
+{term}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="term" refwarn="True">
+ <inline classes="xref std std-term">
+ a
+.
+
+ref (`sphinx.roles.XRefRole`):
+.
+{ref}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="ref" refwarn="True">
+ <inline classes="xref std std-ref">
+ a
+.
+
+ref with line breaks (`sphinx.roles.XRefRole`):
+.
+{ref}`some
+text
+<and
+a
+custom reference>`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="True" reftarget="and a custom reference" reftype="ref" refwarn="True">
+ <inline classes="xref std std-ref">
+ some text
+.
+
+numref (`sphinx.roles.XRefRole`):
+.
+{numref}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="numref" refwarn="True">
+ <literal classes="xref std std-numref">
+ a
+.
+
+keyword (`sphinx.roles.XRefRole`):
+.
+{keyword}`a`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="a" reftype="keyword" refwarn="True">
+ <literal classes="xref std std-keyword">
+ a
+.
+
+doc (`sphinx.roles.XRefRole`):
+.
+{doc}`this lecture <heavy_tails>`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="True" reftarget="heavy_tails" reftype="doc" refwarn="True">
+ <inline classes="xref std std-doc">
+ this lecture
+.
diff --git a/tests/test_renderers/fixtures/sphinx_syntax_elements.md b/tests/test_renderers/fixtures/sphinx_syntax_elements.md
new file mode 100644
index 0000000..1ac085c
--- /dev/null
+++ b/tests/test_renderers/fixtures/sphinx_syntax_elements.md
@@ -0,0 +1,800 @@
+Raw
+.
+foo
+.
+<document source="<src>/index.md">
+ <paragraph>
+ foo
+.
+
+Hard-break
+.
+foo\
+bar
+.
+<document source="<src>/index.md">
+ <paragraph>
+ foo
+ <raw format="html" xml:space="preserve">
+ <br />
+ <raw format="latex" xml:space="preserve">
+ \\
+ bar
+.
+
+Strong:
+.
+**foo**
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <strong>
+ foo
+.
+
+Emphasis
+.
+*foo*
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <emphasis>
+ foo
+.
+
+Escaped Emphasis:
+.
+\*foo*
+.
+<document source="<src>/index.md">
+ <paragraph>
+ *foo*
+.
+
+Mixed Inline
+.
+a *b* **c** `abc` \\*
+.
+<document source="<src>/index.md">
+ <paragraph>
+ a
+ <emphasis>
+ b
+
+ <strong>
+ c
+
+ <literal>
+ abc
+ \*
+.
+
+Inline Code:
+.
+`foo`
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <literal>
+ foo
+.
+
+Heading:
+.
+# foo
+.
+<document source="<src>/index.md">
+ <section ids="foo" names="foo">
+ <title>
+ foo
+.
+
+Heading Levels:
+.
+# a
+## b
+### c
+# d
+.
+<document source="<src>/index.md">
+ <section ids="a" names="a">
+ <title>
+ a
+ <section ids="b" names="b">
+ <title>
+ b
+ <section ids="c" names="c">
+ <title>
+ c
+ <section ids="d" names="d">
+ <title>
+ d
+.
+
+Block Code:
+.
+ foo
+.
+<document source="<src>/index.md">
+ <literal_block language="none" xml:space="preserve">
+ foo
+.
+
+Fenced Code:
+.
+```sh
+foo
+```
+.
+<document source="<src>/index.md">
+ <literal_block language="sh" xml:space="preserve">
+ foo
+.
+
+Fenced Code no language:
+.
+```
+foo
+```
+.
+<document source="<src>/index.md">
+ <literal_block language="default" xml:space="preserve">
+ foo
+.
+
+Fenced Code no language with trailing whitespace:
+.
+```
+foo
+```
+.
+<document source="<src>/index.md">
+ <literal_block language="default" xml:space="preserve">
+ foo
+.
+
+Image empty:
+.
+![]()
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <image alt="" uri="">
+.
+
+Image with alt and title:
+.
+![alt](src "title")
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <image alt="alt" title="title" uri="src">
+.
+
+Image with escapable html:
+.
+![alt](http://www.google<>.com)
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <image alt="alt" uri="http://www.google%3C%3E.com">
+.
+
+Block Quote:
+.
+> *foo*
+.
+<document source="<src>/index.md">
+ <block_quote>
+ <paragraph>
+ <emphasis>
+ foo
+.
+
+Bullet List:
+.
+- *foo*
+* bar
+.
+<document source="<src>/index.md">
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ <emphasis>
+ foo
+ <bullet_list bullet="*">
+ <list_item>
+ <paragraph>
+ bar
+.
+
+Nested Bullets
+.
+- a
+ - b
+ - c
+ - d
+.
+<document source="<src>/index.md">
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ a
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ b
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ c
+ <list_item>
+ <paragraph>
+ d
+.
+
+Enumerated List:
+.
+1. *foo*
+
+1) bar
+
+para
+
+10. starting
+11. enumerator
+.
+<document source="<src>/index.md">
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ <emphasis>
+ foo
+ <enumerated_list enumtype="arabic" prefix="" suffix=")">
+ <list_item>
+ <paragraph>
+ bar
+ <paragraph>
+ para
+ <enumerated_list enumtype="arabic" prefix="" start="10" suffix=".">
+ <list_item>
+ <paragraph>
+ starting
+ <list_item>
+ <paragraph>
+ enumerator
+.
+
+Nested Enumrated List:
+.
+1. a
+2. b
+ 1. c
+.
+<document source="<src>/index.md">
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ a
+ <list_item>
+ <paragraph>
+ b
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ c
+.
+
+Sphinx Role containing backtick:
+.
+{code}``a=1{`}``
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <literal classes="code">
+ a=1{`}
+.
+
+Target:
+.
+(target)=
+.
+<document source="<src>/index.md">
+ <target ids="target" names="target">
+.
+
+Target with whitespace:
+.
+(target with space)=
+.
+<document source="<src>/index.md">
+ <target ids="target-with-space" names="target\ with\ space">
+.
+
+Referencing:
+.
+(target)=
+
+Title
+=====
+
+[alt1](target)
+
+[](target2)
+
+[alt2](https://www.google.com)
+
+[alt3](#target3)
+.
+<document source="<src>/index.md">
+ <target ids="target" names="target">
+ <section ids="title" names="title">
+ <title>
+ Title
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="target" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ alt1
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="target2" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <reference refuri="https://www.google.com">
+ alt2
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="#target3" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ alt3
+.
+
+Comments:
+.
+line 1
+% a comment
+line 2
+.
+<document source="<src>/index.md">
+ <paragraph>
+ line 1
+ <comment xml:space="preserve">
+ a comment
+ <paragraph>
+ line 2
+.
+
+Block Break:
+.
++++ string
+.
+<document source="<src>/index.md">
+ <comment classes="block_break" xml:space="preserve">
+ string
+.
+
+Link Reference:
+.
+[name][key]
+
+[key]: https://www.google.com "a title"
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <reference refuri="https://www.google.com" title="a title">
+ name
+.
+
+Link Reference short version:
+.
+[name]
+
+[name]: https://www.google.com "a title"
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <reference refuri="https://www.google.com" title="a title">
+ name
+.
+
+Block Quotes:
+.
+```{epigraph}
+a b*c*
+
+-- a**b**
+```
+.
+<document source="<src>/index.md">
+ <block_quote classes="epigraph">
+ <paragraph>
+ a b
+ <emphasis>
+ c
+ <attribution>
+ a
+ <strong>
+ b
+.
+
+Link Definition in directive:
+.
+```{note}
+[a]
+```
+
+[a]: link
+.
+<document source="<src>/index.md">
+ <note>
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="link" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ a
+.
+
+Link Definition in nested directives:
+.
+```{note}
+[ref1]: link
+```
+
+```{note}
+[ref1]
+[ref2]
+```
+
+```{note}
+[ref2]: link
+```
+.
+<document source="<src>/index.md">
+ <note>
+ <note>
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="link" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ ref1
+
+ [ref2]
+ <note>
+.
+
+Footnotes:
+.
+[^a]
+
+[^a]: footnote*text*
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <footnote_reference auto="1" ids="id1" refname="a">
+ <transition classes="footnotes">
+ <footnote auto="1" ids="a" names="a">
+ <paragraph>
+ footnote
+ <emphasis>
+ text
+.
+
+Footnotes nested blocks:
+.
+[^a]
+
+[^a]: footnote*text*
+
+ abc
+xyz
+
+ > a
+
+ - b
+
+ c
+
+finish
+.
+<document source="<src>/index.md">
+ <paragraph>
+ <footnote_reference auto="1" ids="id1" refname="a">
+ <paragraph>
+ finish
+ <transition classes="footnotes">
+ <footnote auto="1" ids="a" names="a">
+ <paragraph>
+ footnote
+ <emphasis>
+ text
+ <paragraph>
+ abc
+
+ xyz
+ <block_quote>
+ <paragraph>
+ a
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ b
+ <paragraph>
+ c
+.
+
+Front Matter:
+.
+---
+a: 1
+b: foo
+c:
+ d: 2
+---
+.
+<document source="<src>/index.md">
+ <field_list>
+ <field>
+ <field_name>
+ a
+ <field_body>
+ <paragraph>
+ <literal>
+ 1
+ <field>
+ <field_name>
+ b
+ <field_body>
+ <paragraph>
+ <literal>
+ foo
+ <field>
+ <field_name>
+ c
+ <field_body>
+ <paragraph>
+ <literal>
+ {"d": 2}
+.
+
+Front Matter Biblio:
+.
+---
+author: Chris Sewell
+authors: Chris Sewell, Chris Hodgraf
+organization: EPFL
+address: |
+ 1 Cedar Park Close
+ Thundersley
+ Essex
+contact: <https://example.com>
+version: 1.0
+revision: 1.1
+status: good
+date: 2/12/1985
+copyright: MIT
+dedication: |
+ To my *homies*
+abstract:
+ Something something **dark** side
+other: Something else
+---
+.
+<document source="<src>/index.md">
+ <field_list>
+ <field>
+ <field_name>
+ author
+ <field_body>
+ <paragraph>
+ Chris Sewell
+ <field>
+ <field_name>
+ authors
+ <field_body>
+ <paragraph>
+ Chris Sewell, Chris Hodgraf
+ <field>
+ <field_name>
+ organization
+ <field_body>
+ <paragraph>
+ EPFL
+ <field>
+ <field_name>
+ address
+ <field_body>
+ <paragraph>
+ 1 Cedar Park Close
+
+ Thundersley
+
+ Essex
+
+ <field>
+ <field_name>
+ contact
+ <field_body>
+ <paragraph>
+ <reference refuri="https://example.com">
+ https://example.com
+ <field>
+ <field_name>
+ version
+ <field_body>
+ <paragraph>
+ 1.0
+ <field>
+ <field_name>
+ revision
+ <field_body>
+ <paragraph>
+ 1.1
+ <field>
+ <field_name>
+ status
+ <field_body>
+ <paragraph>
+ good
+ <field>
+ <field_name>
+ date
+ <field_body>
+ <paragraph>
+ 2/12/1985
+ <field>
+ <field_name>
+ copyright
+ <field_body>
+ <paragraph>
+ MIT
+ <field>
+ <field_name>
+ dedication
+ <field_body>
+ <paragraph>
+ To my
+ <emphasis>
+ homies
+
+ <field>
+ <field_name>
+ abstract
+ <field_body>
+ <paragraph>
+ Something something
+ <strong>
+ dark
+ side
+ <field>
+ <field_name>
+ other
+ <field_body>
+ <paragraph>
+ <literal>
+ Something else
+.
+
+Front Matter Bad Yaml:
+.
+---
+a: {
+---
+.
+<document source="<src>/index.md">
+ <system_message level="2" line="1" source="<src>/index.md" type="WARNING">
+ <paragraph>
+ Malformed YAML [myst.topmatter]
+.
+
+Front Matter HTML Meta
+.
+---
+myst:
+ html_meta:
+ keywords: Sphinx, documentation, builder
+ description lang=en: An amusing story
+ description lang=fr: Un histoire amusant
+ http-equiv=Content-Type: text/html; charset=ISO-8859-1
+---
+.
+<document source="<src>/index.md">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="Sphinx, documentation, builder" name="keywords">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="An amusing story" lang="en" name="description">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="Un histoire amusant" lang="fr" name="description">
+ <pending>
+ .. internal attributes:
+ .transform: docutils.transforms.components.Filter
+ .details:
+ component: 'writer'
+ format: 'html'
+ nodes:
+ <meta content="text/html; charset=ISO-8859-1" http-equiv="Content-Type">
+.
+
+Full Test:
+.
+---
+a: 1
+---
+
+(target)=
+# header 1
+## sub header 1
+
+a *b* **c** `abc`
+
+## sub header 2
+
+x y [a](http://www.xyz.com) z
+
+---
+
+# header 2
+
+```::python {a=1}
+a = 1
+```
+
+[](target)
+.
+<document source="<src>/index.md">
+ <field_list>
+ <field>
+ <field_name>
+ a
+ <field_body>
+ <paragraph>
+ <literal>
+ 1
+ <target ids="target" names="target">
+ <section ids="header-1" names="header\ 1">
+ <title>
+ header 1
+ <section ids="sub-header-1" names="sub\ header\ 1">
+ <title>
+ sub header 1
+ <paragraph>
+ a
+ <emphasis>
+ b
+
+ <strong>
+ c
+
+ <literal>
+ abc
+ <section ids="sub-header-2" names="sub\ header\ 2">
+ <title>
+ sub header 2
+ <paragraph>
+ x y
+ <reference refuri="http://www.xyz.com">
+ a
+ z
+ <transition>
+ <section ids="header-2" names="header\ 2">
+ <title>
+ header 2
+ <literal_block language="::python" xml:space="preserve">
+ a = 1
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="target" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+.
diff --git a/tests/test_renderers/fixtures/tables.md b/tests/test_renderers/fixtures/tables.md
new file mode 100644
index 0000000..b478b92
--- /dev/null
+++ b/tests/test_renderers/fixtures/tables.md
@@ -0,0 +1,148 @@
+Simple:
+.
+a|b
+-|-
+1|2
+.
+<document source="<src>/index.md">
+ <table classes="colwidths-auto">
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ a
+ <entry>
+ <paragraph>
+ b
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ 1
+ <entry>
+ <paragraph>
+ 2
+.
+
+Header only:
+.
+| abc | def |
+| --- | --- |
+.
+<document source="<src>/index.md">
+ <table classes="colwidths-auto">
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ abc
+ <entry>
+ <paragraph>
+ def
+.
+
+Aligned:
+.
+a | b | c
+:-|:-:| -:
+1 | 2 | 3
+.
+<document source="<src>/index.md">
+ <table classes="colwidths-auto">
+ <tgroup cols="3">
+ <colspec colwidth="33">
+ <colspec colwidth="33">
+ <colspec colwidth="33">
+ <thead>
+ <row>
+ <entry classes="text-left">
+ <paragraph>
+ a
+ <entry classes="text-center">
+ <paragraph>
+ b
+ <entry classes="text-right">
+ <paragraph>
+ c
+ <tbody>
+ <row>
+ <entry classes="text-left">
+ <paragraph>
+ 1
+ <entry classes="text-center">
+ <paragraph>
+ 2
+ <entry classes="text-right">
+ <paragraph>
+ 3
+.
+
+Nested syntax:
+.
+| *a* | __*b*__ |
+| --- | -------- |
+|c | {sub}`x` |
+.
+<document source="<src>/index.md">
+ <table classes="colwidths-auto">
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ <emphasis>
+ a
+ <entry>
+ <paragraph>
+ <strong>
+ <emphasis>
+ b
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ c
+ <entry>
+ <paragraph>
+ <subscript>
+ x
+.
+
+External links:
+.
+a|b
+|-|-|
+[link-a](https://www.google.com/)|[link-b](https://www.python.org/)
+.
+<document source="<src>/index.md">
+ <table classes="colwidths-auto">
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ a
+ <entry>
+ <paragraph>
+ b
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ <reference refuri="https://www.google.com/">
+ link-a
+ <entry>
+ <paragraph>
+ <reference refuri="https://www.python.org/">
+ link-b
+.
diff --git a/tests/test_renderers/test_error_reporting.py b/tests/test_renderers/test_error_reporting.py
new file mode 100644
index 0000000..2547e85
--- /dev/null
+++ b/tests/test_renderers/test_error_reporting.py
@@ -0,0 +1,22 @@
+"""Tests of the warning reporting for different MyST Markdown inputs."""
+from io import StringIO
+from pathlib import Path
+
+import pytest
+from docutils.core import publish_doctree
+
+from myst_parser.parsers.docutils_ import Parser
+
+FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "reporter_warnings.md")
+def test_basic(file_params):
+ """Test basic functionality."""
+ report_stream = StringIO()
+ publish_doctree(
+ file_params.content,
+ parser=Parser(),
+ settings_overrides={"warning_stream": report_stream},
+ )
+ file_params.assert_expected(report_stream.getvalue(), rstrip=True)
diff --git a/tests/test_renderers/test_fixtures_docutils.py b/tests/test_renderers/test_fixtures_docutils.py
new file mode 100644
index 0000000..a1e16e5
--- /dev/null
+++ b/tests/test_renderers/test_fixtures_docutils.py
@@ -0,0 +1,107 @@
+"""Test fixture files, using the ``DocutilsRenderer``.
+
+Note, the output AST is before any transforms are applied.
+"""
+import shlex
+from io import StringIO
+from pathlib import Path
+
+import pytest
+from docutils.core import Publisher, publish_doctree
+
+from myst_parser.parsers.docutils_ import Parser
+
+FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "docutil_syntax_elements.md")
+def test_syntax_elements(file_params, monkeypatch):
+ """Test conversion of Markdown to docutils AST (before transforms are applied)."""
+
+ def _apply_transforms(self):
+ pass
+
+ monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
+
+ doctree = publish_doctree(
+ file_params.content,
+ source_path="notset",
+ parser=Parser(),
+ settings_overrides={"myst_highlight_code_blocks": False},
+ )
+
+ # in docutils 0.18 footnote ids have changed
+ outcome = doctree.pformat().replace('"footnote-reference-1"', '"id1"')
+ file_params.assert_expected(outcome, rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "docutil_roles.md")
+def test_docutils_roles(file_params, monkeypatch):
+ """Test conversion of Markdown to docutils AST (before transforms are applied)."""
+
+ def _apply_transforms(self):
+ pass
+
+ monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
+
+ doctree = publish_doctree(
+ file_params.content,
+ source_path="notset",
+ parser=Parser(),
+ )
+
+ ptree = doctree.pformat()
+ # docutils >=0.19 changes:
+ ptree = ptree.replace(
+ 'refuri="http://tools.ietf.org/html/rfc1.html"',
+ 'refuri="https://tools.ietf.org/html/rfc1.html"',
+ )
+ ptree = ptree.replace(
+ 'refuri="http://www.python.org/dev/peps/pep-0000"',
+ 'refuri="https://peps.python.org/pep-0000"',
+ )
+
+ file_params.assert_expected(ptree, rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "docutil_directives.md")
+def test_docutils_directives(file_params, monkeypatch):
+ """Test output of docutils directives."""
+ if "SKIP" in file_params.description: # line-block directive not yet supported
+ pytest.skip(file_params.description)
+
+ def _apply_transforms(self):
+ pass
+
+ monkeypatch.setattr(Publisher, "apply_transforms", _apply_transforms)
+
+ doctree = publish_doctree(
+ file_params.content,
+ source_path="notset",
+ parser=Parser(),
+ )
+
+ file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "docutil_syntax_extensions.txt")
+def test_syntax_extensions(file_params):
+ """The description is parsed as a docutils commandline"""
+ pub = Publisher(parser=Parser())
+ option_parser = pub.setup_option_parser()
+ try:
+ settings = option_parser.parse_args(
+ shlex.split(file_params.description)
+ ).__dict__
+ except Exception as err:
+ raise AssertionError(
+ f"Failed to parse commandline: {file_params.description}\n{err}"
+ )
+ report_stream = StringIO()
+ settings["warning_stream"] = report_stream
+ doctree = publish_doctree(
+ file_params.content,
+ parser=Parser(),
+ settings_overrides=settings,
+ )
+ file_params.assert_expected(doctree.pformat(), rstrip_lines=True)
diff --git a/tests/test_renderers/test_fixtures_sphinx.py b/tests/test_renderers/test_fixtures_sphinx.py
new file mode 100644
index 0000000..b8cf549
--- /dev/null
+++ b/tests/test_renderers/test_fixtures_sphinx.py
@@ -0,0 +1,119 @@
+"""Test fixture files, using the ``SphinxRenderer``.
+
+Note, the output AST is before any transforms are applied.
+"""
+from __future__ import annotations
+
+import re
+import sys
+from pathlib import Path
+
+import pytest
+from sphinx_pytest.plugin import CreateDoctree
+
+from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer
+
+FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "sphinx_syntax_elements.md")
+def test_syntax_elements(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "tables.md")
+def test_tables(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "directive_options.md")
+def test_directive_options(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "sphinx_directives.md")
+def test_sphinx_directives(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ # TODO fix skipped directives
+ # TODO test domain directives
+ if file_params.title.startswith("SKIP") or file_params.title.startswith(
+ "SPHINX4-SKIP"
+ ):
+ pytest.skip(file_params.title)
+
+ sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
+ pformat = sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
+ # see https://github.com/sphinx-doc/sphinx/issues/9827
+ pformat = pformat.replace('<glossary sorted="False">', "<glossary>")
+ # see https://github.com/executablebooks/MyST-Parser/issues/522
+ if sys.maxsize == 2147483647:
+ pformat = pformat.replace('"2147483647"', '"9223372036854775807"')
+ file_params.assert_expected(pformat, rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "sphinx_roles.md")
+def test_sphinx_roles(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ if file_params.title.startswith("SKIP"):
+ pytest.skip(file_params.title)
+
+ sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
+ pformat = sphinx_doctree_no_tr(file_params.content, "index.md").pformat("index")
+ # sphinx 3 adds a parent key
+ pformat = re.sub('cpp:parent_key="[^"]*"', 'cpp:parent_key=""', pformat)
+ # sphinx >= 4.5.0 adds a trailing slash to PEP URLs,
+ # see https://github.com/sphinx-doc/sphinx/commit/658689433eacc9eb
+ pformat = pformat.replace(
+ ' refuri="http://www.python.org/dev/peps/pep-0001">',
+ ' refuri="http://www.python.org/dev/peps/pep-0001/">',
+ )
+ file_params.assert_expected(pformat, rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "dollarmath.md")
+def test_dollarmath(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ sphinx_doctree_no_tr.set_conf(
+ {"extensions": ["myst_parser"], "myst_enable_extensions": ["dollarmath"]}
+ )
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "amsmath.md")
+def test_amsmath(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch):
+ monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid")
+ sphinx_doctree_no_tr.set_conf(
+ {"extensions": ["myst_parser"], "myst_enable_extensions": ["amsmath"]}
+ )
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "containers.md")
+def test_containers(file_params, sphinx_doctree_no_tr: CreateDoctree, monkeypatch):
+ monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid")
+ sphinx_doctree_no_tr.set_conf(
+ {"extensions": ["myst_parser"], "myst_enable_extensions": ["colon_fence"]}
+ )
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "eval_rst.md")
+def test_evalrst_elements(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ sphinx_doctree_no_tr.set_conf({"extensions": ["myst_parser"]})
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "definition_lists.md")
+def test_definition_lists(file_params, sphinx_doctree_no_tr: CreateDoctree):
+ sphinx_doctree_no_tr.set_conf(
+ {"extensions": ["myst_parser"], "myst_enable_extensions": ["deflist"]}
+ )
+ result = sphinx_doctree_no_tr(file_params.content, "index.md")
+ file_params.assert_expected(result.pformat("index"), rstrip_lines=True)
diff --git a/tests/test_renderers/test_include_directive.py b/tests/test_renderers/test_include_directive.py
new file mode 100644
index 0000000..f02b246
--- /dev/null
+++ b/tests/test_renderers/test_include_directive.py
@@ -0,0 +1,52 @@
+import os
+from io import StringIO
+from pathlib import Path
+
+import pytest
+from docutils.core import publish_doctree
+
+from myst_parser.parsers.docutils_ import Parser
+
+FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "mock_include.md")
+def test_render(file_params, tmp_path, monkeypatch):
+ monkeypatch.chdir(tmp_path)
+
+ tmp_path.joinpath("other.md").write_text("a\nb\nc")
+ tmp_path.joinpath("fmatter.md").write_text("---\na: 1\n---\nb")
+
+ doctree = publish_doctree(
+ file_params.content,
+ parser=Parser(),
+ settings_overrides={"myst_highlight_code_blocks": False},
+ )
+
+ doctree["source"] = "tmpdir/test.md"
+ output = doctree.pformat().replace(str(tmp_path) + os.sep, "tmpdir" + "/").rstrip()
+
+ file_params.assert_expected(output, rstrip=True)
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "mock_include_errors.md")
+def test_errors(file_params, tmp_path, monkeypatch):
+ if file_params.title.startswith("Non-existent path") and os.name == "nt":
+ pytest.skip("tmp_path not converted correctly on Windows")
+
+ monkeypatch.chdir(tmp_path)
+
+ tmp_path.joinpath("bad.md").write_text("{a}`b`")
+
+ report_stream = StringIO()
+ publish_doctree(
+ file_params.content,
+ source_path=str(tmp_path / "test.md"),
+ parser=Parser(),
+ settings_overrides={"halt_level": 6, "warning_stream": report_stream},
+ )
+
+ file_params.assert_expected(
+ report_stream.getvalue().replace(str(tmp_path) + os.sep, "tmpdir" + "/"),
+ rstrip=True,
+ )
diff --git a/tests/test_renderers/test_myst_config.py b/tests/test_renderers/test_myst_config.py
new file mode 100644
index 0000000..31e2444
--- /dev/null
+++ b/tests/test_renderers/test_myst_config.py
@@ -0,0 +1,38 @@
+"""Test (docutils) parsing with different ``MdParserConfig`` options set."""
+import shlex
+from io import StringIO
+from pathlib import Path
+
+import pytest
+from docutils.core import Publisher, publish_doctree
+
+from myst_parser.parsers.docutils_ import Parser
+
+FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "myst-config.txt")
+def test_cmdline(file_params):
+ """The description is parsed as a docutils commandline"""
+ pub = Publisher(parser=Parser())
+ option_parser = pub.setup_option_parser()
+ try:
+ settings = option_parser.parse_args(
+ shlex.split(file_params.description)
+ ).__dict__
+ except Exception as err:
+ raise AssertionError(
+ f"Failed to parse commandline: {file_params.description}\n{err}"
+ )
+ report_stream = StringIO()
+ settings["warning_stream"] = report_stream
+ doctree = publish_doctree(
+ file_params.content,
+ parser=Parser(),
+ settings_overrides=settings,
+ )
+ output = doctree.pformat()
+ warnings = report_stream.getvalue()
+ if warnings:
+ output += "\n" + warnings
+ file_params.assert_expected(output, rstrip_lines=True)
diff --git a/tests/test_renderers/test_myst_refs.py b/tests/test_renderers/test_myst_refs.py
new file mode 100644
index 0000000..083b34a
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs.py
@@ -0,0 +1,38 @@
+import pytest
+from sphinx_pytest.plugin import CreateDoctree
+
+
+@pytest.mark.parametrize(
+ "test_name,text,should_warn",
+ [
+ ("null", "", False),
+ ("missing", "[](ref)", True),
+ ("doc", "[](index)", False),
+ ("doc_with_extension", "[](index.md)", False),
+ ("doc_nested", "[*text*](index)", False),
+ ("ref", "(ref)=\n# Title\n[](ref)", False),
+ ("ref_nested", "(ref)=\n# Title\n[*text*](ref)", False),
+ ("duplicate", "(index)=\n# Title\n[](index)", True),
+ ("ref_colon", "(ref:colon)=\n# Title\n[](ref:colon)", False),
+ ],
+)
+def test_parse(
+ test_name: str,
+ text: str,
+ should_warn: bool,
+ sphinx_doctree: CreateDoctree,
+ file_regression,
+):
+ sphinx_doctree.set_conf({"extensions": ["myst_parser"]})
+ result = sphinx_doctree(text, "index.md")
+ assert not result.warnings
+
+ doctree = result.get_resolved_doctree("index")
+
+ if should_warn:
+ assert result.warnings
+ else:
+ assert not result.warnings
+
+ doctree["source"] = "root/index.md"
+ file_regression.check(doctree.pformat(), basename=test_name, extension=".xml")
diff --git a/tests/test_renderers/test_myst_refs/doc.xml b/tests/test_renderers/test_myst_refs/doc.xml
new file mode 100644
index 0000000..55cb74c
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/doc.xml
@@ -0,0 +1,5 @@
+<document source="root/index.md">
+ <paragraph>
+ <reference internal="True" refuri="">
+ <inline classes="doc std std-doc">
+ <no title>
diff --git a/tests/test_renderers/test_myst_refs/doc_nested.xml b/tests/test_renderers/test_myst_refs/doc_nested.xml
new file mode 100644
index 0000000..35679d9
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/doc_nested.xml
@@ -0,0 +1,6 @@
+<document source="root/index.md">
+ <paragraph>
+ <reference internal="True" refuri="">
+ <inline classes="doc std std-doc">
+ <emphasis>
+ text
diff --git a/tests/test_renderers/test_myst_refs/doc_with_extension.xml b/tests/test_renderers/test_myst_refs/doc_with_extension.xml
new file mode 100644
index 0000000..55cb74c
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/doc_with_extension.xml
@@ -0,0 +1,5 @@
+<document source="root/index.md">
+ <paragraph>
+ <reference internal="True" refuri="">
+ <inline classes="doc std std-doc">
+ <no title>
diff --git a/tests/test_renderers/test_myst_refs/duplicate.xml b/tests/test_renderers/test_myst_refs/duplicate.xml
new file mode 100644
index 0000000..755906c
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/duplicate.xml
@@ -0,0 +1,9 @@
+<document source="root/index.md">
+ <target refid="index">
+ <section ids="title index" names="title index">
+ <title>
+ Title
+ <paragraph>
+ <reference internal="True" refid="index">
+ <inline classes="std std-ref">
+ Title
diff --git a/tests/test_renderers/test_myst_refs/missing.xml b/tests/test_renderers/test_myst_refs/missing.xml
new file mode 100644
index 0000000..6bc72ad
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/missing.xml
@@ -0,0 +1,3 @@
+<document source="root/index.md">
+ <paragraph>
+ <inline classes="xref myst">
diff --git a/tests/test_renderers/test_myst_refs/null.xml b/tests/test_renderers/test_myst_refs/null.xml
new file mode 100644
index 0000000..d09aa5e
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/null.xml
@@ -0,0 +1 @@
+<document source="root/index.md">
diff --git a/tests/test_renderers/test_myst_refs/ref.xml b/tests/test_renderers/test_myst_refs/ref.xml
new file mode 100644
index 0000000..e4ae200
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/ref.xml
@@ -0,0 +1,9 @@
+<document source="root/index.md">
+ <target refid="ref">
+ <section ids="title ref" names="title ref">
+ <title>
+ Title
+ <paragraph>
+ <reference internal="True" refid="ref">
+ <inline classes="std std-ref">
+ Title
diff --git a/tests/test_renderers/test_myst_refs/ref_colon.xml b/tests/test_renderers/test_myst_refs/ref_colon.xml
new file mode 100644
index 0000000..f1e9923
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/ref_colon.xml
@@ -0,0 +1,9 @@
+<document source="root/index.md">
+ <target refid="ref-colon">
+ <section ids="title ref-colon" names="title ref:colon">
+ <title>
+ Title
+ <paragraph>
+ <reference internal="True" refid="ref-colon">
+ <inline classes="std std-ref">
+ Title
diff --git a/tests/test_renderers/test_myst_refs/ref_nested.xml b/tests/test_renderers/test_myst_refs/ref_nested.xml
new file mode 100644
index 0000000..be69ef2
--- /dev/null
+++ b/tests/test_renderers/test_myst_refs/ref_nested.xml
@@ -0,0 +1,10 @@
+<document source="root/index.md">
+ <target refid="ref">
+ <section ids="title ref" names="title ref">
+ <title>
+ Title
+ <paragraph>
+ <reference internal="True" refid="ref">
+ <inline classes="std std-ref">
+ <emphasis>
+ text
diff --git a/tests/test_renderers/test_parse_directives.py b/tests/test_renderers/test_parse_directives.py
new file mode 100644
index 0000000..ae6792b
--- /dev/null
+++ b/tests/test_renderers/test_parse_directives.py
@@ -0,0 +1,46 @@
+from pathlib import Path
+
+import pytest
+import yaml
+from docutils.parsers.rst.directives.admonitions import Admonition, Note
+from docutils.parsers.rst.directives.body import Rubric
+from markdown_it import MarkdownIt
+
+from myst_parser.parsers.directives import DirectiveParsingError, parse_directive_text
+
+FIXTURE_PATH = Path(__file__).parent.joinpath("fixtures")
+
+
+@pytest.mark.param_file(FIXTURE_PATH / "directive_parsing.txt")
+def test_parsing(file_params):
+ """Test parsing of directive text."""
+ tokens = MarkdownIt("commonmark").parse(file_params.content)
+ assert len(tokens) == 1 and tokens[0].type == "fence"
+ name, *first_line = tokens[0].info.split(maxsplit=1)
+ if name == "{note}":
+ klass = Note
+ elif name == "{admonition}":
+ klass = Admonition
+ else:
+ raise AssertionError(f"Unknown directive: {name}")
+ arguments, options, body_lines, content_offset = parse_directive_text(
+ klass, first_line[0] if first_line else "", tokens[0].content
+ )
+ outcome = yaml.safe_dump(
+ {
+ "arguments": arguments,
+ "options": options,
+ "body": body_lines,
+ "content_offset": content_offset,
+ },
+ sort_keys=True,
+ )
+ file_params.assert_expected(outcome, rstrip_lines=True)
+
+
+@pytest.mark.parametrize(
+ "descript,klass,arguments,content", [("no content", Rubric, "", "a")]
+)
+def test_parsing_errors(descript, klass, arguments, content):
+ with pytest.raises(DirectiveParsingError):
+ parse_directive_text(klass, arguments, content)
diff --git a/tests/test_sphinx/conftest.py b/tests/test_sphinx/conftest.py
new file mode 100644
index 0000000..4165a31
--- /dev/null
+++ b/tests/test_sphinx/conftest.py
@@ -0,0 +1,127 @@
+"""
+Uses sphinx's pytest fixture to run builds
+
+usage:
+
+.. code-block:: python
+
+ @pytest.mark.sphinx(
+ buildername='html',
+ srcdir='path/to/source')
+ def test_basic(app, status, warning, get_sphinx_app_output):
+
+ app.build()
+
+ assert 'build succeeded' in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ output = get_sphinx_app_output(app, buildername='html')
+
+parameters available to parse to ``@pytest.mark.sphinx``:
+
+- buildername='html'
+- srcdir=None
+- testroot='root' (only used if srcdir not set)
+- freshenv=False
+- confoverrides=None
+- status=None
+- warning=None
+- tags=None
+- docutilsconf=None
+
+"""
+import os
+import pathlib
+import shutil
+
+import pytest
+from bs4 import BeautifulSoup
+from sphinx.testing.path import path
+
+from myst_parser._compat import findall
+
+SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "sourcedirs"))
+
+
+@pytest.fixture(scope="session", autouse=True)
+def remove_sphinx_builds():
+ """remove all build directories from the test folder"""
+ yield
+ srcdirs = pathlib.Path(SOURCE_DIR)
+ for entry in srcdirs.iterdir(): # type: pathlib.Path
+ if entry.is_dir() and entry.joinpath("_build").exists():
+ shutil.rmtree(str(entry.joinpath("_build")))
+
+
+@pytest.fixture
+def get_sphinx_app_output(file_regression):
+ def read(
+ app,
+ buildername="html",
+ filename="index.html",
+ encoding="utf-8",
+ regress_html=False,
+ regress_ext=".html",
+ replace=None,
+ ):
+
+ outpath = path(os.path.join(str(app.srcdir), "_build", buildername, filename))
+ if not outpath.exists():
+ raise OSError(f"no output file exists: {outpath}")
+
+ try:
+ # introduced in sphinx 3.0
+ content = outpath.read_text(encoding=encoding)
+ except AttributeError:
+ content = outpath.text(encoding=encoding)
+
+ if regress_html:
+ # only regress the inner body, since other sections are non-deterministic
+ soup = BeautifulSoup(content, "html.parser")
+ doc_div = soup.findAll("div", {"class": "documentwrapper"})[0]
+ # pygments 2.11.0 introduces a whitespace tag
+ for pygment_whitespace in doc_div.select("pre > span.w"):
+ pygment_whitespace.replace_with(pygment_whitespace.text)
+ text = doc_div.prettify()
+ for find, rep in (replace or {}).items():
+ text = text.replace(find, rep)
+ file_regression.check(text, extension=regress_ext, encoding="utf8")
+
+ return content
+
+ return read
+
+
+@pytest.fixture
+def get_sphinx_app_doctree(file_regression):
+ def read(
+ app,
+ docname="index",
+ resolve=False,
+ regress=False,
+ replace=None,
+ regress_ext=".xml",
+ ):
+ if resolve:
+ doctree = app.env.get_and_resolve_doctree(docname, app.builder)
+ extension = f".resolved{regress_ext}"
+ else:
+ doctree = app.env.get_doctree(docname)
+ extension = regress_ext
+
+ # convert absolute filenames
+ for node in findall(doctree)(
+ lambda n: "source" in n and not isinstance(n, str)
+ ):
+ node["source"] = pathlib.Path(node["source"]).name
+
+ if regress:
+ text = doctree.pformat() # type: str
+ for find, rep in (replace or {}).items():
+ text = text.replace(find, rep)
+ file_regression.check(text, extension=extension)
+
+ return doctree
+
+ return read
diff --git a/tests/test_sphinx/sourcedirs/.gitignore b/tests/test_sphinx/sourcedirs/.gitignore
new file mode 100644
index 0000000..69fa449
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/.gitignore
@@ -0,0 +1 @@
+_build/
diff --git a/tests/test_sphinx/sourcedirs/basic/conf.py b/tests/test_sphinx/sourcedirs/basic/conf.py
new file mode 100644
index 0000000..e1c5009
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/basic/conf.py
@@ -0,0 +1,2 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
diff --git a/tests/test_sphinx/sourcedirs/basic/content.md b/tests/test_sphinx/sourcedirs/basic/content.md
new file mode 100644
index 0000000..ab7b6e2
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/basic/content.md
@@ -0,0 +1,104 @@
+---
+author: Chris Sewell
+authors: Chris Sewell, Chris Hodgraf
+organization: EPFL
+address: |
+ 1 Cedar Park Close
+ Thundersley
+ Essex
+contact: <https://example.com>
+version: 1.0
+revision: 1.1
+status: good
+date: 2/12/1985
+copyright: MIT
+dedication: |
+ To my *homies*
+abstract:
+ Something something **dark** side
+other: Something else
+other_dict:
+ key: value
+---
+
+(target)=
+
+# Header
+
+% comment
+
+````{note}
+abcd *abc* [google](https://www.google.com)
+
+ ```{warning}
+ xyz
+ ```
+
+````
+
+```{admonition} Title with [link](target2)
+Content
+```
+
+(target2)=
+
+```{figure} example.jpg
+---
+height: 40px
+target: https://www.google.com
+---
+Caption
+```
+
+![*alternative text*](example.jpg)
+
+<https://www.google.com>
+
+**{code}`` a=1{`} ``**
+
+{math}`sdfds`
+
+**$a=1$**
+
+$$b=2$$
+
+$$c=2$$ (eq:label)
+
+{eq}`eq:label`
+
+`` a=1{`} ``
+
+| a | b |
+|-----|--:|
+| *a* | 2 |
+| [link-a](https://google.com) | [link-b](https://python.org) |
+
+this
+is
+a
+paragraph
+% a comment 2
+
+this is a second paragraph
+
+- a list
+ - a sub list
+% a comment 3
+- new list?
+
+{ref}`target` {ref}`target2`
+
++++ a block break
+
+[name][key]
+
+[key]: https://www.google.com "a title"
+
+```
+def func(a, b=1):
+ print(a)
+```
+
+Special substitution references:
+
+{sub-ref}`wordcount-words` words | {sub-ref}`wordcount-minutes` min read
diff --git a/tests/test_sphinx/sourcedirs/basic/example.jpg b/tests/test_sphinx/sourcedirs/basic/example.jpg
new file mode 100644
index 0000000..1c1e83e
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/basic/example.jpg
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/basic/index.md b/tests/test_sphinx/sourcedirs/basic/index.md
new file mode 100644
index 0000000..1c9d9c7
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/basic/index.md
@@ -0,0 +1,3 @@
+```{toctree}
+content
+```
diff --git a/tests/test_sphinx/sourcedirs/basic/orphan.md b/tests/test_sphinx/sourcedirs/basic/orphan.md
new file mode 100644
index 0000000..c42b7c3
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/basic/orphan.md
@@ -0,0 +1,5 @@
+---
+orphan: true
+---
+
+This is an orphan document, not specified in any toctrees.
diff --git a/tests/test_sphinx/sourcedirs/commonmark_only/conf.py b/tests/test_sphinx/sourcedirs/commonmark_only/conf.py
new file mode 100644
index 0000000..ef5f1d8
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/commonmark_only/conf.py
@@ -0,0 +1,3 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+myst_commonmark_only = True
diff --git a/tests/test_sphinx/sourcedirs/commonmark_only/index.md b/tests/test_sphinx/sourcedirs/commonmark_only/index.md
new file mode 100644
index 0000000..4212519
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/commonmark_only/index.md
@@ -0,0 +1,7 @@
+# Test
+
+```{note}
+hallo
+```
+
+{a}`b`
diff --git a/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py b/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py
new file mode 100644
index 0000000..9845000
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/extended_syntaxes/conf.py
@@ -0,0 +1,20 @@
+extensions = ["myst_parser"]
+language = "en"
+exclude_patterns = ["_build"]
+myst_disable_syntax = ["emphasis"]
+myst_dmath_allow_space = False
+myst_dmath_double_inline = True
+mathjax_config = {}
+myst_enable_extensions = [
+ "dollarmath",
+ "amsmath",
+ "deflist",
+ "colon_fence",
+ "linkify",
+ "tasklist",
+]
+myst_number_code_blocks = ["typescript"]
+myst_html_meta = {
+ "description lang=en": "meta description",
+ "property=og:locale": "en_US",
+}
diff --git a/tests/test_sphinx/sourcedirs/extended_syntaxes/fun-fish.png b/tests/test_sphinx/sourcedirs/extended_syntaxes/fun-fish.png
new file mode 100644
index 0000000..c9a4997
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/extended_syntaxes/fun-fish.png
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md b/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md
new file mode 100644
index 0000000..e9fcea9
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/extended_syntaxes/index.md
@@ -0,0 +1,67 @@
+# Test
+
+*disabled*
+
+$a=1$
+
+$$x=5$$
+
+$$x=5$$ (2)
+
+$ a=1 $
+
+a $$c=3$$ b
+
+\begin{equation}
+b=2
+\end{equation}
+
+```{math}
+c=3
+
+d=4
+```
+
+Term **1**
+
+: Definition *1*
+
+ second paragraph
+
+Term 2
+ ~ Definition 2a
+ ~ Definition 2b
+
+Term 3
+ : code block
+
+ : > quote
+
+ : other
+
+:::{figure-md} target
+:class: other
+
+![fun-fish](fun-fish.png)
+
+This is a caption in **Markdown**
+:::
+
+:::{figure-md} other-target
+:class: other
+
+<img src="fun-fish.png" alt="fishy" class="bg-primary mb-1" width="200px">
+
+This is a caption in **Markdown**
+:::
+
+linkify URL: www.example.com
+
+- [ ] hallo
+- [x] there
+
+Numbered code block:
+
+```typescript
+type Result = "pass" | "fail"
+```
diff --git a/tests/test_sphinx/sourcedirs/fieldlist/conf.py b/tests/test_sphinx/sourcedirs/fieldlist/conf.py
new file mode 100644
index 0000000..1e3d992
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/fieldlist/conf.py
@@ -0,0 +1,4 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+
+myst_enable_extensions = ["fieldlist"]
diff --git a/tests/test_sphinx/sourcedirs/fieldlist/index.md b/tests/test_sphinx/sourcedirs/fieldlist/index.md
new file mode 100644
index 0000000..2ed4856
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/fieldlist/index.md
@@ -0,0 +1,19 @@
+:orphan:
+
+# Test
+
+:field:
+
+:*field*: content
+
+```{py:function} send_message(sender, priority)
+
+Send a message to a recipient
+
+:param str sender: The person sending the message
+:param priority: The priority of the message, can be a number 1-5
+:type priority: int
+:return: the message id
+:rtype: int
+:raises ValueError: if the message_body exceeds 160 characters
+```
diff --git a/tests/test_sphinx/sourcedirs/footnotes/conf.py b/tests/test_sphinx/sourcedirs/footnotes/conf.py
new file mode 100644
index 0000000..e1c5009
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/footnotes/conf.py
@@ -0,0 +1,2 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
diff --git a/tests/test_sphinx/sourcedirs/footnotes/footnote_md.md b/tests/test_sphinx/sourcedirs/footnotes/footnote_md.md
new file mode 100644
index 0000000..ca46d0e
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/footnotes/footnote_md.md
@@ -0,0 +1,29 @@
+# Footnotes with Markdown
+
+[^c]
+
+```{note}
+[^d]
+```
+
+[^a]
+
+[^a]: some footnote *text*
+
+[^b]: a footnote before its reference
+
+[^b]
+
+[^c]: a footnote referenced first
+
+[^d]: a footnote referenced in a directive
+
+[^123] [^123]
+
+[^123]: multiple references footnote
+
+[^x]: an unreferenced footnote
+
+[^e]
+
+> - [^e]: footnote definition in a block element
diff --git a/tests/test_sphinx/sourcedirs/footnotes/footnote_rst.rst b/tests/test_sphinx/sourcedirs/footnotes/footnote_rst.rst
new file mode 100644
index 0000000..219f98a
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/footnotes/footnote_rst.rst
@@ -0,0 +1,26 @@
+Footnotes with rST
+------------------
+
+[#c]_
+
+.. note::
+
+ [#d]_
+
+[#a]_
+
+.. [#a] some footnote *text*
+
+.. [#b] a footnote before its reference
+
+[#b]_
+
+.. [#c] a footnote referenced first
+
+.. [#d] a footnote referenced in a directive
+
+[#123]_ [#123]_
+
+.. [#123] multiple references footnote
+
+.. [#x] an unreferenced footnote
diff --git a/tests/test_sphinx/sourcedirs/footnotes/index.md b/tests/test_sphinx/sourcedirs/footnotes/index.md
new file mode 100644
index 0000000..4e4bc08
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/footnotes/index.md
@@ -0,0 +1,4 @@
+```{toctree}
+footnote_rst
+footnote_md
+```
diff --git a/tests/test_sphinx/sourcedirs/gettext/conf.py b/tests/test_sphinx/sourcedirs/gettext/conf.py
new file mode 100644
index 0000000..0dff9fe
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/gettext/conf.py
@@ -0,0 +1,4 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+copyright = "2020, Executable Book Project"
+myst_enable_extensions = ["deflist"]
diff --git a/tests/test_sphinx/sourcedirs/gettext/fr/LC_MESSAGES/index.po b/tests/test_sphinx/sourcedirs/gettext/fr/LC_MESSAGES/index.po
new file mode 100644
index 0000000..43e5320
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/gettext/fr/LC_MESSAGES/index.po
@@ -0,0 +1,127 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) XXXX, Executable Book Project
+# This file is distributed under the same license as the Python package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Python \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2000-01-01 00:00+0000\n"
+"PO-Revision-Date: 2000-01-01 00:00+0000\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "**bold** text 1"
+msgstr "texte 1 en **gras**"
+
+msgid "**bold** text 2"
+msgstr "texte 2 en **gras**"
+
+msgid "**bold** text 3"
+msgstr "texte 3 en **gras**"
+
+msgid "**bold** text 4"
+msgstr "texte 4 en **gras**"
+
+msgid "**bold** text 5"
+msgstr "texte 5 en **gras**"
+
+msgid "**bold** text 6"
+msgstr "texte 6 en **gras**"
+
+msgid "**bold** text 7"
+msgstr "texte 7 en **gras**"
+
+msgid "**bold** text 8"
+msgstr "texte 8 en **gras**"
+
+msgid "**bold** text 9"
+msgstr "texte 9 en **gras**"
+
+msgid "**bold** text 10"
+msgstr "texte 10 en **gras**"
+
+msgid "<div markdown=1>\n"
+""
+msgstr "<span markdown=1>\n"
+""
+
+msgid "**bold** text 11"
+msgstr "texte 11 en **gras**"
+
+msgid "Extra ```backticks```"
+msgstr "\"```Backtick```\" supplémentaire"
+
+msgid "</div>\n"
+""
+msgstr "</span>\n"
+""
+
+msgid "**additional** text 12\n"
+""
+msgstr "texte 12 **supplémentaire**\n"
+""
+
+msgid "**additional** text 13\n"
+""
+msgstr "texte 13 **supplémentaire**\n"
+""
+
+msgid "{\n"
+" \"additional\": \"text 14\"\n"
+"}\n"
+""
+msgstr "{\n"
+" \"supplémentaire\": \"texte 14\"\n"
+"}\n"
+""
+
+msgid "<h3>**additional** text 15</h3>\n"
+""
+msgstr "<h3>texte 15 **supplémentaire**</h3>\n"
+""
+
+msgid ">>> print('doctest block')\n"
+"doctest block\n"
+""
+msgstr ">>> print('bloque doctest')\n"
+"'bloque doctest\n"
+""
+
+msgid "<iframe src=\"http://sphinx-doc.org\"></iframe>"
+msgstr "<iframe src=\"http://sphinx-doc.org/fr/master/\"></iframe>"
+
+msgid "![Fun Fish 1](fun-fish.png)"
+msgstr "![Poisson amusant 1](poisson-amusant.png)"
+
+msgid "Fun Fish 1"
+msgstr "Poisson amusant 1"
+
+msgid ".. image:: fun-fish.png"
+msgstr ".. image:: poisson-amusant.png"
+
+msgid "Fun Fish 2"
+msgstr "Poisson amusant 2"
+
+msgid "Fun Fish 3"
+msgstr "Poisson amusant 3"
+
+msgid ".. image:: fun-fish.png\n"
+" :alt: Fun Fish 1"
+msgstr ".. image:: poisson-amusant.png\n"
+" :alt: Poisson amusant 1"
+
+msgid ".. image:: fun-fish.png\n"
+" :alt: Fun Fish 2"
+msgstr ".. image:: poisson-amusant.png\n"
+" :alt: Poisson amusant 2"
+
+msgid ".. image:: fun-fish.png\n"
+" :alt: Fun Fish 3"
+msgstr ".. image:: poisson-amusant.png\n"
+" :alt: Poisson amusant 3"
diff --git a/tests/test_sphinx/sourcedirs/gettext/fun-fish.png b/tests/test_sphinx/sourcedirs/gettext/fun-fish.png
new file mode 100644
index 0000000..c9a4997
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/gettext/fun-fish.png
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/gettext/index.md b/tests/test_sphinx/sourcedirs/gettext/index.md
new file mode 100644
index 0000000..a721944
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/gettext/index.md
@@ -0,0 +1,63 @@
+# **bold** text 1
+
+**bold** text 2
+
+> **bold** text 3
+
+```{eval-rst}
+.. note::
+
+ **bold** text 4
+```
+
+* **bold** text 5
+
+1. **bold** text 6
+
+**bold** text 7
+: **bold** text 8
+
+| **bold** text 9 |
+| ---------------- |
+| **bold** text 10 |
+
+<div markdown=1>
+
+**bold** text 11
+
+Extra ```backticks```
+
+</div>
+
+ **additional** text 12
+
+```
+**additional** text 13
+```
+
+```json
+{
+ "additional": "text 14"
+}
+```
+
+<h3>**additional** text 15</h3>
+
+```python
+>>> print('doctest block')
+doctest block
+```
+
+```{raw} html
+<iframe src="http://sphinx-doc.org"></iframe>
+```
+
+![Fun Fish 1](fun-fish.png)
+
+```{image} fun-fish.png
+:alt: Fun Fish 2
+```
+
+```{figure} fun-fish.png
+:alt: Fun Fish 3
+```
diff --git a/tests/test_sphinx/sourcedirs/gettext/poisson-amusant.png b/tests/test_sphinx/sourcedirs/gettext/poisson-amusant.png
new file mode 100644
index 0000000..c9a4997
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/gettext/poisson-amusant.png
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/heading_slug_func/conf.py b/tests/test_sphinx/sourcedirs/heading_slug_func/conf.py
new file mode 100644
index 0000000..f6612b4
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/heading_slug_func/conf.py
@@ -0,0 +1,6 @@
+from docutils.nodes import make_id
+
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+myst_heading_anchors = 2
+myst_heading_slug_func = make_id
diff --git a/tests/test_sphinx/sourcedirs/heading_slug_func/index.md b/tests/test_sphinx/sourcedirs/heading_slug_func/index.md
new file mode 100644
index 0000000..ec11ada
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/heading_slug_func/index.md
@@ -0,0 +1,3 @@
+# Hyphen - 1
+
+## Dot 1.1
diff --git a/tests/test_sphinx/sourcedirs/include_from_rst/conf.py b/tests/test_sphinx/sourcedirs/include_from_rst/conf.py
new file mode 100644
index 0000000..a743e3c
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/include_from_rst/conf.py
@@ -0,0 +1,2 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build", "include.md"]
diff --git a/tests/test_sphinx/sourcedirs/include_from_rst/include.md b/tests/test_sphinx/sourcedirs/include_from_rst/include.md
new file mode 100644
index 0000000..20f34b9
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/include_from_rst/include.md
@@ -0,0 +1,5 @@
+# Markdown
+
+[target]
+
+[target]: http://example.com/
diff --git a/tests/test_sphinx/sourcedirs/include_from_rst/index.rst b/tests/test_sphinx/sourcedirs/include_from_rst/index.rst
new file mode 100644
index 0000000..0bb3177
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/include_from_rst/index.rst
@@ -0,0 +1,5 @@
+Title
+=====
+
+.. include:: include.md
+ :parser: myst_parser.sphinx_
diff --git a/tests/test_sphinx/sourcedirs/includes/conf.py b/tests/test_sphinx/sourcedirs/includes/conf.py
new file mode 100644
index 0000000..4cf4e93
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/conf.py
@@ -0,0 +1,2 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build", "*.inc.md", "**/*.inc.md"]
diff --git a/tests/test_sphinx/sourcedirs/includes/example1.jpg b/tests/test_sphinx/sourcedirs/includes/example1.jpg
new file mode 100644
index 0000000..1c1e83e
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/example1.jpg
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/includes/include1.inc.md b/tests/test_sphinx/sourcedirs/includes/include1.inc.md
new file mode 100644
index 0000000..3eea6ac
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/include1.inc.md
@@ -0,0 +1,12 @@
+---
+orphan: true
+---
+(inc_header)=
+## A Sub-Heading in Include
+
+Some text with *syntax*
+
+```{include} subfolder/include2.inc.md
+:relative-images:
+:relative-docs: ..
+```
diff --git a/tests/test_sphinx/sourcedirs/includes/include_code.py b/tests/test_sphinx/sourcedirs/includes/include_code.py
new file mode 100644
index 0000000..3158954
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/include_code.py
@@ -0,0 +1,2 @@
+def a_func(param):
+ print(param)
diff --git a/tests/test_sphinx/sourcedirs/includes/include_literal.txt b/tests/test_sphinx/sourcedirs/includes/include_literal.txt
new file mode 100644
index 0000000..5bb9418
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/include_literal.txt
@@ -0,0 +1,6 @@
+This should be *literal*
+
+Lots
+of
+lines
+so we can select some
diff --git a/tests/test_sphinx/sourcedirs/includes/index.md b/tests/test_sphinx/sourcedirs/includes/index.md
new file mode 100644
index 0000000..dc904e3
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/index.md
@@ -0,0 +1,31 @@
+# Main Title
+
+```{include} include1.inc.md
+```
+
+{ref}`inc_header`
+
+```{include} include_code.py
+:code: python
+```
+
+```{include} include_code.py
+:code: python
+:number-lines: 0
+```
+
+```{include} include_literal.txt
+:literal:
+```
+
+```{include} include_literal.txt
+:literal:
+:name: literal_ref
+:start-line: 2
+:end-before: lines
+:number-lines: 0
+```
+
+### A Sub-sub-Heading
+
+some more text
diff --git a/tests/test_sphinx/sourcedirs/includes/subfolder/example2.jpg b/tests/test_sphinx/sourcedirs/includes/subfolder/example2.jpg
new file mode 100644
index 0000000..1c1e83e
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/subfolder/example2.jpg
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/includes/subfolder/include2.inc.md b/tests/test_sphinx/sourcedirs/includes/subfolder/include2.inc.md
new file mode 100644
index 0000000..a6eebcf
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/includes/subfolder/include2.inc.md
@@ -0,0 +1,21 @@
+## A Sub-Heading in Nested Include
+
+Some other text with **syntax**
+
+This relative path will refer to the importing file:
+
+```{figure} example1.jpg
+Caption
+```
+
+This absolute path will refer to the project root (where the `conf.py` is):
+
+```{figure} /subfolder/example2.jpg
+Caption
+```
+
+![alt](example2.jpg)
+
+![alt](https://example.com)
+
+[text](../index.md)
diff --git a/tests/test_sphinx/sourcedirs/mathjax/conf.py b/tests/test_sphinx/sourcedirs/mathjax/conf.py
new file mode 100644
index 0000000..b2a70c8
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/mathjax/conf.py
@@ -0,0 +1,12 @@
+import sphinx
+
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+
+if sphinx.version_info[0] <= 3:
+ mathjax_config = {"tex2jax": {"processClass": "other"}}
+else:
+ mathjax3_config = {"options": {"processHtmlClass": "other"}}
+
+# this should remove the warning
+# suppress_warnings = ["myst.mathjax"]
diff --git a/tests/test_sphinx/sourcedirs/mathjax/index.md b/tests/test_sphinx/sourcedirs/mathjax/index.md
new file mode 100644
index 0000000..0226511
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/mathjax/index.md
@@ -0,0 +1,5 @@
+# Test
+
+```{math}
+a = 1
+```
diff --git a/tests/test_sphinx/sourcedirs/references/conf.py b/tests/test_sphinx/sourcedirs/references/conf.py
new file mode 100644
index 0000000..099510a
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references/conf.py
@@ -0,0 +1,3 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+myst_heading_anchors = 2
diff --git a/tests/test_sphinx/sourcedirs/references/file_link.txt b/tests/test_sphinx/sourcedirs/references/file_link.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references/file_link.txt
diff --git a/tests/test_sphinx/sourcedirs/references/index.md b/tests/test_sphinx/sourcedirs/references/index.md
new file mode 100644
index 0000000..c31bedb
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references/index.md
@@ -0,0 +1,54 @@
+(title)=
+
+# Title with **nested** $a=1$
+
+[](https://example.com)
+
+[plain text](https://example.com)
+
+[nested *syntax*](https://example.com)
+
+[](title)
+
+[plain text](title)
+
+[nested *syntax*](title)
+
+[](index.md)
+
+[plain text](index.md)
+
+[nested *syntax*](index.md)
+
+[download **link**](file_link.txt)
+
+[](subfolder/file_link2.txt)
+
+```{eval-rst}
+.. _insidecodeblock:
+
+I am inside the eval-rst fence
+
+Referencing the :ref:`title`
+
+Still inside the codeblock insidecodeblock_
+```
+
+I am outside the [fence](insidecodeblock)
+
+## Title *anchors*
+
+```{toctree}
+other.md
+subfolder/other2.md
+```
+
+[](#title-anchors)
+
+[](./#title-anchors)
+
+[](./other.md#title-anchors)
+
+[](other.md#title-anchors)
+
+[](subfolder/other2.md#title-anchors)
diff --git a/tests/test_sphinx/sourcedirs/references/other.md b/tests/test_sphinx/sourcedirs/references/other.md
new file mode 100644
index 0000000..9dfb9be
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references/other.md
@@ -0,0 +1,3 @@
+# Title *anchors*
+
+a
diff --git a/tests/test_sphinx/sourcedirs/references/subfolder/file_link2.txt b/tests/test_sphinx/sourcedirs/references/subfolder/file_link2.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references/subfolder/file_link2.txt
diff --git a/tests/test_sphinx/sourcedirs/references/subfolder/other2.md b/tests/test_sphinx/sourcedirs/references/subfolder/other2.md
new file mode 100644
index 0000000..6687282
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references/subfolder/other2.md
@@ -0,0 +1,3 @@
+# Title *anchors*
+
+[](../index.md#title-anchors)
diff --git a/tests/test_sphinx/sourcedirs/references_singlehtml/conf.py b/tests/test_sphinx/sourcedirs/references_singlehtml/conf.py
new file mode 100644
index 0000000..0d760fc
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references_singlehtml/conf.py
@@ -0,0 +1,6 @@
+extensions = [
+ "myst_parser",
+]
+master_doc = "index"
+exclude_patterns = ["_build"]
+myst_heading_anchors = 2
diff --git a/tests/test_sphinx/sourcedirs/references_singlehtml/index.md b/tests/test_sphinx/sourcedirs/references_singlehtml/index.md
new file mode 100644
index 0000000..d38d875
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references_singlehtml/index.md
@@ -0,0 +1,15 @@
+# Title
+
+```{toctree}
+other/index
+```
+
+{doc}`other/other`
+
+{any}`other/other`
+
+[](./other/other.md)
+
+[](#title)
+
+[](./other/other.md#other-title)
diff --git a/tests/test_sphinx/sourcedirs/references_singlehtml/other/index.md b/tests/test_sphinx/sourcedirs/references_singlehtml/other/index.md
new file mode 100644
index 0000000..3a4fb08
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references_singlehtml/other/index.md
@@ -0,0 +1,6 @@
+# Other Index
+
+```{toctree}
+other
+other2
+```
diff --git a/tests/test_sphinx/sourcedirs/references_singlehtml/other/other.md b/tests/test_sphinx/sourcedirs/references_singlehtml/other/other.md
new file mode 100644
index 0000000..a833673
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references_singlehtml/other/other.md
@@ -0,0 +1,9 @@
+# Other Title
+
+{doc}`other2`
+
+{any}`other2`
+
+[](./other2.md)
+
+[](../index.md#title)
diff --git a/tests/test_sphinx/sourcedirs/references_singlehtml/other/other2.md b/tests/test_sphinx/sourcedirs/references_singlehtml/other/other2.md
new file mode 100644
index 0000000..f97239e
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/references_singlehtml/other/other2.md
@@ -0,0 +1 @@
+# Other 2 Title
diff --git a/tests/test_sphinx/sourcedirs/substitutions/conf.py b/tests/test_sphinx/sourcedirs/substitutions/conf.py
new file mode 100644
index 0000000..57c5d68
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/substitutions/conf.py
@@ -0,0 +1,4 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
+myst_enable_extensions = ["substitution"]
+myst_substitutions = {"conf": "This was from the conf", "override": "From conf"}
diff --git a/tests/test_sphinx/sourcedirs/substitutions/fun-fish.png b/tests/test_sphinx/sourcedirs/substitutions/fun-fish.png
new file mode 100644
index 0000000..c9a4997
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/substitutions/fun-fish.png
Binary files differ
diff --git a/tests/test_sphinx/sourcedirs/substitutions/index.md b/tests/test_sphinx/sourcedirs/substitutions/index.md
new file mode 100644
index 0000000..e1dd672
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/substitutions/index.md
@@ -0,0 +1,56 @@
+---
+myst:
+ substitutions:
+ text: "- text"
+ text_with_nest: >
+ output
+ with *Markdown*
+ {{ nested }}
+ nested: nested substitution
+ admonition: |
+ prefix
+
+ ```{note}
+ A note {{ nested }}
+ ```
+ inline_admonition: |
+ ```{note}
+ Inline note
+ ```
+ override: Overridden by front matter
+
+---
+
+{{ text_with_nest }}
+
+{{ admonition }}
+
+a {{ text }} b
+
+c {{ text_with_nest }} d
+
+e {{ inline_admonition }} f
+
+{{ conf }}
+
+{{ override }}
+
+This will process the substitution
+
+```{parsed-literal}
+{{ text_with_nest }}
+```
+
+This will not process the substitution
+
+```python
+{{ text_with_nest }}
+```
+
+Using env and filters:
+
+{{ env.docname | upper }}
+
+```{toctree}
+other.md
+```
diff --git a/tests/test_sphinx/sourcedirs/substitutions/other.md b/tests/test_sphinx/sourcedirs/substitutions/other.md
new file mode 100644
index 0000000..8681af4
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/substitutions/other.md
@@ -0,0 +1,3 @@
+# Other
+
+{{ override }}
diff --git a/tests/test_sphinx/sourcedirs/texi_table/conf.py b/tests/test_sphinx/sourcedirs/texi_table/conf.py
new file mode 100644
index 0000000..e1c5009
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/texi_table/conf.py
@@ -0,0 +1,2 @@
+extensions = ["myst_parser"]
+exclude_patterns = ["_build"]
diff --git a/tests/test_sphinx/sourcedirs/texi_table/index.md b/tests/test_sphinx/sourcedirs/texi_table/index.md
new file mode 100644
index 0000000..9face4b
--- /dev/null
+++ b/tests/test_sphinx/sourcedirs/texi_table/index.md
@@ -0,0 +1,3 @@
+| foo | bar |
+| --- | --- |
+| baz | bim |
diff --git a/tests/test_sphinx/test_sphinx_builds.py b/tests/test_sphinx/test_sphinx_builds.py
new file mode 100644
index 0000000..beba484
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds.py
@@ -0,0 +1,565 @@
+"""Uses sphinx's pytest fixture to run builds.
+
+see conftest.py for fixture usage
+
+NOTE: sphinx 3 & 4 regress against different output files,
+the major difference being sphinx 4 uses docutils 0.17,
+which uses semantic HTML tags
+(e.g. converting `<div class="section">` to `<section>`)
+"""
+import os
+import re
+
+import pytest
+import sphinx
+from docutils import VersionInfo, __version_info__
+
+SOURCE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "sourcedirs"))
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "basic"),
+ freshenv=True,
+ confoverrides={"myst_enable_extensions": ["dollarmath"]},
+)
+def test_basic(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """basic test."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(
+ app,
+ docname="content",
+ regress=True,
+ )
+ finally:
+ get_sphinx_app_doctree(
+ app,
+ docname="content",
+ resolve=True,
+ regress=True,
+ )
+ get_sphinx_app_output(
+ app,
+ filename="content.html",
+ regress_html=True,
+ regress_ext=f".sphinx{sphinx.version_info[0]}.html",
+ )
+
+ assert app.env.metadata["content"] == {
+ "author": "Chris Sewell",
+ "authors": ["Chris Sewell", "Chris Hodgraf"],
+ "organization": "EPFL",
+ "address": "1 Cedar Park Close\nThundersley\nEssex\n",
+ "contact": "https://example.com",
+ "version": "1.0",
+ "revision": "1.1",
+ "status": "good",
+ "date": "2/12/1985",
+ "copyright": "MIT",
+ "other": "Something else",
+ "other_dict": '{"key": "value"}',
+ "wordcount": {"minutes": 0, "words": 57},
+ }
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "references"),
+ freshenv=True,
+ confoverrides={"myst_enable_extensions": ["dollarmath"]},
+)
+def test_references(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """Test reference resolution."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(app, docname="index", regress=True)
+ finally:
+ try:
+ get_sphinx_app_doctree(app, docname="index", resolve=True, regress=True)
+ finally:
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ replace={"Permalink to this headline": "Permalink to this heading"},
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="singlehtml",
+ srcdir=os.path.join(SOURCE_DIR, "references_singlehtml"),
+ freshenv=True,
+ confoverrides={"nitpicky": True},
+)
+def test_references_singlehtml(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """Test reference resolution for singlehtml builds."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ # try:
+ # get_sphinx_app_doctree(app, docname="index", regress=True)
+ # finally:
+ # get_sphinx_app_doctree(app, docname="index", resolve=True, regress=True)
+
+ try:
+ get_sphinx_app_doctree(
+ app,
+ docname="other/other",
+ regress=True,
+ replace={"other\\other.md": "other/other.md"},
+ )
+ finally:
+ get_sphinx_app_doctree(
+ app,
+ docname="other/other",
+ resolve=True,
+ regress=True,
+ replace={"other\\other.md": "other/other.md"},
+ )
+
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ buildername="singlehtml",
+ regress_html=True,
+ replace={"Permalink to this headline": "Permalink to this heading"},
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "heading_slug_func"),
+ freshenv=True,
+)
+def test_heading_slug_func(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """Test heading_slug_func configuration."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(app, docname="index", regress=True)
+ finally:
+ get_sphinx_app_doctree(app, docname="index", resolve=True, regress=True)
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ replace={"Permalink to this headline": "Permalink to this heading"},
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "extended_syntaxes"),
+ freshenv=True,
+)
+def test_extended_syntaxes(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+ monkeypatch,
+):
+ """test setting addition configuration values."""
+ from myst_parser.mdit_to_docutils.sphinx_ import SphinxRenderer
+
+ monkeypatch.setattr(SphinxRenderer, "_random_label", lambda self: "mock-uuid")
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(
+ app,
+ docname="index",
+ regress=True,
+ )
+ finally:
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ replace={"Permalink to this headline": "Permalink to this heading"},
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="html", srcdir=os.path.join(SOURCE_DIR, "includes"), freshenv=True
+)
+def test_includes(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """Test of include directive."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(
+ app,
+ docname="index",
+ regress=True,
+ # fix for Windows CI
+ replace={
+ r"subfolder\example2.jpg": "subfolder/example2.jpg",
+ r"subfolder\\example2.jpg": "subfolder/example2.jpg",
+ r"subfolder\\\\example2.jpg": "subfolder/example2.jpg",
+ },
+ )
+ finally:
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ replace={
+ "Permalink to this headline": "Permalink to this heading",
+ r"'subfolder\\example2'": "'subfolder/example2'",
+ r'uri="subfolder\\example2"': 'uri="subfolder/example2"',
+ "_images/example21.jpg": "_images/example2.jpg",
+ },
+ )
+
+
+@pytest.mark.skipif(
+ __version_info__ < VersionInfo(0, 17, 0, "final", 0, True),
+ reason="parser option added in docutils 0.17",
+)
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "include_from_rst"),
+ freshenv=True,
+)
+def test_include_from_rst(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+):
+ """Test of include directive inside RST file."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ get_sphinx_app_doctree(
+ app,
+ docname="index",
+ regress=True,
+ regress_ext=".xml",
+ )
+
+
+@pytest.mark.skipif(
+ __version_info__ < VersionInfo(0, 19, 0, "final", 0, True),
+ reason="Footnote HTML changed in docutils 0.19",
+)
+@pytest.mark.sphinx(
+ buildername="html", srcdir=os.path.join(SOURCE_DIR, "footnotes"), freshenv=True
+)
+def test_footnotes(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """Test of include directive."""
+ app.build()
+
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(app, docname="footnote_md", regress=True)
+ finally:
+ get_sphinx_app_output(
+ app,
+ filename="footnote_md.html",
+ regress_html=True,
+ regress_ext=f".sphinx{sphinx.version_info[0]}.html",
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "commonmark_only"),
+ freshenv=True,
+)
+def test_commonmark_only(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """test setting addition configuration values."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert "lexer name '{note}'" in warnings
+
+ try:
+ get_sphinx_app_doctree(app, docname="index", regress=True)
+ finally:
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ replace={"Permalink to this headline": "Permalink to this heading"},
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "substitutions"),
+ freshenv=True,
+)
+def test_substitutions(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+ file_regression,
+):
+ """test setting addition configuration values."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(app, docname="index", regress=True)
+ file_regression.check(
+ get_sphinx_app_doctree(app, docname="other").pformat(),
+ extension=".other.xml",
+ )
+ finally:
+ get_sphinx_app_output(app, filename="index.html", regress_html=True)
+
+
+@pytest.mark.sphinx(
+ buildername="gettext", srcdir=os.path.join(SOURCE_DIR, "gettext"), freshenv=True
+)
+def test_gettext(
+ app,
+ status,
+ warning,
+ get_sphinx_app_output,
+ file_regression,
+):
+ """Test gettext message extraction."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ output = get_sphinx_app_output(app, filename="index.pot", buildername="gettext")
+ output = re.sub(r"POT-Creation-Date: [0-9: +-]+", "POT-Creation-Date: ", output)
+ output = re.sub(r"Copyright \(C\) [0-9]{4}", "Copyright (C) XXXX", output)
+
+ file_regression.check(output, extension=".pot")
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "gettext"),
+ freshenv=True,
+ confoverrides={"language": "fr", "gettext_compact": False, "locale_dirs": ["."]},
+)
+def test_gettext_html(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """Test gettext message extraction."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(
+ app,
+ docname="index",
+ regress=True,
+ )
+ finally:
+ get_sphinx_app_doctree(
+ app,
+ docname="index",
+ resolve=True,
+ regress=True,
+ )
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ regress_ext=f".sphinx{sphinx.version_info[0]}.html",
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="gettext",
+ srcdir=os.path.join(SOURCE_DIR, "gettext"),
+ freshenv=True,
+ confoverrides={
+ "gettext_additional_targets": [
+ "index",
+ "literal-block",
+ "doctest-block",
+ "raw",
+ "image",
+ ],
+ },
+)
+def test_gettext_additional_targets(
+ app,
+ status,
+ warning,
+ get_sphinx_app_output,
+ file_regression,
+):
+ """Test gettext message extraction."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ output = get_sphinx_app_output(app, filename="index.pot", buildername="gettext")
+ output = re.sub(r"POT-Creation-Date: [0-9: +-]+", "POT-Creation-Date: ", output)
+ output = re.sub(r"Copyright \(C\) [0-9]{4}", "Copyright (C) XXXX", output)
+
+ file_regression.check(output, extension=".pot")
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "mathjax"),
+ freshenv=True,
+ confoverrides={"myst_enable_extensions": ["dollarmath"]},
+)
+def test_mathjax_warning(
+ app,
+ status,
+ warning,
+):
+ """Test mathjax config override warning."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert (
+ "overridden by myst-parser: 'other' -> 'tex2jax_process|mathjax_process|math|output_area'"
+ in warnings
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="html",
+ srcdir=os.path.join(SOURCE_DIR, "fieldlist"),
+ freshenv=True,
+)
+def test_fieldlist_extension(
+ app,
+ status,
+ warning,
+ get_sphinx_app_doctree,
+ get_sphinx_app_output,
+):
+ """test setting addition configuration values."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
+
+ try:
+ get_sphinx_app_doctree(
+ app,
+ docname="index",
+ regress=True,
+ # changed in:
+ # https://www.sphinx-doc.org/en/master/changes.html#release-4-4-0-released-jan-17-2022
+ replace={
+ (
+ '<literal_strong py:class="True" '
+ 'py:module="True" refspecific="True">'
+ ): "<literal_strong>"
+ },
+ )
+ finally:
+ get_sphinx_app_output(
+ app,
+ filename="index.html",
+ regress_html=True,
+ regress_ext=f".sphinx{sphinx.version_info[0]}.html",
+ )
+
+
+@pytest.mark.sphinx(
+ buildername="texinfo",
+ srcdir=os.path.join(SOURCE_DIR, "texi_table"),
+ freshenv=True,
+)
+def test_texinfo_table(
+ app,
+ status,
+ warning,
+):
+ """Test that tables can be built with the Texinfo builder."""
+ app.build()
+ assert "build succeeded" in status.getvalue() # Build succeeded
+ warnings = warning.getvalue().strip()
+ assert warnings == ""
diff --git a/tests/test_sphinx/test_sphinx_builds/test_basic.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_basic.resolved.xml
new file mode 100644
index 0000000..8a12765
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_basic.resolved.xml
@@ -0,0 +1,154 @@
+<document source="content.md">
+ <topic classes="dedication">
+ <title>
+ Dedication
+ <paragraph>
+ To my
+ <emphasis>
+ homies
+
+ <topic classes="abstract">
+ <title>
+ Abstract
+ <paragraph>
+ Something something
+ <strong>
+ dark
+ side
+ <target refid="target">
+ <section classes="tex2jax_ignore mathjax_ignore" ids="header target" names="header target">
+ <title>
+ Header
+ <comment xml:space="preserve">
+ comment
+ <note>
+ <paragraph>
+ abcd
+ <emphasis>
+ abc
+
+ <reference refuri="https://www.google.com">
+ google
+ <warning>
+ <paragraph>
+ xyz
+ <admonition classes="admonition-title-with-link-target2">
+ <title>
+ Title with
+ <reference internal="True" refid="target2">
+ <inline classes="std std-ref">
+ link
+ <paragraph>
+ Content
+ <target refid="target2">
+ <figure ids="id1 target2" names="target2">
+ <reference refuri="https://www.google.com">
+ <image candidates="{'*': 'example.jpg'}" height="40px" uri="example.jpg">
+ <caption>
+ Caption
+ <paragraph>
+ <image alt="alternative text" candidates="{'*': 'example.jpg'}" uri="example.jpg">
+ <paragraph>
+ <reference refuri="https://www.google.com">
+ https://www.google.com
+ <paragraph>
+ <strong>
+ <literal classes="code">
+ a=1{`}
+ <paragraph>
+ <math>
+ sdfds
+ <paragraph>
+ <strong>
+ <math>
+ a=1
+ <math_block nowrap="False" number="True" xml:space="preserve">
+ b=2
+ <target refid="equation-eq-label">
+ <math_block docname="content" ids="equation-eq-label" label="eq:label" nowrap="False" number="1" xml:space="preserve">
+ c=2
+ <paragraph>
+ <reference internal="True" refid="equation-eq-label">
+ (1)
+ <paragraph>
+ <literal>
+ a=1{`}
+ <table classes="colwidths-auto">
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ a
+ <entry classes="text-right">
+ <paragraph>
+ b
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ <emphasis>
+ a
+ <entry classes="text-right">
+ <paragraph>
+ 2
+ <row>
+ <entry>
+ <paragraph>
+ <reference refuri="https://google.com">
+ link-a
+ <entry classes="text-right">
+ <paragraph>
+ <reference refuri="https://python.org">
+ link-b
+ <paragraph>
+ this
+
+ is
+
+ a
+
+ paragraph
+ <comment xml:space="preserve">
+ a comment 2
+ <paragraph>
+ this is a second paragraph
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ a list
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ a sub list
+ <comment xml:space="preserve">
+ a comment 3
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ new list?
+ <paragraph>
+ <reference internal="True" refid="target">
+ <inline classes="std std-ref">
+ Header
+
+ <reference internal="True" refid="target2">
+ <inline classes="std std-ref">
+ Caption
+ <comment classes="block_break" xml:space="preserve">
+ a block break
+ <paragraph>
+ <reference refuri="https://www.google.com" title="a title">
+ name
+ <literal_block language="default" linenos="False" xml:space="preserve">
+ def func(a, b=1):
+ print(a)
+ <paragraph>
+ Special substitution references:
+ <paragraph>
+ 57
+ words |
+ 0
+ min read
diff --git a/tests/test_sphinx/test_sphinx_builds/test_basic.sphinx4.html b/tests/test_sphinx/test_sphinx_builds/test_basic.sphinx4.html
new file mode 100644
index 0000000..743f7a1
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_basic.sphinx4.html
@@ -0,0 +1,252 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <div class="dedication topic">
+ <p class="topic-title">
+ Dedication
+ </p>
+ <p>
+ To my
+ <em>
+ homies
+ </em>
+ </p>
+ </div>
+ <div class="abstract topic">
+ <p class="topic-title">
+ Abstract
+ </p>
+ <p>
+ Something something
+ <strong>
+ dark
+ </strong>
+ side
+ </p>
+ </div>
+ <section class="tex2jax_ignore mathjax_ignore" id="header">
+ <span id="target">
+ </span>
+ <h1>
+ Header
+ <a class="headerlink" href="#header" title="Permalink to this headline">
+ ¶
+ </a>
+ </h1>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ abcd
+ <em>
+ abc
+ </em>
+ <a class="reference external" href="https://www.google.com">
+ google
+ </a>
+ </p>
+ <div class="admonition warning">
+ <p class="admonition-title">
+ Warning
+ </p>
+ <p>
+ xyz
+ </p>
+ </div>
+ </div>
+ <div class="admonition-title-with-link-target2 admonition">
+ <p class="admonition-title">
+ Title with
+ <a class="reference internal" href="#target2">
+ <span class="std std-ref">
+ link
+ </span>
+ </a>
+ </p>
+ <p>
+ Content
+ </p>
+ </div>
+ <figure class="align-default" id="id1">
+ <span id="target2">
+ </span>
+ <a class="reference external image-reference" href="https://www.google.com">
+ <img alt="_images/example.jpg" src="_images/example.jpg" style="height: 40px;"/>
+ </a>
+ <figcaption>
+ <p>
+ <span class="caption-text">
+ Caption
+ </span>
+ <a class="headerlink" href="#id1" title="Permalink to this image">
+ ¶
+ </a>
+ </p>
+ </figcaption>
+ </figure>
+ <p>
+ <img alt="alternative text" src="_images/example.jpg"/>
+ </p>
+ <p>
+ <a class="reference external" href="https://www.google.com">
+ https://www.google.com
+ </a>
+ </p>
+ <p>
+ <strong>
+ <code class="code docutils literal notranslate">
+ <span class="pre">
+ a=1{`}
+ </span>
+ </code>
+ </strong>
+ </p>
+ <p>
+ <span class="math notranslate nohighlight">
+ \(sdfds\)
+ </span>
+ </p>
+ <p>
+ <strong>
+ <span class="math notranslate nohighlight">
+ \(a=1\)
+ </span>
+ </strong>
+ </p>
+ <div class="math notranslate nohighlight">
+ \[b=2\]
+ </div>
+ <div class="math notranslate nohighlight" id="equation-eq-label">
+ <span class="eqno">
+ (1)
+ <a class="headerlink" href="#equation-eq-label" title="Permalink to this equation">
+ ¶
+ </a>
+ </span>
+ \[c=2\]
+ </div>
+ <p>
+ <a class="reference internal" href="#equation-eq-label">
+ (1)
+ </a>
+ </p>
+ <p>
+ <code class="docutils literal notranslate">
+ <span class="pre">
+ a=1{`}
+ </span>
+ </code>
+ </p>
+ <table class="colwidths-auto docutils align-default">
+ <thead>
+ <tr class="row-odd">
+ <th class="head">
+ <p>
+ a
+ </p>
+ </th>
+ <th class="text-right head">
+ <p>
+ b
+ </p>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="row-even">
+ <td>
+ <p>
+ <em>
+ a
+ </em>
+ </p>
+ </td>
+ <td class="text-right">
+ <p>
+ 2
+ </p>
+ </td>
+ </tr>
+ <tr class="row-odd">
+ <td>
+ <p>
+ <a class="reference external" href="https://google.com">
+ link-a
+ </a>
+ </p>
+ </td>
+ <td class="text-right">
+ <p>
+ <a class="reference external" href="https://python.org">
+ link-b
+ </a>
+ </p>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <p>
+ this
+is
+a
+paragraph
+ </p>
+ <p>
+ this is a second paragraph
+ </p>
+ <ul class="simple">
+ <li>
+ <p>
+ a list
+ </p>
+ <ul>
+ <li>
+ <p>
+ a sub list
+ </p>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="simple">
+ <li>
+ <p>
+ new list?
+ </p>
+ </li>
+ </ul>
+ <p>
+ <a class="reference internal" href="#target">
+ <span class="std std-ref">
+ Header
+ </span>
+ </a>
+ <a class="reference internal" href="#target2">
+ <span class="std std-ref">
+ Caption
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference external" href="https://www.google.com">
+ name
+ </a>
+ </p>
+ <div class="highlight-default notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
+ <span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
+</pre>
+ </div>
+ </div>
+ <p>
+ Special substitution references:
+ </p>
+ <p>
+ 57 words | 0 min read
+ </p>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_basic.sphinx5.html b/tests/test_sphinx/test_sphinx_builds/test_basic.sphinx5.html
new file mode 100644
index 0000000..9813b70
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_basic.sphinx5.html
@@ -0,0 +1,252 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <div class="topic dedication" role="doc-dedication">
+ <p class="topic-title">
+ Dedication
+ </p>
+ <p>
+ To my
+ <em>
+ homies
+ </em>
+ </p>
+ </div>
+ <div class="topic abstract" role="doc-abstract">
+ <p class="topic-title">
+ Abstract
+ </p>
+ <p>
+ Something something
+ <strong>
+ dark
+ </strong>
+ side
+ </p>
+ </div>
+ <section class="tex2jax_ignore mathjax_ignore" id="header">
+ <span id="target">
+ </span>
+ <h1>
+ Header
+ <a class="headerlink" href="#header" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ abcd
+ <em>
+ abc
+ </em>
+ <a class="reference external" href="https://www.google.com">
+ google
+ </a>
+ </p>
+ <div class="admonition warning">
+ <p class="admonition-title">
+ Warning
+ </p>
+ <p>
+ xyz
+ </p>
+ </div>
+ </div>
+ <div class="admonition-title-with-link-target2 admonition">
+ <p class="admonition-title">
+ Title with
+ <a class="reference internal" href="#target2">
+ <span class="std std-ref">
+ link
+ </span>
+ </a>
+ </p>
+ <p>
+ Content
+ </p>
+ </div>
+ <figure class="align-default" id="id1">
+ <span id="target2">
+ </span>
+ <a class="reference external image-reference" href="https://www.google.com">
+ <img alt="_images/example.jpg" src="_images/example.jpg" style="height: 40px;"/>
+ </a>
+ <figcaption>
+ <p>
+ <span class="caption-text">
+ Caption
+ </span>
+ <a class="headerlink" href="#id1" title="Permalink to this image">
+ ¶
+ </a>
+ </p>
+ </figcaption>
+ </figure>
+ <p>
+ <img alt="alternative text" src="_images/example.jpg"/>
+ </p>
+ <p>
+ <a class="reference external" href="https://www.google.com">
+ https://www.google.com
+ </a>
+ </p>
+ <p>
+ <strong>
+ <code class="code docutils literal notranslate">
+ <span class="pre">
+ a=1{`}
+ </span>
+ </code>
+ </strong>
+ </p>
+ <p>
+ <span class="math notranslate nohighlight">
+ \(sdfds\)
+ </span>
+ </p>
+ <p>
+ <strong>
+ <span class="math notranslate nohighlight">
+ \(a=1\)
+ </span>
+ </strong>
+ </p>
+ <div class="math notranslate nohighlight">
+ \[b=2\]
+ </div>
+ <div class="math notranslate nohighlight" id="equation-eq-label">
+ <span class="eqno">
+ (1)
+ <a class="headerlink" href="#equation-eq-label" title="Permalink to this equation">
+ ¶
+ </a>
+ </span>
+ \[c=2\]
+ </div>
+ <p>
+ <a class="reference internal" href="#equation-eq-label">
+ (1)
+ </a>
+ </p>
+ <p>
+ <code class="docutils literal notranslate">
+ <span class="pre">
+ a=1{`}
+ </span>
+ </code>
+ </p>
+ <table class="docutils align-default">
+ <thead>
+ <tr class="row-odd">
+ <th class="head">
+ <p>
+ a
+ </p>
+ </th>
+ <th class="head text-right">
+ <p>
+ b
+ </p>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="row-even">
+ <td>
+ <p>
+ <em>
+ a
+ </em>
+ </p>
+ </td>
+ <td class="text-right">
+ <p>
+ 2
+ </p>
+ </td>
+ </tr>
+ <tr class="row-odd">
+ <td>
+ <p>
+ <a class="reference external" href="https://google.com">
+ link-a
+ </a>
+ </p>
+ </td>
+ <td class="text-right">
+ <p>
+ <a class="reference external" href="https://python.org">
+ link-b
+ </a>
+ </p>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <p>
+ this
+is
+a
+paragraph
+ </p>
+ <p>
+ this is a second paragraph
+ </p>
+ <ul class="simple">
+ <li>
+ <p>
+ a list
+ </p>
+ <ul>
+ <li>
+ <p>
+ a sub list
+ </p>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <ul class="simple">
+ <li>
+ <p>
+ new list?
+ </p>
+ </li>
+ </ul>
+ <p>
+ <a class="reference internal" href="#target">
+ <span class="std std-ref">
+ Header
+ </span>
+ </a>
+ <a class="reference internal" href="#target2">
+ <span class="std std-ref">
+ Caption
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference external" href="https://www.google.com">
+ name
+ </a>
+ </p>
+ <div class="highlight-default notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
+ <span class="nb">print</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
+</pre>
+ </div>
+ </div>
+ <p>
+ Special substitution references:
+ </p>
+ <p>
+ 57 words | 0 min read
+ </p>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_basic.xml b/tests/test_sphinx/test_sphinx_builds/test_basic.xml
new file mode 100644
index 0000000..34b0e3c
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_basic.xml
@@ -0,0 +1,155 @@
+<document source="content.md">
+ <topic classes="dedication">
+ <title>
+ Dedication
+ <paragraph>
+ To my
+ <emphasis>
+ homies
+
+ <topic classes="abstract">
+ <title>
+ Abstract
+ <paragraph>
+ Something something
+ <strong>
+ dark
+ side
+ <target refid="target">
+ <section classes="tex2jax_ignore mathjax_ignore" ids="header target" names="header target">
+ <title>
+ Header
+ <comment xml:space="preserve">
+ comment
+ <note>
+ <paragraph>
+ abcd
+ <emphasis>
+ abc
+
+ <reference refuri="https://www.google.com">
+ google
+ <warning>
+ <paragraph>
+ xyz
+ <admonition classes="admonition-title-with-link-target2">
+ <title>
+ Title with
+ <pending_xref refdoc="content" refdomain="True" refexplicit="True" reftarget="target2" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ link
+ <paragraph>
+ Content
+ <target refid="target2">
+ <figure ids="id1 target2" names="target2">
+ <reference refuri="https://www.google.com">
+ <image candidates="{'*': 'example.jpg'}" height="40px" uri="example.jpg">
+ <caption>
+ Caption
+ <paragraph>
+ <image alt="alternative text" candidates="{'*': 'example.jpg'}" uri="example.jpg">
+ <paragraph>
+ <reference refuri="https://www.google.com">
+ https://www.google.com
+ <paragraph>
+ <strong>
+ <literal classes="code">
+ a=1{`}
+ <paragraph>
+ <math>
+ sdfds
+ <paragraph>
+ <strong>
+ <math>
+ a=1
+ <math_block nowrap="False" number="True" xml:space="preserve">
+ b=2
+ <target refid="equation-eq-label">
+ <math_block docname="content" ids="equation-eq-label" label="eq:label" nowrap="False" number="1" xml:space="preserve">
+ c=2
+ <paragraph>
+ <pending_xref refdoc="content" refdomain="math" refexplicit="False" reftarget="eq:label" reftype="eq" refwarn="True">
+ <literal classes="xref eq">
+ eq:label
+ <paragraph>
+ <literal>
+ a=1{`}
+ <table classes="colwidths-auto">
+ <tgroup cols="2">
+ <colspec colwidth="50">
+ <colspec colwidth="50">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ a
+ <entry classes="text-right">
+ <paragraph>
+ b
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ <emphasis>
+ a
+ <entry classes="text-right">
+ <paragraph>
+ 2
+ <row>
+ <entry>
+ <paragraph>
+ <reference refuri="https://google.com">
+ link-a
+ <entry classes="text-right">
+ <paragraph>
+ <reference refuri="https://python.org">
+ link-b
+ <paragraph>
+ this
+
+ is
+
+ a
+
+ paragraph
+ <comment xml:space="preserve">
+ a comment 2
+ <paragraph>
+ this is a second paragraph
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ a list
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ a sub list
+ <comment xml:space="preserve">
+ a comment 3
+ <bullet_list bullet="-">
+ <list_item>
+ <paragraph>
+ new list?
+ <paragraph>
+ <pending_xref refdoc="content" refdomain="std" refexplicit="False" reftarget="target" reftype="ref" refwarn="True">
+ <inline classes="xref std std-ref">
+ target
+
+ <pending_xref refdoc="content" refdomain="std" refexplicit="False" reftarget="target2" reftype="ref" refwarn="True">
+ <inline classes="xref std std-ref">
+ target2
+ <comment classes="block_break" xml:space="preserve">
+ a block break
+ <paragraph>
+ <reference refuri="https://www.google.com" title="a title">
+ name
+ <literal_block language="default" xml:space="preserve">
+ def func(a, b=1):
+ print(a)
+ <paragraph>
+ Special substitution references:
+ <paragraph>
+ 57
+ words |
+ 0
+ min read
diff --git a/tests/test_sphinx/test_sphinx_builds/test_commonmark_only.html b/tests/test_sphinx/test_sphinx_builds/test_commonmark_only.html
new file mode 100644
index 0000000..381996f
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_commonmark_only.html
@@ -0,0 +1,28 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="test">
+ <h1>
+ Test
+ <a class="headerlink" href="#test" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <div class="highlight-{note} notranslate">
+ <div class="highlight">
+ <pre><span></span>hallo
+</pre>
+ </div>
+ </div>
+ <p>
+ {a}
+ <code class="docutils literal notranslate">
+ <span class="pre">
+ b
+ </span>
+ </code>
+ </p>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_commonmark_only.xml b/tests/test_sphinx/test_sphinx_builds/test_commonmark_only.xml
new file mode 100644
index 0000000..d685466
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_commonmark_only.xml
@@ -0,0 +1,10 @@
+<document source="index.md">
+ <section ids="test" names="test">
+ <title>
+ Test
+ <literal_block language="{note}" xml:space="preserve">
+ hallo
+ <paragraph>
+ {a}
+ <literal>
+ b
diff --git a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html
new file mode 100644
index 0000000..cc6e85b
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.html
@@ -0,0 +1,166 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section class="tex2jax_ignore mathjax_ignore" id="test">
+ <h1>
+ Test
+ <a class="headerlink" href="#test" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <p>
+ *disabled*
+ </p>
+ <p>
+ <span class="math notranslate nohighlight">
+ \(a=1\)
+ </span>
+ </p>
+ <div class="math notranslate nohighlight">
+ \[x=5\]
+ </div>
+ <div class="math notranslate nohighlight" id="equation-2">
+ <span class="eqno">
+ (1)
+ <a class="headerlink" href="#equation-2" title="Permalink to this equation">
+ ¶
+ </a>
+ </span>
+ \[x=5\]
+ </div>
+ <p>
+ $ a=1 $
+ </p>
+ <p>
+ a
+ <div class="math notranslate nohighlight">
+ \[c=3\]
+ </div>
+ b
+ </p>
+ <div class="amsmath math notranslate nohighlight" id="equation-mock-uuid">
+ <span class="eqno">
+ (2)
+ <a class="headerlink" href="#equation-mock-uuid" title="Permalink to this equation">
+ ¶
+ </a>
+ </span>
+ \[\begin{equation}
+b=2
+\end{equation}\]
+ </div>
+ <div class="math notranslate nohighlight">
+ \[ \begin{align}\begin{aligned}c=3\\d=4\end{aligned}\end{align} \]
+ </div>
+ <dl class="simple myst">
+ <dt>
+ Term **1**
+ </dt>
+ <dd>
+ <p>
+ Definition *1*
+ </p>
+ <p>
+ second paragraph
+ </p>
+ </dd>
+ <dt>
+ Term 2
+ </dt>
+ <dd>
+ <p>
+ Definition 2a
+ </p>
+ </dd>
+ <dd>
+ <p>
+ Definition 2b
+ </p>
+ </dd>
+ <dt>
+ Term 3
+ </dt>
+ <dd>
+ <div class="highlight-none notranslate">
+ <div class="highlight">
+ <pre><span></span>code block
+</pre>
+ </div>
+ </div>
+ </dd>
+ <dd>
+ <blockquote>
+ <div>
+ <p>
+ quote
+ </p>
+ </div>
+ </blockquote>
+ </dd>
+ <dd>
+ <p>
+ other
+ </p>
+ </dd>
+ </dl>
+ <figure class="other align-default" id="target">
+ <img alt="fun-fish" src="_images/fun-fish.png"/>
+ <figcaption>
+ <p>
+ <span class="caption-text">
+ This is a caption in **Markdown**
+ </span>
+ <a class="headerlink" href="#target" title="Permalink to this image">
+ ¶
+ </a>
+ </p>
+ </figcaption>
+ </figure>
+ <figure class="other align-default" id="other-target">
+ <a class="bg-primary mb-1 reference internal image-reference" href="_images/fun-fish.png">
+ <img alt="fishy" class="bg-primary mb-1" src="_images/fun-fish.png" style="width: 200px;"/>
+ </a>
+ <figcaption>
+ <p>
+ <span class="caption-text">
+ This is a caption in **Markdown**
+ </span>
+ <a class="headerlink" href="#other-target" title="Permalink to this image">
+ ¶
+ </a>
+ </p>
+ </figcaption>
+ </figure>
+ <p>
+ linkify URL:
+ <a class="reference external" href="http://www.example.com">
+ www.example.com
+ </a>
+ </p>
+ <ul class="contains-task-list simple">
+ <li class="task-list-item">
+ <p>
+ <input class="task-list-item-checkbox" disabled="disabled" type="checkbox"/>
+ hallo
+ </p>
+ </li>
+ <li class="task-list-item">
+ <p>
+ <input checked="checked" class="task-list-item-checkbox" disabled="disabled" type="checkbox"/>
+ there
+ </p>
+ </li>
+ </ul>
+ <p>
+ Numbered code block:
+ </p>
+ <div class="highlight-typescript notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="linenos">1</span><span class="kr">type</span> <span class="nx">Result</span> <span class="o">=</span> <span class="s2">"pass"</span> <span class="o">|</span> <span class="s2">"fail"</span>
+</pre>
+ </div>
+ </div>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml
new file mode 100644
index 0000000..8cefbe3
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_extended_syntaxes.xml
@@ -0,0 +1,90 @@
+<document source="index.md">
+ <meta content="meta description" lang="en" name="description">
+ <meta content="en_US" property="og:locale">
+ <section classes="tex2jax_ignore mathjax_ignore" ids="test" names="test">
+ <title>
+ Test
+ <paragraph>
+ *disabled*
+ <paragraph>
+ <math>
+ a=1
+ <math_block nowrap="False" number="True" xml:space="preserve">
+ x=5
+ <target refid="equation-2">
+ <math_block docname="index" ids="equation-2" label="2" nowrap="False" number="1" xml:space="preserve">
+ x=5
+ <paragraph>
+ $ a=1 $
+ <paragraph>
+ a
+ <math_block nowrap="False" number="True" xml:space="preserve">
+ c=3
+ b
+ <target refid="equation-mock-uuid">
+ <math_block classes="amsmath" docname="index" ids="equation-mock-uuid" label="mock-uuid" nowrap="True" number="2" xml:space="preserve">
+ \begin{equation}
+ b=2
+ \end{equation}
+ <math_block docname="index" label="True" nowrap="False" number="True" xml:space="preserve">
+ c=3
+
+ d=4
+ <definition_list classes="simple myst">
+ <definition_list_item>
+ <term>
+ Term **1**
+ <definition>
+ <paragraph>
+ Definition *1*
+ <paragraph>
+ second paragraph
+ <definition_list_item>
+ <term>
+ Term 2
+ <definition>
+ <paragraph>
+ Definition 2a
+ <definition>
+ <paragraph>
+ Definition 2b
+ <definition_list_item>
+ <term>
+ Term 3
+ <definition>
+ <literal_block language="none" xml:space="preserve">
+ code block
+ <definition>
+ <block_quote>
+ <paragraph>
+ quote
+ <definition>
+ <paragraph>
+ other
+ <figure classes="other" ids="target" names="target">
+ <image alt="fun-fish" candidates="{'*': 'fun-fish.png'}" uri="fun-fish.png">
+ <caption>
+ This is a caption in **Markdown**
+ <figure classes="other" ids="other-target" names="other-target">
+ <image alt="fishy" candidates="{'*': 'fun-fish.png'}" classes="bg-primary mb-1" uri="fun-fish.png" width="200px">
+ <caption>
+ This is a caption in **Markdown**
+ <paragraph>
+ linkify URL:
+ <reference refuri="http://www.example.com">
+ www.example.com
+ <bullet_list bullet="-" classes="contains-task-list">
+ <list_item classes="task-list-item">
+ <paragraph>
+ <raw format="html" xml:space="preserve">
+ <input class="task-list-item-checkbox" disabled="disabled" type="checkbox">
+ hallo
+ <list_item classes="task-list-item">
+ <paragraph>
+ <raw format="html" xml:space="preserve">
+ <input class="task-list-item-checkbox" checked="checked" disabled="disabled" type="checkbox">
+ there
+ <paragraph>
+ Numbered code block:
+ <literal_block language="typescript" linenos="True" xml:space="preserve">
+ type Result = "pass" | "fail"
diff --git a/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx4.html b/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx4.html
new file mode 100644
index 0000000..bef86e9
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx4.html
@@ -0,0 +1,131 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="test">
+ <h1>
+ Test
+ <a class="headerlink" href="#test" title="Permalink to this headline">
+ ¶
+ </a>
+ </h1>
+ <dl class="myst field-list simple">
+ <dt class="field-odd">
+ field
+ </dt>
+ <dd class="field-odd">
+ <p>
+ </p>
+ </dd>
+ <dt class="field-even">
+ <em>
+ field
+ </em>
+ </dt>
+ <dd class="field-even">
+ <p>
+ content
+ </p>
+ </dd>
+ </dl>
+ <dl class="py function">
+ <dt class="sig sig-object py" id="send_message">
+ <span class="sig-name descname">
+ <span class="pre">
+ send_message
+ </span>
+ </span>
+ <span class="sig-paren">
+ (
+ </span>
+ <em class="sig-param">
+ <span class="n">
+ <span class="pre">
+ sender
+ </span>
+ </span>
+ </em>
+ ,
+ <em class="sig-param">
+ <span class="n">
+ <span class="pre">
+ priority
+ </span>
+ </span>
+ </em>
+ <span class="sig-paren">
+ )
+ </span>
+ <a class="headerlink" href="#send_message" title="Permalink to this definition">
+ ¶
+ </a>
+ </dt>
+ <dd>
+ <p>
+ Send a message to a recipient
+ </p>
+ <dl class="myst field-list simple">
+ <dt class="field-odd">
+ Parameters
+ </dt>
+ <dd class="field-odd">
+ <ul class="simple">
+ <li>
+ <p>
+ <strong>
+ sender
+ </strong>
+ (
+ <em>
+ str
+ </em>
+ ) – The person sending the message
+ </p>
+ </li>
+ <li>
+ <p>
+ <strong>
+ priority
+ </strong>
+ (
+ <em>
+ int
+ </em>
+ ) – The priority of the message, can be a number 1-5
+ </p>
+ </li>
+ </ul>
+ </dd>
+ <dt class="field-even">
+ Returns
+ </dt>
+ <dd class="field-even">
+ <p>
+ the message id
+ </p>
+ </dd>
+ <dt class="field-odd">
+ Return type
+ </dt>
+ <dd class="field-odd">
+ <p>
+ int
+ </p>
+ </dd>
+ <dt class="field-even">
+ Raises
+ </dt>
+ <dd class="field-even">
+ <p>
+ <strong>
+ ValueError
+ </strong>
+ – if the message_body exceeds 160 characters
+ </p>
+ </dd>
+ </dl>
+ </dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx5.html b/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx5.html
new file mode 100644
index 0000000..8548f3d
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.sphinx5.html
@@ -0,0 +1,149 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="test">
+ <h1>
+ Test
+ <a class="headerlink" href="#test" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <dl class="myst field-list simple">
+ <dt class="field-odd">
+ field
+ <span class="colon">
+ :
+ </span>
+ </dt>
+ <dd class="field-odd">
+ <p>
+ </p>
+ </dd>
+ <dt class="field-even">
+ <em>
+ field
+ </em>
+ <span class="colon">
+ :
+ </span>
+ </dt>
+ <dd class="field-even">
+ <p>
+ content
+ </p>
+ </dd>
+ </dl>
+ <dl class="py function">
+ <dt class="sig sig-object py" id="send_message">
+ <span class="sig-name descname">
+ <span class="pre">
+ send_message
+ </span>
+ </span>
+ <span class="sig-paren">
+ (
+ </span>
+ <em class="sig-param">
+ <span class="n">
+ <span class="pre">
+ sender
+ </span>
+ </span>
+ </em>
+ ,
+ <em class="sig-param">
+ <span class="n">
+ <span class="pre">
+ priority
+ </span>
+ </span>
+ </em>
+ <span class="sig-paren">
+ )
+ </span>
+ <a class="headerlink" href="#send_message" title="Permalink to this definition">
+ ¶
+ </a>
+ </dt>
+ <dd>
+ <p>
+ Send a message to a recipient
+ </p>
+ <dl class="myst field-list simple">
+ <dt class="field-odd">
+ Parameters
+ <span class="colon">
+ :
+ </span>
+ </dt>
+ <dd class="field-odd">
+ <ul class="simple">
+ <li>
+ <p>
+ <strong>
+ sender
+ </strong>
+ (
+ <em>
+ str
+ </em>
+ ) – The person sending the message
+ </p>
+ </li>
+ <li>
+ <p>
+ <strong>
+ priority
+ </strong>
+ (
+ <em>
+ int
+ </em>
+ ) – The priority of the message, can be a number 1-5
+ </p>
+ </li>
+ </ul>
+ </dd>
+ <dt class="field-even">
+ Returns
+ <span class="colon">
+ :
+ </span>
+ </dt>
+ <dd class="field-even">
+ <p>
+ the message id
+ </p>
+ </dd>
+ <dt class="field-odd">
+ Return type
+ <span class="colon">
+ :
+ </span>
+ </dt>
+ <dd class="field-odd">
+ <p>
+ int
+ </p>
+ </dd>
+ <dt class="field-even">
+ Raises
+ <span class="colon">
+ :
+ </span>
+ </dt>
+ <dd class="field-even">
+ <p>
+ <strong>
+ ValueError
+ </strong>
+ – if the message_body exceeds 160 characters
+ </p>
+ </dd>
+ </dl>
+ </dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.xml b/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.xml
new file mode 100644
index 0000000..1fb20d4
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_fieldlist_extension.xml
@@ -0,0 +1,82 @@
+<document source="index.md">
+ <section ids="test" names="test">
+ <title>
+ Test
+ <field_list classes="myst">
+ <field>
+ <field_name>
+ field
+ <field_body>
+ <field>
+ <field_name>
+ <emphasis>
+ field
+ <field_body>
+ <paragraph>
+ content
+ <index entries="('pair',\ 'built-in\ function;\ send_message()',\ 'send_message',\ '',\ None)">
+ <desc classes="py function" desctype="function" domain="py" noindex="False" objtype="function">
+ <desc_signature class="" classes="sig sig-object" fullname="send_message" ids="send_message" module="True">
+ <desc_name classes="sig-name descname" xml:space="preserve">
+ send_message
+ <desc_parameterlist xml:space="preserve">
+ <desc_parameter xml:space="preserve">
+ <desc_sig_name classes="n">
+ sender
+ <desc_parameter xml:space="preserve">
+ <desc_sig_name classes="n">
+ priority
+ <desc_content>
+ <paragraph>
+ Send a message to a recipient
+ <field_list classes="myst">
+ <field>
+ <field_name>
+ Parameters
+ <field_body>
+ <bullet_list>
+ <list_item>
+ <paragraph>
+ <literal_strong>
+ sender
+ (
+ <pending_xref py:class="True" py:module="True" refdomain="py" refexplicit="False" refspecific="True" reftarget="str" reftype="class">
+ <literal_emphasis>
+ str
+ )
+ –
+ The person sending the message
+ <list_item>
+ <paragraph>
+ <literal_strong>
+ priority
+ (
+ <pending_xref py:class="True" py:module="True" refdomain="py" refexplicit="False" refspecific="True" reftarget="int" reftype="class">
+ <literal_emphasis>
+ int
+ )
+ –
+ The priority of the message, can be a number 1-5
+ <field>
+ <field_name>
+ Returns
+ <field_body>
+ <paragraph>
+ the message id
+ <field>
+ <field_name>
+ Return type
+ <field_body>
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdomain="py" refexplicit="False" refspecific="True" reftarget="int" reftype="class">
+ int
+ <field>
+ <field_name>
+ Raises
+ <field_body>
+ <paragraph>
+ <pending_xref py:class="True" py:module="True" refdomain="py" refexplicit="False" refspecific="True" reftarget="ValueError" reftype="exc">
+ <literal_strong>
+ ValueError
+ –
+ if the message_body exceeds 160 characters
diff --git a/tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx4.html b/tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx4.html
new file mode 100644
index 0000000..70cfb54
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx4.html
@@ -0,0 +1,147 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="footnotes-with-markdown">
+ <h1>
+ Footnotes with Markdown
+ <a class="headerlink" href="#footnotes-with-markdown" title="Permalink to this headline">
+ ¶
+ </a>
+ </h1>
+ <p>
+ <a class="footnote-reference brackets" href="#c" id="id1">
+ 1
+ </a>
+ </p>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#d" id="id2">
+ 2
+ </a>
+ </p>
+ </div>
+ <p>
+ <a class="footnote-reference brackets" href="#a" id="id3">
+ 3
+ </a>
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#b" id="id4">
+ 4
+ </a>
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#id8" id="id5">
+ 123
+ </a>
+ <a class="footnote-reference brackets" href="#id8" id="id6">
+ 123
+ </a>
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#e" id="id7">
+ 5
+ </a>
+ </p>
+ <blockquote>
+ <div>
+ <ul class="simple">
+ <li>
+ </li>
+ </ul>
+ </div>
+ </blockquote>
+ <hr class="footnotes docutils"/>
+ <dl class="footnote brackets">
+ <dt class="label" id="c">
+ <span class="brackets">
+ <a class="fn-backref" href="#id1">
+ 1
+ </a>
+ </span>
+ </dt>
+ <dd>
+ <p>
+ a footnote referenced first
+ </p>
+ </dd>
+ <dt class="label" id="d">
+ <span class="brackets">
+ <a class="fn-backref" href="#id2">
+ 2
+ </a>
+ </span>
+ </dt>
+ <dd>
+ <p>
+ a footnote referenced in a directive
+ </p>
+ </dd>
+ <dt class="label" id="a">
+ <span class="brackets">
+ <a class="fn-backref" href="#id3">
+ 3
+ </a>
+ </span>
+ </dt>
+ <dd>
+ <p>
+ some footnote
+ <em>
+ text
+ </em>
+ </p>
+ </dd>
+ <dt class="label" id="b">
+ <span class="brackets">
+ <a class="fn-backref" href="#id4">
+ 4
+ </a>
+ </span>
+ </dt>
+ <dd>
+ <p>
+ a footnote before its reference
+ </p>
+ </dd>
+ <dt class="label" id="id8">
+ <span class="brackets">
+ 123
+ </span>
+ <span class="fn-backref">
+ (
+ <a href="#id5">
+ 1
+ </a>
+ ,
+ <a href="#id6">
+ 2
+ </a>
+ )
+ </span>
+ </dt>
+ <dd>
+ <p>
+ multiple references footnote
+ </p>
+ </dd>
+ <dt class="label" id="e">
+ <span class="brackets">
+ <a class="fn-backref" href="#id7">
+ 5
+ </a>
+ </span>
+ </dt>
+ <dd>
+ <p>
+ footnote definition in a block element
+ </p>
+ </dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx5.html b/tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx5.html
new file mode 100644
index 0000000..546739c
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_footnotes.sphinx5.html
@@ -0,0 +1,213 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="footnotes-with-markdown">
+ <h1>
+ Footnotes with Markdown
+ <a class="headerlink" href="#footnotes-with-markdown" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <p>
+ <a class="footnote-reference brackets" href="#c" id="id1" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 1
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ </p>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#d" id="id2" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 2
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ </p>
+ </div>
+ <p>
+ <a class="footnote-reference brackets" href="#a" id="id3" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 3
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#b" id="id4" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 4
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#id8" id="id5" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 123
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ <a class="footnote-reference brackets" href="#id8" id="id6" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 123
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="footnote-reference brackets" href="#e" id="id7" role="doc-noteref">
+ <span class="fn-bracket">
+ [
+ </span>
+ 5
+ <span class="fn-bracket">
+ ]
+ </span>
+ </a>
+ </p>
+ <blockquote>
+ <div>
+ <ul class="simple">
+ <li>
+ </li>
+ </ul>
+ </div>
+ </blockquote>
+ <hr class="footnotes docutils"/>
+ <aside class="footnote-list brackets">
+ <aside class="footnote brackets" id="c" role="note">
+ <span class="label">
+ <span class="fn-bracket">
+ [
+ </span>
+ <a href="#id1" role="doc-backlink">
+ 1
+ </a>
+ <span class="fn-bracket">
+ ]
+ </span>
+ </span>
+ <p>
+ a footnote referenced first
+ </p>
+ </aside>
+ <aside class="footnote brackets" id="d" role="note">
+ <span class="label">
+ <span class="fn-bracket">
+ [
+ </span>
+ <a href="#id2" role="doc-backlink">
+ 2
+ </a>
+ <span class="fn-bracket">
+ ]
+ </span>
+ </span>
+ <p>
+ a footnote referenced in a directive
+ </p>
+ </aside>
+ <aside class="footnote brackets" id="a" role="note">
+ <span class="label">
+ <span class="fn-bracket">
+ [
+ </span>
+ <a href="#id3" role="doc-backlink">
+ 3
+ </a>
+ <span class="fn-bracket">
+ ]
+ </span>
+ </span>
+ <p>
+ some footnote
+ <em>
+ text
+ </em>
+ </p>
+ </aside>
+ <aside class="footnote brackets" id="b" role="note">
+ <span class="label">
+ <span class="fn-bracket">
+ [
+ </span>
+ <a href="#id4" role="doc-backlink">
+ 4
+ </a>
+ <span class="fn-bracket">
+ ]
+ </span>
+ </span>
+ <p>
+ a footnote before its reference
+ </p>
+ </aside>
+ <aside class="footnote brackets" id="id8" role="note">
+ <span class="label">
+ <span class="fn-bracket">
+ [
+ </span>
+ 123
+ <span class="fn-bracket">
+ ]
+ </span>
+ </span>
+ <span class="backrefs">
+ (
+ <a href="#id5" role="doc-backlink">
+ 1
+ </a>
+ ,
+ <a href="#id6" role="doc-backlink">
+ 2
+ </a>
+ )
+ </span>
+ <p>
+ multiple references footnote
+ </p>
+ </aside>
+ <aside class="footnote brackets" id="e" role="note">
+ <span class="label">
+ <span class="fn-bracket">
+ [
+ </span>
+ <a href="#id7" role="doc-backlink">
+ 5
+ </a>
+ <span class="fn-bracket">
+ ]
+ </span>
+ </span>
+ <p>
+ footnote definition in a block element
+ </p>
+ </aside>
+ </aside>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_footnotes.xml b/tests/test_sphinx/test_sphinx_builds/test_footnotes.xml
new file mode 100644
index 0000000..950d4f8
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_footnotes.xml
@@ -0,0 +1,62 @@
+<document source="footnote_md.md">
+ <section ids="footnotes-with-markdown" names="footnotes\ with\ markdown">
+ <title>
+ Footnotes with Markdown
+ <paragraph>
+ <footnote_reference auto="1" docname="footnote_md" ids="id1" refid="c">
+ 1
+ <note>
+ <paragraph>
+ <footnote_reference auto="1" docname="footnote_md" ids="id2" refid="d">
+ 2
+ <paragraph>
+ <footnote_reference auto="1" docname="footnote_md" ids="id3" refid="a">
+ 3
+ <paragraph>
+ <footnote_reference auto="1" docname="footnote_md" ids="id4" refid="b">
+ 4
+ <paragraph>
+ <footnote_reference docname="footnote_md" ids="id5" refid="id8">
+ 123
+
+ <footnote_reference docname="footnote_md" ids="id6" refid="id8">
+ 123
+ <paragraph>
+ <footnote_reference auto="1" docname="footnote_md" ids="id7" refid="e">
+ 5
+ <block_quote>
+ <bullet_list bullet="-">
+ <list_item>
+ <transition classes="footnotes">
+ <footnote auto="1" backrefs="id1" docname="footnote_md" ids="c" names="c">
+ <label>
+ 1
+ <paragraph>
+ a footnote referenced first
+ <footnote auto="1" backrefs="id2" docname="footnote_md" ids="d" names="d">
+ <label>
+ 2
+ <paragraph>
+ a footnote referenced in a directive
+ <footnote auto="1" backrefs="id3" docname="footnote_md" ids="a" names="a">
+ <label>
+ 3
+ <paragraph>
+ some footnote
+ <emphasis>
+ text
+ <footnote auto="1" backrefs="id4" docname="footnote_md" ids="b" names="b">
+ <label>
+ 4
+ <paragraph>
+ a footnote before its reference
+ <footnote backrefs="id5 id6" docname="footnote_md" ids="id8" names="123">
+ <label>
+ 123
+ <paragraph>
+ multiple references footnote
+ <footnote auto="1" backrefs="id7" docname="footnote_md" ids="e" names="e">
+ <label>
+ 5
+ <paragraph>
+ footnote definition in a block element
diff --git a/tests/test_sphinx/test_sphinx_builds/test_gettext.pot b/tests/test_sphinx/test_sphinx_builds/test_gettext.pot
new file mode 100644
index 0000000..933b8b6
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_gettext.pot
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) XXXX, Executable Book Project
+# This file is distributed under the same license as the Python package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Python \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../index.md:1
+msgid "**bold** text 1"
+msgstr ""
+
+#: ../../index.md:3
+msgid "**bold** text 2"
+msgstr ""
+
+#: ../../index.md:5
+msgid "**bold** text 3"
+msgstr ""
+
+#: ../../index.md:10
+msgid "**bold** text 4"
+msgstr ""
+
+#: ../../index.md:13
+msgid "**bold** text 5"
+msgstr ""
+
+#: ../../index.md:15
+msgid "**bold** text 6"
+msgstr ""
+
+#: ../../index.md:17
+msgid "**bold** text 7"
+msgstr ""
+
+#: ../../index.md:18
+msgid "**bold** text 8"
+msgstr ""
+
+#: ../../index.md:0
+msgid "**bold** text 9"
+msgstr ""
+
+#: ../../index.md:0
+msgid "**bold** text 10"
+msgstr ""
+
+#: ../../index.md:26
+msgid "**bold** text 11"
+msgstr ""
+
+#: ../../index.md:28
+msgid "Extra ```backticks```"
+msgstr ""
+
+#: ../../index.md:55
+msgid "![Fun Fish 1](fun-fish.png)"
+msgstr ""
+
+#: ../../index.md:55
+msgid "Fun Fish 1"
+msgstr ""
+
+#: ../../index.md:57
+msgid "Fun Fish 2"
+msgstr ""
+
+#: ../../index.md:61
+msgid "Fun Fish 3"
+msgstr ""
diff --git a/tests/test_sphinx/test_sphinx_builds/test_gettext_additional_targets.pot b/tests/test_sphinx/test_sphinx_builds/test_gettext_additional_targets.pot
new file mode 100644
index 0000000..11c51d4
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_gettext_additional_targets.pot
@@ -0,0 +1,129 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) XXXX, Executable Book Project
+# This file is distributed under the same license as the Python package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: Python \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: ../../index.md:1
+msgid "**bold** text 1"
+msgstr ""
+
+#: ../../index.md:3
+msgid "**bold** text 2"
+msgstr ""
+
+#: ../../index.md:5
+msgid "**bold** text 3"
+msgstr ""
+
+#: ../../index.md:10
+msgid "**bold** text 4"
+msgstr ""
+
+#: ../../index.md:13
+msgid "**bold** text 5"
+msgstr ""
+
+#: ../../index.md:15
+msgid "**bold** text 6"
+msgstr ""
+
+#: ../../index.md:17
+msgid "**bold** text 7"
+msgstr ""
+
+#: ../../index.md:18
+msgid "**bold** text 8"
+msgstr ""
+
+#: ../../index.md:0
+msgid "**bold** text 9"
+msgstr ""
+
+#: ../../index.md:0
+msgid "**bold** text 10"
+msgstr ""
+
+#: ../../index.md:24
+msgid "<div markdown=1>\n"
+""
+msgstr ""
+
+#: ../../index.md:26
+msgid "**bold** text 11"
+msgstr ""
+
+#: ../../index.md:28
+msgid "Extra ```backticks```"
+msgstr ""
+
+#: ../../index.md:30
+msgid "</div>\n"
+""
+msgstr ""
+
+#: ../../index.md:32
+msgid "**additional** text 12\n"
+""
+msgstr ""
+
+#: ../../index.md:34
+msgid "**additional** text 13\n"
+""
+msgstr ""
+
+#: ../../index.md:38
+msgid "{\n"
+" \"additional\": \"text 14\"\n"
+"}\n"
+""
+msgstr ""
+
+#: ../../index.md:44
+msgid "<h3>**additional** text 15</h3>\n"
+""
+msgstr ""
+
+#: ../../index.md:46
+msgid ">>> print('doctest block')\n"
+"doctest block\n"
+""
+msgstr ""
+
+#: ../../index.md:51
+msgid "<iframe src=\"http://sphinx-doc.org\"></iframe>"
+msgstr ""
+
+#: ../../index.md:55
+msgid "![Fun Fish 1](fun-fish.png)"
+msgstr ""
+
+#: ../../index.md:55
+msgid "Fun Fish 1"
+msgstr ""
+
+#: ../../index.md:55
+#: ../../index.md:57
+#: ../../index.md:61
+msgid ".. image:: fun-fish.png"
+msgstr ""
+
+#: ../../index.md:57
+msgid "Fun Fish 2"
+msgstr ""
+
+#: ../../index.md:61
+msgid "Fun Fish 3"
+msgstr ""
diff --git a/tests/test_sphinx/test_sphinx_builds/test_gettext_html.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.resolved.xml
new file mode 100644
index 0000000..231ca33
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.resolved.xml
@@ -0,0 +1,93 @@
+<document source="index.md">
+ <section ids="bold-text-1" names="bold\ text\ 1 texte\ 1\ en\ gras">
+ <title>
+ texte 1 en
+ <strong>
+ gras
+ <paragraph>
+ texte 2 en
+ <strong>
+ gras
+ <block_quote>
+ <paragraph>
+ texte 3 en
+ <strong>
+ gras
+ <note>
+ <paragraph>
+ texte 4 en
+ <strong>
+ gras
+ <bullet_list bullet="*">
+ <list_item>
+ <paragraph>
+ texte 5 en
+ <strong>
+ gras
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ texte 6 en
+ <strong>
+ gras
+ <definition_list classes="simple myst">
+ <definition_list_item>
+ <term>
+ texte 7 en
+ <strong>
+ gras
+ <definition>
+ <paragraph>
+ texte 8 en
+ <strong>
+ gras
+ <table classes="colwidths-auto">
+ <tgroup cols="1">
+ <colspec colwidth="100">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ texte 9 en
+ <strong>
+ gras
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ texte 10 en
+ <strong>
+ gras
+ <raw format="html" xml:space="preserve">
+ <div markdown=1>
+ <paragraph>
+ texte 11 en
+ <strong>
+ gras
+ <paragraph>
+ « 
+ <literal>
+ Backtick
+  » supplémentaire
+ <raw format="html" xml:space="preserve">
+ </div>
+ <literal_block language="none" linenos="False" xml:space="preserve">
+ **additional** text 12
+ <literal_block language="default" linenos="False" xml:space="preserve">
+ **additional** text 13
+ <literal_block language="json" linenos="False" xml:space="preserve">
+ {
+ "additional": "text 14"
+ }
+ <raw format="html" xml:space="preserve">
+ <h3>**additional** text 15</h3>
+ <literal_block language="python" linenos="False" xml:space="preserve">
+ >>> print('doctest block')
+ doctest block
+ <raw format="html" xml:space="preserve">
+ <iframe src="http://sphinx-doc.org"></iframe>
+ <paragraph>
+ <image alt="Poisson amusant 1" candidates="{'*': 'poisson-amusant.png'}" uri="poisson-amusant.png">
+ <image alt="Poisson amusant 2" candidates="{'*': 'fun-fish.png'}" uri="fun-fish.png">
+ <figure>
+ <image alt="Poisson amusant 3" candidates="{'*': 'fun-fish.png'}" uri="fun-fish.png">
diff --git a/tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx4.html b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx4.html
new file mode 100644
index 0000000..825048a
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx4.html
@@ -0,0 +1,162 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="bold-text-1">
+ <h1>
+ texte 1 en
+ <strong>
+ gras
+ </strong>
+ <a class="headerlink" href="#bold-text-1" title="Lien permanent vers ce titre">
+ ¶
+ </a>
+ </h1>
+ <p>
+ texte 2 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ <blockquote>
+ <div>
+ <p>
+ texte 3 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </div>
+ </blockquote>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ texte 4 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </div>
+ <ul class="simple">
+ <li>
+ <p>
+ texte 5 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </li>
+ </ul>
+ <ol class="arabic simple">
+ <li>
+ <p>
+ texte 6 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </li>
+ </ol>
+ <dl class="simple myst">
+ <dt>
+ texte 7 en
+ <strong>
+ gras
+ </strong>
+ </dt>
+ <dd>
+ <p>
+ texte 8 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </dd>
+ </dl>
+ <table class="colwidths-auto docutils align-default">
+ <thead>
+ <tr class="row-odd">
+ <th class="head">
+ <p>
+ texte 9 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="row-even">
+ <td>
+ <p>
+ texte 10 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div markdown="1">
+ <p>
+ texte 11 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ <p>
+ «
+ <code class="docutils literal notranslate">
+ <span class="pre">
+ Backtick
+ </span>
+ </code>
+ » supplémentaire
+ </p>
+ </div>
+ <div class="highlight-none notranslate">
+ <div class="highlight">
+ <pre><span></span>**additional** text 12
+</pre>
+ </div>
+ </div>
+ <div class="highlight-default notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="o">**</span><span class="n">additional</span><span class="o">**</span> <span class="n">text</span> <span class="mi">13</span>
+</pre>
+ </div>
+ </div>
+ <div class="highlight-json notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="p">{</span>
+ <span class="nt">"additional"</span><span class="p">:</span> <span class="s2">"text 14"</span>
+<span class="p">}</span>
+</pre>
+ </div>
+ </div>
+ <h3>
+ **additional** text 15
+ </h3>
+ <div class="highlight-python notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="s1">'doctest block'</span><span class="p">)</span>
+<span class="go">doctest block</span>
+</pre>
+ </div>
+ </div>
+ <iframe src="http://sphinx-doc.org">
+ </iframe>
+ <p>
+ <img alt="Poisson amusant 1" src="_images/poisson-amusant.png"/>
+ </p>
+ <img alt="Poisson amusant 2" src="_images/fun-fish.png"/>
+ <figure class="align-default">
+ <img alt="Poisson amusant 3" src="_images/fun-fish.png"/>
+ </figure>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx5.html b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx5.html
new file mode 100644
index 0000000..469e188
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.sphinx5.html
@@ -0,0 +1,162 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="bold-text-1">
+ <h1>
+ texte 1 en
+ <strong>
+ gras
+ </strong>
+ <a class="headerlink" href="#bold-text-1" title="Lien permanent vers cette rubrique">
+ ¶
+ </a>
+ </h1>
+ <p>
+ texte 2 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ <blockquote>
+ <div>
+ <p>
+ texte 3 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </div>
+ </blockquote>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ texte 4 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </div>
+ <ul class="simple">
+ <li>
+ <p>
+ texte 5 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </li>
+ </ul>
+ <ol class="arabic simple">
+ <li>
+ <p>
+ texte 6 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </li>
+ </ol>
+ <dl class="simple myst">
+ <dt>
+ texte 7 en
+ <strong>
+ gras
+ </strong>
+ </dt>
+ <dd>
+ <p>
+ texte 8 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </dd>
+ </dl>
+ <table class="docutils align-default">
+ <thead>
+ <tr class="row-odd">
+ <th class="head">
+ <p>
+ texte 9 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr class="row-even">
+ <td>
+ <p>
+ texte 10 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <div markdown="1">
+ <p>
+ texte 11 en
+ <strong>
+ gras
+ </strong>
+ </p>
+ <p>
+ «
+ <code class="docutils literal notranslate">
+ <span class="pre">
+ Backtick
+ </span>
+ </code>
+ » supplémentaire
+ </p>
+ </div>
+ <div class="highlight-none notranslate">
+ <div class="highlight">
+ <pre><span></span>**additional** text 12
+</pre>
+ </div>
+ </div>
+ <div class="highlight-default notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="o">**</span><span class="n">additional</span><span class="o">**</span> <span class="n">text</span> <span class="mi">13</span>
+</pre>
+ </div>
+ </div>
+ <div class="highlight-json notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="p">{</span>
+ <span class="nt">"additional"</span><span class="p">:</span> <span class="s2">"text 14"</span>
+<span class="p">}</span>
+</pre>
+ </div>
+ </div>
+ <h3>
+ **additional** text 15
+ </h3>
+ <div class="highlight-python notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="s1">'doctest block'</span><span class="p">)</span>
+<span class="go">doctest block</span>
+</pre>
+ </div>
+ </div>
+ <iframe src="http://sphinx-doc.org">
+ </iframe>
+ <p>
+ <img alt="Poisson amusant 1" src="_images/poisson-amusant.png"/>
+ </p>
+ <img alt="Poisson amusant 2" src="_images/fun-fish.png"/>
+ <figure class="align-default">
+ <img alt="Poisson amusant 3" src="_images/fun-fish.png"/>
+ </figure>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_gettext_html.xml b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.xml
new file mode 100644
index 0000000..dfc5f41
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_gettext_html.xml
@@ -0,0 +1,93 @@
+<document source="index.md">
+ <section ids="bold-text-1" names="bold\ text\ 1 texte\ 1\ en\ gras">
+ <title>
+ texte 1 en
+ <strong>
+ gras
+ <paragraph>
+ texte 2 en
+ <strong>
+ gras
+ <block_quote>
+ <paragraph>
+ texte 3 en
+ <strong>
+ gras
+ <note>
+ <paragraph>
+ texte 4 en
+ <strong>
+ gras
+ <bullet_list bullet="*">
+ <list_item>
+ <paragraph>
+ texte 5 en
+ <strong>
+ gras
+ <enumerated_list enumtype="arabic" prefix="" suffix=".">
+ <list_item>
+ <paragraph>
+ texte 6 en
+ <strong>
+ gras
+ <definition_list classes="simple myst">
+ <definition_list_item>
+ <term>
+ texte 7 en
+ <strong>
+ gras
+ <definition>
+ <paragraph>
+ texte 8 en
+ <strong>
+ gras
+ <table classes="colwidths-auto">
+ <tgroup cols="1">
+ <colspec colwidth="100">
+ <thead>
+ <row>
+ <entry>
+ <paragraph>
+ texte 9 en
+ <strong>
+ gras
+ <tbody>
+ <row>
+ <entry>
+ <paragraph>
+ texte 10 en
+ <strong>
+ gras
+ <raw format="html" xml:space="preserve">
+ <div markdown=1>
+ <paragraph>
+ texte 11 en
+ <strong>
+ gras
+ <paragraph>
+ « 
+ <literal>
+ Backtick
+  » supplémentaire
+ <raw format="html" xml:space="preserve">
+ </div>
+ <literal_block language="none" xml:space="preserve">
+ **additional** text 12
+ <literal_block language="default" xml:space="preserve">
+ **additional** text 13
+ <literal_block language="json" xml:space="preserve">
+ {
+ "additional": "text 14"
+ }
+ <raw format="html" xml:space="preserve">
+ <h3>**additional** text 15</h3>
+ <literal_block language="python" xml:space="preserve">
+ >>> print('doctest block')
+ doctest block
+ <raw format="html" xml:space="preserve">
+ <iframe src="http://sphinx-doc.org"></iframe>
+ <paragraph>
+ <image alt="Poisson amusant 1" candidates="{'*': 'poisson-amusant.png'}" uri="poisson-amusant.png">
+ <image alt="Poisson amusant 2" candidates="{'*': 'fun-fish.png'}" uri="fun-fish.png">
+ <figure>
+ <image alt="Poisson amusant 3" candidates="{'*': 'fun-fish.png'}" uri="fun-fish.png">
diff --git a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.html b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.html
new file mode 100644
index 0000000..67c4415
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.html
@@ -0,0 +1,22 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="hyphen-1">
+ <h1>
+ Hyphen - 1
+ <a class="headerlink" href="#hyphen-1" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <section id="dot-1-1">
+ <h2>
+ Dot 1.1
+ <a class="headerlink" href="#dot-1-1" title="Permalink to this heading">
+ ¶
+ </a>
+ </h2>
+ </section>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml
new file mode 100644
index 0000000..e48908d
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.resolved.xml
@@ -0,0 +1,7 @@
+<document source="index.md">
+ <section ids="hyphen-1" myst-anchor="index.md#hyphen-1" names="hyphen\ -\ 1">
+ <title>
+ Hyphen - 1
+ <section ids="dot-1-1" myst-anchor="index.md#dot-1-1" names="dot\ 1.1">
+ <title>
+ Dot 1.1
diff --git a/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml
new file mode 100644
index 0000000..e48908d
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_heading_slug_func.xml
@@ -0,0 +1,7 @@
+<document source="index.md">
+ <section ids="hyphen-1" myst-anchor="index.md#hyphen-1" names="hyphen\ -\ 1">
+ <title>
+ Hyphen - 1
+ <section ids="dot-1-1" myst-anchor="index.md#dot-1-1" names="dot\ 1.1">
+ <title>
+ Dot 1.1
diff --git a/tests/test_sphinx/test_sphinx_builds/test_include_from_rst.xml b/tests/test_sphinx/test_sphinx_builds/test_include_from_rst.xml
new file mode 100644
index 0000000..9248bd3
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_include_from_rst.xml
@@ -0,0 +1,10 @@
+<document source="index.rst">
+ <section ids="title" names="title">
+ <title>
+ Title
+ <section ids="markdown" names="markdown">
+ <title>
+ Markdown
+ <paragraph>
+ <reference refuri="http://example.com/">
+ target
diff --git a/tests/test_sphinx/test_sphinx_builds/test_includes.html b/tests/test_sphinx/test_sphinx_builds/test_includes.html
new file mode 100644
index 0000000..41eb1ee
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_includes.html
@@ -0,0 +1,135 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="main-title">
+ <h1>
+ Main Title
+ <a class="headerlink" href="#main-title" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <section id="a-sub-heading-in-include">
+ <span id="inc-header">
+ </span>
+ <h2>
+ A Sub-Heading in Include
+ <a class="headerlink" href="#a-sub-heading-in-include" title="Permalink to this heading">
+ ¶
+ </a>
+ </h2>
+ <p>
+ Some text with
+ <em>
+ syntax
+ </em>
+ </p>
+ </section>
+ <section id="a-sub-heading-in-nested-include">
+ <h2>
+ A Sub-Heading in Nested Include
+ <a class="headerlink" href="#a-sub-heading-in-nested-include" title="Permalink to this heading">
+ ¶
+ </a>
+ </h2>
+ <p>
+ Some other text with
+ <strong>
+ syntax
+ </strong>
+ </p>
+ <p>
+ This relative path will refer to the importing file:
+ </p>
+ <figure class="align-default" id="id1">
+ <img alt="_images/example1.jpg" src="_images/example1.jpg"/>
+ <figcaption>
+ <p>
+ <span class="caption-text">
+ Caption
+ </span>
+ <a class="headerlink" href="#id1" title="Permalink to this image">
+ ¶
+ </a>
+ </p>
+ </figcaption>
+ </figure>
+ <p>
+ This absolute path will refer to the project root (where the
+ <code class="docutils literal notranslate">
+ <span class="pre">
+ conf.py
+ </span>
+ </code>
+ is):
+ </p>
+ <figure class="align-default" id="id2">
+ <img alt="_images/example2.jpg" src="_images/example2.jpg"/>
+ <figcaption>
+ <p>
+ <span class="caption-text">
+ Caption
+ </span>
+ <a class="headerlink" href="#id2" title="Permalink to this image">
+ ¶
+ </a>
+ </p>
+ </figcaption>
+ </figure>
+ <p>
+ <img alt="alt" src="_images/example2.jpg"/>
+ </p>
+ <p>
+ <img alt="alt" src="https://example.com"/>
+ </p>
+ <p>
+ <a class="reference internal" href="#">
+ <span class="doc std std-doc">
+ text
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#inc-header">
+ <span class="std std-ref">
+ A Sub-Heading in Include
+ </span>
+ </a>
+ </p>
+ <div class="code python highlight-default notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="k">def</span> <span class="nf">a_func</span><span class="p">(</span><span class="n">param</span><span class="p">):</span>
+ <span class="nb">print</span><span class="p">(</span><span class="n">param</span><span class="p">)</span>
+</pre>
+ </div>
+ </div>
+ <pre class="code python literal-block"><small class="ln">0 </small><code data-lineno="0 "><span class="keyword">def</span> <span class="name function">a_func</span><span class="punctuation">(</span><span class="name">param</span><span class="punctuation">):</span>
+</code><small class="ln">1 </small><code data-lineno="1 "> <span class="name builtin">print</span><span class="punctuation">(</span><span class="name">param</span><span class="punctuation">)</span></code></pre>
+ <div class="highlight-default notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="n">This</span> <span class="n">should</span> <span class="n">be</span> <span class="o">*</span><span class="n">literal</span><span class="o">*</span>
+
+<span class="n">Lots</span>
+<span class="n">of</span>
+<span class="n">lines</span>
+<span class="n">so</span> <span class="n">we</span> <span class="n">can</span> <span class="n">select</span> <span class="n">some</span>
+</pre>
+ </div>
+ </div>
+ <pre class="literal-block" id="literal-ref"><span class="ln">0 </span>Lots
+<span class="ln">1 </span>of</pre>
+ <section id="a-sub-sub-heading">
+ <h3>
+ A Sub-sub-Heading
+ <a class="headerlink" href="#a-sub-sub-heading" title="Permalink to this heading">
+ ¶
+ </a>
+ </h3>
+ <p>
+ some more text
+ </p>
+ </section>
+ </section>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_includes.xml b/tests/test_sphinx/test_sphinx_builds/test_includes.xml
new file mode 100644
index 0000000..1e8779c
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_includes.xml
@@ -0,0 +1,113 @@
+<document source="index.md">
+ <section ids="main-title" names="main\ title">
+ <title>
+ Main Title
+ <target refid="inc-header">
+ <section ids="a-sub-heading-in-include inc-header" names="a\ sub-heading\ in\ include inc_header">
+ <title>
+ A Sub-Heading in Include
+ <paragraph>
+ Some text with
+ <emphasis>
+ syntax
+ <section ids="a-sub-heading-in-nested-include" names="a\ sub-heading\ in\ nested\ include">
+ <title>
+ A Sub-Heading in Nested Include
+ <paragraph>
+ Some other text with
+ <strong>
+ syntax
+ <paragraph>
+ This relative path will refer to the importing file:
+ <figure ids="id1">
+ <image candidates="{'*': 'example1.jpg'}" uri="example1.jpg">
+ <caption>
+ Caption
+ <paragraph>
+ This absolute path will refer to the project root (where the
+ <literal>
+ conf.py
+ is):
+ <figure ids="id2">
+ <image candidates="{'*': 'subfolder/example2.jpg'}" uri="subfolder/example2.jpg">
+ <caption>
+ Caption
+ <paragraph>
+ <image alt="alt" candidates="{'*': 'subfolder/example2.jpg'}" uri="subfolder/example2.jpg">
+ <paragraph>
+ <image alt="alt" candidates="{'?': 'https://example.com'}" uri="https://example.com">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="index.md" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ text
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="inc_header" reftype="ref" refwarn="True">
+ <inline classes="xref std std-ref">
+ inc_header
+ <literal_block classes="code python" source="include_code.py" xml:space="preserve">
+ <inline classes="keyword">
+ def
+
+ <inline classes="name function">
+ a_func
+ <inline classes="punctuation">
+ (
+ <inline classes="name">
+ param
+ <inline classes="punctuation">
+ ):
+
+
+ <inline classes="name builtin">
+ print
+ <inline classes="punctuation">
+ (
+ <inline classes="name">
+ param
+ <inline classes="punctuation">
+ )
+ <literal_block classes="code python" source="include_code.py" xml:space="preserve">
+ <inline classes="ln">
+ 0
+ <inline classes="keyword">
+ def
+
+ <inline classes="name function">
+ a_func
+ <inline classes="punctuation">
+ (
+ <inline classes="name">
+ param
+ <inline classes="punctuation">
+ ):
+
+ <inline classes="ln">
+ 1
+
+ <inline classes="name builtin">
+ print
+ <inline classes="punctuation">
+ (
+ <inline classes="name">
+ param
+ <inline classes="punctuation">
+ )
+ <literal_block source="include_literal.txt" xml:space="preserve">
+ This should be *literal*
+
+ Lots
+ of
+ lines
+ so we can select some
+ <literal_block ids="literal-ref" names="literal_ref" source="include_literal.txt" xml:space="preserve">
+ <inline classes="ln">
+ 0
+ Lots
+ <inline classes="ln">
+ 1
+ of
+ <section ids="a-sub-sub-heading" names="a\ sub-sub-heading">
+ <title>
+ A Sub-sub-Heading
+ <paragraph>
+ some more text
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references.html b/tests/test_sphinx/test_sphinx_builds/test_references.html
new file mode 100644
index 0000000..a6d4036
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_references.html
@@ -0,0 +1,195 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section class="tex2jax_ignore mathjax_ignore" id="title-with-nested-a-1">
+ <span id="title">
+ </span>
+ <h1>
+ Title with
+ <strong>
+ nested
+ </strong>
+ <span class="math notranslate nohighlight">
+ \(a=1\)
+ </span>
+ <a class="headerlink" href="#title-with-nested-a-1" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <p>
+ <a class="reference external" href="https://example.com">
+ </a>
+ </p>
+ <p>
+ <a class="reference external" href="https://example.com">
+ plain text
+ </a>
+ </p>
+ <p>
+ <a class="reference external" href="https://example.com">
+ nested
+ <em>
+ syntax
+ </em>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#title">
+ <span class="std std-ref">
+ Title with nested a=1
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#title">
+ <span class="std std-ref">
+ plain text
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#title">
+ <span class="std std-ref">
+ nested
+ <em>
+ syntax
+ </em>
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#">
+ <span class="doc std std-doc">
+ Title with nested a=1
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#">
+ <span class="doc std std-doc">
+ plain text
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#">
+ <span class="doc std std-doc">
+ nested
+ <em>
+ syntax
+ </em>
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference download internal" download="" href="_downloads/ab0d698fdd2b6a81c34b5ed380fe6f61/file_link.txt">
+ <span class="xref download myst">
+ download
+ <strong>
+ link
+ </strong>
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference download internal" download="" href="_downloads/1952147a8403903cb78cecf56f049085/file_link2.txt">
+ <span class="xref download myst">
+ subfolder/file_link2.txt
+ </span>
+ </a>
+ </p>
+ <p id="insidecodeblock">
+ I am inside the eval-rst fence
+ </p>
+ <p>
+ Referencing the
+ <a class="reference internal" href="#title">
+ <span class="std std-ref">
+ Title with nested a=1
+ </span>
+ </a>
+ </p>
+ <p>
+ Still inside the codeblock
+ <a class="reference internal" href="#insidecodeblock">
+ insidecodeblock
+ </a>
+ </p>
+ <p>
+ I am outside the
+ <a class="reference internal" href="#insidecodeblock">
+ <span class="std std-ref">
+ fence
+ </span>
+ </a>
+ </p>
+ <section id="title-anchors">
+ <h2>
+ Title
+ <em>
+ anchors
+ </em>
+ <a class="headerlink" href="#title-anchors" title="Permalink to this heading">
+ ¶
+ </a>
+ </h2>
+ <div class="toctree-wrapper compound">
+ <ul>
+ <li class="toctree-l1">
+ <a class="reference internal" href="other.html">
+ Title
+ <em>
+ anchors
+ </em>
+ </a>
+ </li>
+ <li class="toctree-l1">
+ <a class="reference internal" href="subfolder/other2.html">
+ Title
+ <em>
+ anchors
+ </em>
+ </a>
+ </li>
+ </ul>
+ </div>
+ <p>
+ <a class="reference internal" href="#title-anchors">
+ <span class="std std-doc">
+ Title anchors
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#title-anchors">
+ <span class="std std-doc">
+ Title anchors
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="other.html#title-anchors">
+ <span class="std std-doc">
+ Title anchors
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="other.html#title-anchors">
+ <span class="std std-doc">
+ Title anchors
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="subfolder/other2.html#title-anchors">
+ <span class="std std-doc">
+ Title anchors
+ </span>
+ </a>
+ </p>
+ </section>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml
new file mode 100644
index 0000000..9c6a4ca
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_references.resolved.xml
@@ -0,0 +1,115 @@
+<document source="index.md">
+ <target refid="title">
+ <section classes="tex2jax_ignore mathjax_ignore" ids="title-with-nested-a-1 title" myst-anchor="index.md#title-with-nested" names="title\ with\ nested\ a=1 title">
+ <title>
+ Title with
+ <strong>
+ nested
+
+ <math>
+ a=1
+ <paragraph>
+ <reference refuri="https://example.com">
+ <paragraph>
+ <reference refuri="https://example.com">
+ plain text
+ <paragraph>
+ <reference refuri="https://example.com">
+ nested
+ <emphasis>
+ syntax
+ <paragraph>
+ <reference internal="True" refid="title">
+ <inline classes="std std-ref">
+ Title with nested a=1
+ <paragraph>
+ <reference internal="True" refid="title">
+ <inline classes="std std-ref">
+ plain text
+ <paragraph>
+ <reference internal="True" refid="title">
+ <inline classes="std std-ref">
+ nested
+ <emphasis>
+ syntax
+ <paragraph>
+ <reference internal="True" refuri="">
+ <inline classes="doc std std-doc">
+ Title with nested a=1
+ <paragraph>
+ <reference internal="True" refuri="">
+ <inline classes="doc std std-doc">
+ plain text
+ <paragraph>
+ <reference internal="True" refuri="">
+ <inline classes="doc std std-doc">
+ nested
+ <emphasis>
+ syntax
+ <paragraph>
+ <download_reference filename="ab0d698fdd2b6a81c34b5ed380fe6f61/file_link.txt" refdoc="index" refdomain="True" refexplicit="True" reftarget="file_link.txt" reftype="myst" refwarn="False">
+ <inline classes="xref download myst">
+ download
+ <strong>
+ link
+ <paragraph>
+ <download_reference filename="1952147a8403903cb78cecf56f049085/file_link2.txt" refdoc="index" refdomain="True" refexplicit="False" reftarget="subfolder/file_link2.txt" reftype="myst" refwarn="False">
+ <inline classes="xref download myst">
+ subfolder/file_link2.txt
+ <target refid="insidecodeblock">
+ <paragraph ids="insidecodeblock" names="insidecodeblock">
+ I am inside the eval-rst fence
+ <paragraph>
+ Referencing the
+ <reference internal="True" refid="title">
+ <inline classes="std std-ref">
+ Title with nested a=1
+ <paragraph>
+ Still inside the codeblock
+ <reference name="insidecodeblock" refid="insidecodeblock">
+ insidecodeblock
+ <paragraph>
+ I am outside the
+ <reference internal="True" refid="insidecodeblock">
+ <inline classes="std std-ref">
+ fence
+ <section ids="title-anchors" myst-anchor="index.md#title-anchors" names="title\ anchors">
+ <title>
+ Title
+ <emphasis>
+ anchors
+ <compound classes="toctree-wrapper">
+ <compact_paragraph toctree="True">
+ <bullet_list>
+ <list_item classes="toctree-l1">
+ <compact_paragraph classes="toctree-l1">
+ <reference anchorname="" internal="True" refuri="other.html">
+ Title
+ <emphasis>
+ anchors
+ <list_item classes="toctree-l1">
+ <compact_paragraph classes="toctree-l1">
+ <reference anchorname="" internal="True" refuri="subfolder/other2.html">
+ Title
+ <emphasis>
+ anchors
+ <paragraph>
+ <reference internal="True" refid="title-anchors">
+ <inline classes="std std-doc">
+ Title anchors
+ <paragraph>
+ <reference internal="True" refid="title-anchors">
+ <inline classes="std std-doc">
+ Title anchors
+ <paragraph>
+ <reference internal="True" refuri="other.html#title-anchors">
+ <inline classes="std std-doc">
+ Title anchors
+ <paragraph>
+ <reference internal="True" refuri="other.html#title-anchors">
+ <inline classes="std std-doc">
+ Title anchors
+ <paragraph>
+ <reference internal="True" refuri="subfolder/other2.html#title-anchors">
+ <inline classes="std std-doc">
+ Title anchors
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references.xml b/tests/test_sphinx/test_sphinx_builds/test_references.xml
new file mode 100644
index 0000000..03bfb8d
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_references.xml
@@ -0,0 +1,95 @@
+<document source="index.md">
+ <target refid="title">
+ <section classes="tex2jax_ignore mathjax_ignore" ids="title-with-nested-a-1 title" myst-anchor="index.md#title-with-nested" names="title\ with\ nested\ a=1 title">
+ <title>
+ Title with
+ <strong>
+ nested
+
+ <math>
+ a=1
+ <paragraph>
+ <reference refuri="https://example.com">
+ <paragraph>
+ <reference refuri="https://example.com">
+ plain text
+ <paragraph>
+ <reference refuri="https://example.com">
+ nested
+ <emphasis>
+ syntax
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="title" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="title" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ plain text
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="title" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ nested
+ <emphasis>
+ syntax
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="index.md" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="index.md" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ plain text
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="index.md" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ nested
+ <emphasis>
+ syntax
+ <paragraph>
+ <download_reference filename="ab0d698fdd2b6a81c34b5ed380fe6f61/file_link.txt" refdoc="index" refdomain="True" refexplicit="True" reftarget="file_link.txt" reftype="myst" refwarn="False">
+ <inline classes="xref download myst">
+ download
+ <strong>
+ link
+ <paragraph>
+ <download_reference filename="1952147a8403903cb78cecf56f049085/file_link2.txt" refdoc="index" refdomain="True" refexplicit="False" reftarget="subfolder/file_link2.txt" reftype="myst" refwarn="False">
+ <inline classes="xref download myst">
+ subfolder/file_link2.txt
+ <target refid="insidecodeblock">
+ <paragraph ids="insidecodeblock" names="insidecodeblock">
+ I am inside the eval-rst fence
+ <paragraph>
+ Referencing the
+ <pending_xref refdoc="index" refdomain="std" refexplicit="False" reftarget="title" reftype="ref" refwarn="True">
+ <inline classes="xref std std-ref">
+ title
+ <paragraph>
+ Still inside the codeblock
+ <reference name="insidecodeblock" refid="insidecodeblock">
+ insidecodeblock
+ <paragraph>
+ I am outside the
+ <pending_xref refdoc="index" refdomain="True" refexplicit="True" reftarget="insidecodeblock" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ fence
+ <section ids="title-anchors" myst-anchor="index.md#title-anchors" names="title\ anchors">
+ <title>
+ Title
+ <emphasis>
+ anchors
+ <compound classes="toctree-wrapper">
+ <toctree caption="True" entries="(None,\ 'other') (None,\ 'subfolder/other2')" glob="False" hidden="False" includefiles="other subfolder/other2" includehidden="False" maxdepth="-1" numbered="0" parent="index" rawentries="" titlesonly="False">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="#title-anchors" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="./#title-anchors" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="./other.md#title-anchors" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="other.md#title-anchors" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="index" refdomain="True" refexplicit="False" reftarget="subfolder/other2.md#title-anchors" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html
new file mode 100644
index 0000000..b3d98a9
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.html
@@ -0,0 +1,111 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <section id="title">
+ <h1>
+ Title
+ <a class="headerlink" href="#title" title="Permalink to this heading">
+ ¶
+ </a>
+ </h1>
+ <div class="toctree-wrapper compound">
+ <span id="document-other/index">
+ </span>
+ <section id="other-index">
+ <h2>
+ Other Index
+ <a class="headerlink" href="#other-index" title="Permalink to this heading">
+ ¶
+ </a>
+ </h2>
+ <div class="toctree-wrapper compound">
+ <span id="document-other/other">
+ </span>
+ <section id="other-title">
+ <h3>
+ Other Title
+ <a class="headerlink" href="#other-title" title="Permalink to this heading">
+ ¶
+ </a>
+ </h3>
+ <p>
+ <a class="reference internal" href="index.html#document-other/other2">
+ <span class="doc">
+ Other 2 Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="index.html#document-other/other2">
+ <span class="doc">
+ Other 2 Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="index.html#document-other/other2">
+ <span class="doc std std-doc">
+ Other 2 Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="index.html#title">
+ <span class="std std-doc">
+ Title
+ </span>
+ </a>
+ </p>
+ </section>
+ <span id="document-other/other2">
+ </span>
+ <section id="other-2-title">
+ <h3>
+ Other 2 Title
+ <a class="headerlink" href="#other-2-title" title="Permalink to this heading">
+ ¶
+ </a>
+ </h3>
+ </section>
+ </div>
+ </section>
+ </div>
+ <p>
+ <a class="reference internal" href="index.html#document-other/other">
+ <span class="doc">
+ Other Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="index.html#document-other/other">
+ <span class="doc">
+ Other Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="index.html#document-other/other">
+ <span class="doc std std-doc">
+ Other Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="#title">
+ <span class="std std-doc">
+ Title
+ </span>
+ </a>
+ </p>
+ <p>
+ <a class="reference internal" href="index.html#other-title">
+ <span class="std std-doc">
+ Other Title
+ </span>
+ </a>
+ </p>
+ </section>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml
new file mode 100644
index 0000000..606e769
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.resolved.xml
@@ -0,0 +1,20 @@
+<document source="other.md">
+ <section ids="other-title" myst-anchor="other/other.md#other-title" names="other\ title">
+ <title>
+ Other Title
+ <paragraph>
+ <reference internal="True" refuri="index.html#document-other/other2">
+ <inline classes="doc">
+ Other 2 Title
+ <paragraph>
+ <reference internal="True" refuri="index.html#document-other/other2">
+ <inline classes="doc doc doc">
+ Other 2 Title
+ <paragraph>
+ <reference internal="True" refuri="index.html#document-other/other2">
+ <inline classes="doc std std-doc">
+ Other 2 Title
+ <paragraph>
+ <reference internal="True" refuri="index.html#document-index#title">
+ <inline classes="std std-doc">
+ Title
diff --git a/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml
new file mode 100644
index 0000000..a209b4f
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_references_singlehtml.xml
@@ -0,0 +1,18 @@
+<document source="other.md">
+ <section ids="other-title" myst-anchor="other/other.md#other-title" names="other\ title">
+ <title>
+ Other Title
+ <paragraph>
+ <pending_xref refdoc="other/other" refdomain="std" refexplicit="False" reftarget="other2" reftype="doc" refwarn="True">
+ <inline classes="xref std std-doc">
+ other2
+ <paragraph>
+ <pending_xref refdoc="other/other" refdomain="" refexplicit="False" reftarget="other2" reftype="any" refwarn="True">
+ <literal classes="xref any">
+ other2
+ <paragraph>
+ <pending_xref refdoc="other/other" refdomain="True" refexplicit="False" reftarget="./other2.md" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
+ <paragraph>
+ <pending_xref refdoc="other/other" refdomain="True" refexplicit="False" reftarget="../index.md#title" reftype="myst" refwarn="True">
+ <inline classes="xref myst">
diff --git a/tests/test_sphinx/test_sphinx_builds/test_substitutions.html b/tests/test_sphinx/test_sphinx_builds/test_substitutions.html
new file mode 100644
index 0000000..c5b3f43
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_substitutions.html
@@ -0,0 +1,82 @@
+<div class="documentwrapper">
+ <div class="bodywrapper">
+ <div class="body" role="main">
+ <p>
+ output with
+ <em>
+ Markdown
+ </em>
+ nested substitution
+ </p>
+ <p>
+ prefix
+ </p>
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ A note nested substitution
+ </p>
+ </div>
+ <p>
+ a - text b
+ </p>
+ <p>
+ c output with
+ <em>
+ Markdown
+ </em>
+ nested substitution
+ d
+ </p>
+ <p>
+ e
+ <div class="admonition note">
+ <p class="admonition-title">
+ Note
+ </p>
+ <p>
+ Inline note
+ </p>
+ </div>
+ f
+ </p>
+ <p>
+ This was from the conf
+ </p>
+ <p>
+ Overridden by front matter
+ </p>
+ <p>
+ This will process the substitution
+ </p>
+ <pre class="literal-block">output with <em>Markdown</em> nested substitution
+</pre>
+ <p>
+ This will not process the substitution
+ </p>
+ <div class="highlight-python notranslate">
+ <div class="highlight">
+ <pre><span></span><span class="p">{{</span> <span class="n">text_with_nest</span> <span class="p">}}</span>
+</pre>
+ </div>
+ </div>
+ <p>
+ Using env and filters:
+ </p>
+ <p>
+ INDEX
+ </p>
+ <div class="toctree-wrapper compound">
+ <ul>
+ <li class="toctree-l1">
+ <a class="reference internal" href="other.html">
+ Other
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/tests/test_sphinx/test_sphinx_builds/test_substitutions.other.xml b/tests/test_sphinx/test_sphinx_builds/test_substitutions.other.xml
new file mode 100644
index 0000000..b8f94f5
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_substitutions.other.xml
@@ -0,0 +1,6 @@
+<document source="other.md">
+ <section ids="other" names="other">
+ <title>
+ Other
+ <paragraph>
+ From conf
diff --git a/tests/test_sphinx/test_sphinx_builds/test_substitutions.xml b/tests/test_sphinx/test_sphinx_builds/test_substitutions.xml
new file mode 100644
index 0000000..9a63ee2
--- /dev/null
+++ b/tests/test_sphinx/test_sphinx_builds/test_substitutions.xml
@@ -0,0 +1,55 @@
+<document source="index.md">
+ <paragraph>
+ output with
+ <emphasis>
+ Markdown
+
+ nested substitution
+ <paragraph>
+ prefix
+ <note>
+ <paragraph>
+ A note
+ nested substitution
+ <paragraph>
+ a
+ - text
+ b
+ <paragraph>
+ c
+ output with
+ <emphasis>
+ Markdown
+
+ nested substitution
+
+ d
+ <paragraph>
+ e
+ <note>
+ <paragraph>
+ Inline note
+ f
+ <paragraph>
+ This was from the conf
+ <paragraph>
+ Overridden by front matter
+ <paragraph>
+ This will process the substitution
+ <literal_block xml:space="preserve">
+ output with
+ <emphasis>
+ Markdown
+
+ nested substitution
+
+ <paragraph>
+ This will not process the substitution
+ <literal_block language="python" xml:space="preserve">
+ {{ text_with_nest }}
+ <paragraph>
+ Using env and filters:
+ <paragraph>
+ INDEX
+ <compound classes="toctree-wrapper">
+ <toctree caption="True" entries="(None,\ 'other')" glob="False" hidden="False" includefiles="other" includehidden="False" maxdepth="-1" numbered="0" parent="index" rawentries="" titlesonly="False">
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..4a0110e
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,63 @@
+# To use tox, see https://tox.readthedocs.io
+# Simply pip or conda install tox
+# If you use conda, you may also want to install tox-conda
+# then run `tox` or `tox -- {pytest args}`
+# To run in parallel using `tox -p` (this does not appear to work for this repo)
+
+# To rebuild the tox environment, for example when dependencies change, use
+# `tox -r`
+
+# Note: if the following error is encountered: `ImportError while loading conftest`
+# then then deleting compiled files has been found to fix it: `find . -name \*.pyc -delete`
+
+[tox]
+envlist = py37-sphinx5
+
+[testenv]
+usedevelop = true
+
+[testenv:py{37,38,39,310}-sphinx{4,5}]
+deps =
+ black
+ flake8
+extras =
+ linkify
+ testing
+commands_pre =
+ sphinx4: pip install --quiet --upgrade-strategy "only-if-needed" "sphinx==4.5.0"
+commands = pytest {posargs}
+
+[testenv:docs-{update,clean}]
+extras =
+ linkify
+ rtd
+whitelist_externals =
+ rm
+ echo
+commands =
+ clean: rm -rf docs/_build
+ sphinx-build -nW --keep-going -b {posargs:html} docs/ docs/_build/{posargs:html}
+commands_post = echo "open file://{toxinidir}/docs/_build/{posargs:html}/index.html"
+
+[testenv:docs-live]
+description = Build the documentation and launch browser
+deps = sphinx-autobuild
+extras =
+ linkify
+ rtd
+commands =
+ sphinx-autobuild \
+ --re-ignore _build/.* \
+ --port 0 --open-browser \
+ -n -b {posargs:html} docs/ docs/_build/{posargs:html}
+
+
+[pytest]
+addopts = --ignore=setup.py
+markers =
+ sphinx: set parameters for the sphinx `app` fixture (see ipypublish/sphinx/tests/conftest.py)
+filterwarnings =
+
+[flake8]
+max-line-length = 100
+extend-ignore = E203