xoofx/markdig
A fast, powerful, CommonMark compliant, extensible Markdown processor for .NET
Healthy across the board
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 2d ago
- ✓18 active contributors
- ✓Distributed ownership (top contributor 41% of recent commits)
Show 3 more →Show less
- ✓BSD-2-Clause licensed
- ✓CI configured
- ✓Tests present
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/xoofx/markdig)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/xoofx/markdig on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: xoofx/markdig
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/xoofx/markdig shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across the board
- Last commit 2d ago
- 18 active contributors
- Distributed ownership (top contributor 41% of recent commits)
- BSD-2-Clause licensed
- CI configured
- Tests present
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live xoofx/markdig
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/xoofx/markdig.
What it runs against: a local clone of xoofx/markdig — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in xoofx/markdig | Confirms the artifact applies here, not a fork |
| 2 | License is still BSD-2-Clause | Catches relicense before you depend on it |
| 3 | Default branch main exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 32 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of xoofx/markdig. If you don't
# have one yet, run these first:
#
# git clone https://github.com/xoofx/markdig.git
# cd markdig
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of xoofx/markdig and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "xoofx/markdig(\\.git)?\\b" \\
&& ok "origin remote is xoofx/markdig" \\
|| miss "origin remote is not xoofx/markdig (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(BSD-2-Clause)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"BSD-2-Clause\"" package.json 2>/dev/null) \\
&& ok "license is BSD-2-Clause" \\
|| miss "license drift — was BSD-2-Clause at generation time"
# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
&& ok "default branch main exists" \\
|| miss "default branch main no longer exists"
# 4. Critical files exist
test -f "src/Markdig/Markdown.cs" \\
&& ok "src/Markdig/Markdown.cs" \\
|| miss "missing critical file: src/Markdig/Markdown.cs"
test -f "src/Markdig/MarkdownPipeline.cs" \\
&& ok "src/Markdig/MarkdownPipeline.cs" \\
|| miss "missing critical file: src/Markdig/MarkdownPipeline.cs"
test -f "src/Markdig/Parser/BlockParser.cs" \\
&& ok "src/Markdig/Parser/BlockParser.cs" \\
|| miss "missing critical file: src/Markdig/Parser/BlockParser.cs"
test -f "src/Markdig/Parser/InlineParser.cs" \\
&& ok "src/Markdig/Parser/InlineParser.cs" \\
|| miss "missing critical file: src/Markdig/Parser/InlineParser.cs"
test -f "src/Markdig/Renderers/HtmlRenderer.cs" \\
&& ok "src/Markdig/Renderers/HtmlRenderer.cs" \\
|| miss "missing critical file: src/Markdig/Renderers/HtmlRenderer.cs"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 32 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~2d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/xoofx/markdig"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Markdig is a high-performance, CommonMark-compliant Markdown parser and HTML renderer for .NET written in C# that avoids regex and minimizes garbage collection pressure. It parses Markdown into an Abstract Syntax Tree (AST) with precise source locations and supports 20+ built-in extensions including pipe/grid tables, footnotes, custom containers, and emphasis extras, making it suitable for both simple rendering and advanced use cases like Markdown editors. Modular architecture: core parsing engine in src/Markdig/ with pluggable block/inline parsers (see site/docs/advanced/block-parsers.md, inline-parsers.md); 20+ extensions as separate modules under src/Markdig/Extensions/; test specs in src/Markdig.Tests/Specs/ using markdown files as test input; comprehensive documentation in site/docs/ with separate menus for core CommonMark, extensions, and advanced topics.
👥Who it's for
.NET developers building Markdown processing tools, documentation systems, or editors (e.g., the Markdown Editor v2 extension for Visual Studio 2022 is powered by Markdig). Also targets library maintainers needing a pluggable, extensible CommonMark implementation rather than a fixed parsing behavior.
🌱Maturity & risk
Production-ready and actively maintained. The project passes 600+ CommonMark specification tests (v0.31.2), has CI/CD via GitHub Actions (ci.yml, test-netstandard.yml, site.yml), comprehensive documentation in site/docs/, and a changelog tracking releases. Recent activity evident from organized extension docs and roundtrip parsing support, indicating continued development.
Low risk for stability but single-maintainer (xoofx) — no visible team contributions in file list. No external dependency risks evident (C# standard library based). Main concern is rapid extension ecosystem growth (20+ extensions) could dilute maintenance focus. Roundtrip parsing feature adds complexity that may introduce subtle behavioral changes in future versions.
Active areas of work
Active documentation and extension expansion: alert-blocks, diagrams, and mathematics extensions documented; roundtrip parsing capability added (Roundtrip.md); CI/CD automation refined (separate test-netstandard.yml); Copilot instructions added (.github/copilot-instructions.md, CLAUDE.md), suggesting recent AI-assisted development workflow setup.
🚀Get running
git clone https://github.com/xoofx/markdig.git
cd markdig
dotnet build
dotnet test
Daily commands:
dotnet build src/Markdig/Markdig.csproj
# To run tests:
dotnet test src/Markdig.Tests/Markdig.Tests.csproj
# Example usage (interactive):
dotnet repl # then: Markdig.Markdown.ToHtml("**bold**")
🗺️Map of the codebase
src/Markdig/Markdown.cs— Entry point for the Markdown parser—all parsing operations flow through this class's Parse() and Convert() methods.src/Markdig/MarkdownPipeline.cs— Core extensibility mechanism defining how block/inline parsers and renderers are composed and applied to markdown documents.src/Markdig/Parser/BlockParser.cs— Abstract base for all block-level parsers—fundamental to understanding how markdown structure is parsed.src/Markdig/Parser/InlineParser.cs— Abstract base for inline parsers—handles parsing of text-level markdown constructs within blocks.src/Markdig/Renderers/HtmlRenderer.cs— Main HTML output renderer—demonstrates the renderer abstraction and drives most real-world usage.src/Markdig/Ast/Block.cs— Root AST node for block elements—defines the abstract syntax tree structure that all parsers populate.src/Markdig/Extensions— Extension folder housing modular markdown features (tables, footnotes, emoji, etc.)—illustrates the plugin architecture.
🛠️How to make changes
Add a new block-level extension (e.g., custom container)
- Create a new parser class inheriting from BlockParser in src/Markdig/Parsers/BlockParsers/ (
src/Markdig/Parsers/BlockParsers/CustomBlockParser.cs) - Create AST node classes in src/Markdig/Ast/ to represent the parsed block structure (
src/Markdig/Ast/CustomBlock.cs) - Add a renderer method to HtmlRenderer for your custom AST node (
src/Markdig/Renderers/HtmlRenderer.cs) - Create an extension class that registers your parser and renderer with the MarkdownPipeline (
src/Markdig/Extensions/CustomExtension.cs) - Add tests in src/Markdig.Tests/ with spec files and generated test classes (
src/Markdig.Tests/CustomExtensionSpecs/CustomExtension.md)
Add a new inline-level extension (e.g., custom link handler)
- Create a parser inheriting from InlineParser in src/Markdig/Parsers/InlineParsers/ (
src/Markdig/Parsers/InlineParsers/CustomInlineParser.cs) - Define AST node classes extending Inline in src/Markdig/Ast/ (
src/Markdig/Ast/CustomInline.cs) - Register renderer method in HtmlRenderer or custom renderer class (
src/Markdig/Renderers/HtmlRenderer.cs) - Create extension registration class that adds your parser to the inline parser chain (
src/Markdig/Extensions/CustomInlineExtension.cs)
Enable extensions in the markdown pipeline
- Import extension namespace and call UseExtensionName() on MarkdownPipelineBuilder (
src/Markdig/MarkdownExtensions.cs) - Or manually instantiate extension and call Build() to get configured MarkdownPipeline (
src/Markdig/Markdown.cs) - Pass pipeline to Markdown.Parse() or Markdown.ToHtml() methods (
src/Markdig/Markdown.cs)
Implement a custom renderer (e.g., LaTeX, Docx, JSON AST)
- Create class inheriting from Renderer<T> in src/Markdig/Renderers/ (
src/Markdig/Renderers/CustomRenderer.cs) - Override Write/Render methods for each AST node type (Block, Paragraph, Heading, etc.) (
src/Markdig/Renderers/CustomRenderer.cs) - Instantiate custom renderer and call Render() with parsed MarkdownDocument (
usage-code)
🔧Why these technologies
- .NET (C#) — Cross-platform, JIT-compiled performance, strong type safety, mature ecosystem for text processing.
- No regular expressions in core parser — Reduces GC pressure and allocations; enables streaming/line-by-line parsing for bounded memory footprint.
- AST with source position tracking — Enables rich editor features (syntax highlighting, diagnostics) and roundtrip markdown generation.
- Plugin architecture (MarkdownPipeline, extensions) — CommonMark core remains slim and fast; users pay only for features they enable.
⚖️Trade-offs already made
- No regex in core parser vs. simpler character-by-character logic
- Why: Maximizes throughput and minimizes allocations; aligns with performance goal.
- Consequence: undefined
🪤Traps & gotchas
- Roundtrip parsing (Roundtrip.md) stores trivia in AST nodes — modifying AST and re-rendering may alter whitespace/formatting unexpectedly if trivia not handled. 2. Extension initialization order matters: some extensions modify parser state or depend on other extensions (check MarkdownExtensions.cs for fluent builder sequences). 3. No external package dependencies means no breaking changes from upstream, but also means maintaining all parsing logic in-house. 4. AST source locations are precise but zero-indexed — easy off-by-one errors when integrating with editor APIs expecting 1-indexed positions.
🏗️Architecture
💡Concepts to learn
- CommonMark Specification Compliance — Markdig's core value is passing 600+ CommonMark test cases (v0.31.2); understanding the spec ensures extensions don't break baseline interoperability with other Markdown implementations
- Recursive Descent Parsing — Markdig uses recursive descent instead of regex for parsing; this is why it's fast and GC-friendly, critical to understand when authoring custom block/inline parsers
- Abstract Syntax Tree (AST) with Trivia Preservation — Markdig's roundtrip feature preserves whitespace/newlines (trivia) in the AST; essential for lossless Markdown document editing and for building editor features
- Pipeline/Middleware Pattern — The MarkdownPipeline uses a middleware-style architecture where extensions inject custom parsers/renderers; fundamental to understanding how to extend Markdig
- GitHub Flavored Markdown (GFM) — Markdig includes GFM extensions like pipe tables and fenced code blocks; GFM is the de facto standard for documentation and READMEs, critical for practical Markdown processing
- Garbage Collection Pressure / Memory Allocation Strategy — Markdig's design philosophy emphasizes low GC pressure and minimal allocations; understanding object pooling and allocation patterns is key to maintaining performance for large document processing
- Visitor/Renderer Pattern for AST Traversal — HtmlRenderer and custom renderers use the visitor pattern to traverse the AST; essential for generating output formats (HTML, Markdown, LaTeX) from parsed trees
🔗Related repos
lunet-io/markdig— Fork/mirror of xoofx/markdig used in Lunet static site generator; shows downstream real-world usage for documentation generationCommonMark/cmark-gfm— Reference C implementation of CommonMark with GitHub Flavored Markdown (GFM) extensions; Markdig's spec compliance is tested against thisadam-p/markdown-here— Browser extension for rendering Markdown in email; demonstrates Markdown rendering in production UI, complementary use case to Markdigpandoc/pandoc— Universal document converter with Markdown support; Markdig borrows extension concepts (pipe tables, grid tables, definition lists) from Pandoc's implementationmicrosoft/vscode-markdown-languageservice— Language service for Markdown in VS Code; represents ecosystem integration point for AST-based editors that Markdig's precise source locations enable
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive unit tests for CommonMark spec compliance
The repo has extensive CommonMark documentation at site/docs/commonmark.md and claims CommonMark compliance, but the file structure shows no dedicated test suite for spec validation. The benchmarks directory exists (src/Markdig.Benchmarks) but there's no visible CommonMark specification test harness. This would ensure regressions don't break compliance and help new contributors understand expected behavior.
- [ ] Review site/docs/commonmark.md to identify all spec sections
- [ ] Create src/Markdig.Tests/CommonMarkSpecTests.cs with parametrized tests for each spec section
- [ ] Download or reference the official CommonMark JSON test fixtures (https://spec.commonmark.org/0.30/)
- [ ] Add test cases for edge cases in each extension documented in site/docs/extensions/
- [ ] Update .github/workflows/test-netstandard.yml to run CommonMark spec validation as part of CI
Create integration tests for all documented extensions
The site/docs/extensions/ directory lists 15+ extensions (tables, footnotes, diagrams, mathematics, etc.), but the visible test structure suggests incomplete coverage. Each extension should have dedicated integration tests demonstrating the documented behavior from site/docs/extensions/*.md files. This helps maintainers validate extension behavior and guides contributors on extension patterns.
- [ ] Audit each .md file in site/docs/extensions/ (abbreviations.md, alert-blocks.md, tables.md, etc.)
- [ ] Create src/Markdig.Tests/Extensions/ subdirectory with one test class per extension
- [ ] For each extension, extract code examples from the site/docs files and convert to unit tests
- [ ] Ensure test names match extension behavior documented (e.g., ExtensionNameTests.TestFeatureDescription)
- [ ] Add a test that validates all extensions can be combined without parsing conflicts
Document and test the Parser Authoring API with examples
The site/docs/advanced/parser-authoring-api.md and site/docs/advanced/parser-authoring-api-migration.md files exist, but there are no accompanying code examples or tests in src/. New contributors wanting to extend Markdig have no reference implementations. Creating example extension tests would serve as both documentation and validation.
- [ ] Create src/Markdig.Tests/Advanced/ParserAuthoringApiExamplesTests.cs
- [ ] Implement 3-4 complete custom inline and block parser examples matching site/docs/advanced/parser-authoring-api.md
- [ ] Test custom parser registration via the pipeline (reference site/docs/advanced/pipeline.md)
- [ ] Add tests for the migration scenarios mentioned in parser-authoring-api-migration.md
- [ ] Update site/docs/advanced/parser-authoring-api.md with links to concrete test implementations as runnable examples
🌿Good first issues
- Add missing test specs for the mathematics extension (site/docs/extensions/mathematics.md exists but src/Markdig.Tests/Specs/ lacks MathematicsSpecs.md); review pandoc LaTeX syntax and add round-trip test cases following existing spec patterns.
- Enhance diagram extension documentation (site/docs/extensions/diagrams.md) with concrete before/after examples showing Mermaid/PlantUML rendering; currently underdocumented compared to tables and footnotes.
- Add performance benchmarks for roundtrip parsing vs. regular parsing in site/docs/advanced/performance.md; document memory/speed trade-offs when preserving trivia and provide guidance on when to use each mode.
⭐Top contributors
Click to expand
Top contributors
- @xoofx — 41 commits
- @MihaZupan — 30 commits
- @prozolic — 7 commits
- @Akarinnnnn — 4 commits
- @messani — 3 commits
📝Recent commits
Click to expand
Recent commits
7dceff2— Add opt-in support for nested alert blocks (#938) (kapsiR)3d69933— Attribute with StringSyntax (#937) (SimonCropp)5365879— Fix AbbreviationExtension corrupting emphasis/bold/italic resolution (#935) (#936) (Kryptos-FR)a9bd7c6— Fix to calculate LinkReferenceDefinition span positions from StringLineGroup.Lines (#931) (prozolic)7cb7583— Fix emoji not rendering in table cells (#928) (prozolic)bddaa55— docs(site): document CJK friendly emphasis extension (xoofx)6d5a124— Add CJK-friendly Emphasis Extension (#921) (tats-u)7959e3b— Reject pipe-only rows as pipe table separators (Fixes #927) (xoofx)391e03b— Update readme (xoofx)361231d— Fix ci (xoofx)
🔒Security observations
Markdig appears to be a well-maintained, security-conscious project with reasonable security practices. The main concerns are: (1) a strong name key file in the repository (low risk for open-source), (2) absence of a formal security policy/disclosure process, and (3) standard XSS considerations for any HTML-generating markdown processor. No critical vulnerabilities were identified in the visible file structure. Dependency analysis could not be performed as no package dependency file was provided. The project uses GitHub Actions for CI/CD with no obvious misconfigurations visible.
- Low · Signed Assembly Key File in Repository —
src/Markdig.Signed/key.snk. The file 'src/Markdig.Signed/key.snk' appears to be a strong name key file stored in the repository. While this is sometimes acceptable for open-source projects, it could pose a risk if the private key is compromised or if unauthorized parties gain repository access. Fix: Consider using Azure Key Vault, GitHub Secrets, or other secure key management systems for production signing. If this is intentionally public for open-source purposes, document this decision clearly. - Low · No Security Policy Documented —
Repository root. The repository does not appear to have a SECURITY.md file for responsible disclosure of vulnerabilities. This can hinder security researchers from reporting issues responsibly. Fix: Create a SECURITY.md file that outlines vulnerability disclosure procedures and responsible reporting guidelines. - Low · Potential XSS Risk in Markdown Processing —
src/Markdig (core rendering logic). As a Markdown processor that outputs HTML, Markdig could be vulnerable to XSS attacks if user input is not properly sanitized. The codebase processes markdown to HTML, which is a common injection vector. Fix: Ensure all HTML output is properly sanitized. Review the HTML renderer to confirm that user-controlled content cannot break out of intended HTML context. Consider providing sanitization options in the API.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.