[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"swhDYAsSRh":3},"# lean-markdown\n\nA Markdown parser and HTML renderer for Lean 4. Supports both [CommonMark 0.31.2](https://spec.commonmark.org/0.31.2/) and [GitHub Flavored Markdown (GFM)](https://github.com/github/cmark-gfm/).\n\nSee [A (somewhat) formally verified implementation of Markdown](https://paulbutcher.com/lean-markdown.html)\n\n## Guarantees\n\n- **Conformant**: passes every test in the official CommonMark and cmark-gfm suites, and every test in md4c's suite for the optional LaTeX math extension.\n- **Total**: never panics or loops on any input, including adversarial input.\n- **Safe**: proved to never let an AST leaf's string content produce unescaped HTML markup, or break out of an attribute.\n- **Well-formed**: for input with no embedded raw HTML, output is proved well-formed: balanced tags, no stray `\u003C`/`>`, and every attribute a quoted `name=\"value\"` pair. Rendering is in the XHTML dialect, so that is well-formed XML.\n\nBoth CommonMark and GFM pass raw HTML through verbatim by design. For untrusted input use `renderHtmlSafe`, whose output is proved well-formed for *every* input, adversarial ones included, with no side condition.\n\nSee [KNOWN_ISSUES.md](KNOWN_ISSUES.md).\n\n## Usage\n\n```lean\nimport CommonMark\n\nopen CommonMark\n\ndef main : IO Unit := do\n  let doc := parseDocument \"# Hello\\n\\nSome *emphasis* and a [link](https://example.com).\\n\"\n  IO.println (renderHtml doc)\n```\n\nrenders:\n\n```html\n\u003Ch1>Hello\u003C/h1>\n\u003Cp>Some \u003Cem>emphasis\u003C/em> and a \u003Ca href=\"https://example.com\">link\u003C/a>.\u003C/p>\n```\n\nFor GFM use `GFMarkdown` instead:\n\n```lean\nimport GFMarkdown\n\nopen GFMarkdown\n\ndef main : IO Unit := do\n  let doc := parseDocument \"- [x] Done\\n- [ ] ~~Not~~ Still to do\\n\"\n  IO.println (renderHtml doc)\n```\n\nrenders:\n\n```html\n\u003Cul>\n\u003Cli>\u003Cinput type=\"checkbox\" checked=\"\" disabled=\"\" /> Done\u003C/li>\n\u003Cli>\u003Cinput type=\"checkbox\" disabled=\"\" /> \u003Cdel>Not\u003C/del> Still to do\u003C/li>\n\u003C/ul>\n```\n\nLaTeX math is an optional extension, off by default and independent of GFM, so all four combinations are available through `parseDocumentWith`:\n\n```lean\nopen CommonMark\n\n#eval renderHtml (parseDocumentWith { math := true } \"$x^2$ and $$e=mc^2$$\\n\")\n-- \u003Cp>\u003Cspan class=\"math inline\">\\(x^2\\)\u003C/span> and \u003Cspan class=\"math display\">\\[e=mc^2\\]\u003C/span>\u003C/p>\n```\n\nIt follows [md4c](https://github.com/mity/md4c)'s dialect; output is the pandoc-style `\u003Cspan class=\"math inline\">\\(...\\)\u003C/span>`. `GFMarkdown` has the same `parseDocumentWith`, with GFM's own extensions enabled.\n\nFor untrusted input, use `renderHtmlSafe` instead of `renderHtml`:\n\n```lean\nopen CommonMark\n\n#eval renderHtmlSafe (parseDocument \"\u003Cscript>alert(1)\u003C/script>\\n\\n[x](javascript:alert(1))\\n\")\n-- \u003Cp>\u003C/p>\n-- \u003Cp>\u003Ca href=\"\">x\u003C/a>\u003C/p>\n```\n\n`Document.map`/`Document.fold` (`CommonMark.Ast`) cover whole-tree rewrites and traversals. For localized, cursor-style edits, use the zipper (`BlockZipper`/`InlineZipper` in `CommonMark.Zipper`) instead of walking `Document`/`Block`/`Inline` by hand:\n\n```lean\nopen CommonMark\n\n-- Bolds the first paragraph of a document, leaving everything else untouched.\ndef boldFirstParagraph (doc : Document) : Document :=\n  match BlockZipper.ofDocument doc with\n  | some z =>\n    match z.focus with\n    | .paragraph content => (z.replace (.paragraph [.strong content])).toDocument\n    | _ => doc\n  | none => doc\n```\n\n## Installing\n\nAdd to your `lakefile.toml`:\n\n```toml\n[[require]]\nname = \"markdown\"\ngit = \"https://github.com/paulbutcher/lean-markdown\"\n```\n\n## Development\n\n```\nlake build   # build the library\nlake test    # run the example-suite conformance test and other tests\n```\n\n## Formal verification\n\n- `BlockZipper`/`InlineZipper` round-trip, navigation, and edit laws (`test/ZipperLaws.lean`): navigation steps invert one another, and `replace`/`insertLeft`/`insertRight` change the reconstructed document only at the focus.\n- Newline-normalization algebraic properties (`test/ParserLaws.lean`): output is always `\\r`-free, and normalization is idempotent.\n- HTML well-formedness (`test/HtmlWellFormedness.lean`, `test/GfmHtmlWellFormedness.lean`): for a `Document` with no embedded raw HTML, `renderHtml` produces well-formed HTML (balanced tags, no stray `\u003C`/`>`, every attribute a quoted pair).\n- `renderHtmlSafe` well-formedness (`test/RenderSafeWellFormedness.lean`, `test/GfmRenderSafeWellFormedness.lean`): the same conclusion for *every* `Document`, the hypothesis discharged by what `Document.sanitize` removes.\n- `Document.sanitize` safety (`test/SanitizeSafety.lean`, `test/GfmSanitizeSafety.lean`): its output never contains a `.htmlInline`/`.htmlBlock` leaf, and every `link`/`image` destination in it has an allowlisted URI scheme (or none, i.e. a relative reference).\n- `Document.sanitize` idempotence (`test/SanitizeIdempotence.lean`, `test/GfmSanitizeIdempotence.lean`): sanitizing twice is sanitizing once, so layered defensive calls cost nothing.\n- URI scheme allowlisting is case-insensitive (`test/UriSchemeLaws.lean`): a scheme not on the allowlist is rejected however it is capitalized, `javascript:` included.\n- Fuel laws for the `Block` traversals (`test/AstFuelLaws.lean`, `test/GfmAstFuelLaws.lean`): `Block.listCount` saturates `Block.mapF`/`Block.mapListF`, and sanitizing preserves it. These are what let the well-formedness and sanitize proofs, which pick their fuel independently, be composed.\n- `normalizeMathContent` preserves length (`test/MathProperties.lean`), which is what makes wrongly copying `normalizeCodeSpanContent`'s space-stripping fail to compile.\n\n## Conformance tests\n\n- `test/SpecGuards.lean`: every example in the official CommonMark spec.\n- `test/GfmGuards.lean`: GFM extension examples.\n- `test/GfmRegressionGuards.lean`: regression cases from cmark-gfm.\n- `test/MathGuards.lean`: md4c's own example suite for the LaTeX math extension.\n- `test/MathInteractionGuards.lean`: the flanking, run-length, and construct-interaction cases that suite doesn't reach, authored here with expected output captured from md4c itself.\n\nAll five are generated from the suites under `test/vendor/`; see [test/vendor/README.md](test/vendor/README.md). `test/MathDivergenceGuards.lean` is hand-written instead: it pins the inputs where this library's math output deliberately differs from md4c's, so that \"fixing\" one fails there and forces [KNOWN_ISSUES.md](KNOWN_ISSUES.md) to be updated alongside.\n\n## Property-based testing\n\n- `test/GfmNonEmissionProperties.lean`: [Plausible](https://github.com/leanprover-community/plausible) fuzzing of two claims about parser fallback paths that aren't (yet) formally proven, that randomly generated tables and strikethrough-shaped input never lose text in the rendered output.\n- `test/MathProperties.lean`: three more fuzzed claims, whole-pipeline this time, about the LaTeX math extension: that math-shaped input keeps every `$` while the extension is off (what makes the opt-in real), that switching it on yields a math span carrying the LaTeX source through intact, and that no `$` survives once the delimiters have been consumed.\n- `test/SanitizeExamples.lean`: not fuzzed, but hand-picked examples of `Document.sanitize` neutralizing specific known-dangerous input end-to-end, a behavioral check on top of the proofs that legitimate content is not needlessly lost either.\n\n## License\n\nApache License 2.0; see [LICENSE](LICENSE).\n",1788104603926]