Skip to content

iliaal/mdparser

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

112 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

mdparser

Tests Windows Build Version License: BSD-3-Clause Follow @iliaa

mdparser: ~10-20Γ— faster than pure-PHP

Native C CommonMark + GitHub Flavored Markdown parser for PHP. ~10-20Γ— faster than pure-PHP alternatives (Parsedown, cebe, michelf) on a clean optimized build, targeting CommonMark 0.31 (652/652 spec examples pass; see docs/spec-coverage.md). GFM extensions: tables, strikethrough, task lists, autolinks, tagfilter. Installable via PIE (the PHP Foundation's PECL successor); ships as a single .so. PHP 8.2 minimum, OO API with final classes and readonly options.

πŸ“¦ Install

# PIE (PHP Foundation's extension installer; uses the composer.json
# at the repo root with type: "php-ext")
pie install iliaal/mdparser

On a minimal PHP image (e.g. php:8.x-cli from Docker Hub), PIE needs a few build tools installed first:

# Debian/Ubuntu
sudo apt install -y git bison libtool-bin

# macOS
brew install bison libtool

From source

git clone https://github.com/iliaal/mdparser.git
cd mdparser
phpize && ./configure --enable-mdparser
make -j
sudo make install
echo 'extension=mdparser.so' | sudo tee /etc/php/conf.d/mdparser.ini

Windows binaries

Pre-built DLLs for PHP 8.3, 8.4, and 8.5 (TS/NTS, x86/x64) are attached to each GitHub release.

πŸ› οΈ Usage

use MdParser\Parser;
use MdParser\Options;

// Default parser: safe mode on, GFM extensions on.
$parser = new Parser();
echo $parser->toHtml('# Hello');
// <h1>Hello</h1>

// Custom options via named arguments. All fields readonly.
$parser = new Parser(new Options(
    smart: true,          // --- -> em dash, -- -> en dash, "..." -> curly
    footnotes: true,      // enable [^ref] / [^ref]: syntax
    unsafe: false,        // raw HTML is escaped (default)
));
echo $parser->toHtml($markdown);

// Three output formats from one parser.
$html = $parser->toHtml($markdown);
$xml  = $parser->toXml($markdown);   // CommonMark XML, DOCTYPE-wrapped
$ast  = $parser->toAst($markdown);   // nested arrays, see below

// AST shape is documented in tests/006_ast.phpt. Brief example:
// [
//   'type' => 'document',
//   'children' => [
//     ['type' => 'heading', 'level' => 1, 'children' => [
//        ['type' => 'text', 'literal' => 'Hello'],
//     ]],
//   ],
// ]

πŸ“Š Performance

Against the major pure-PHP Markdown libraries, on PHP 8.4 (clean optimized build, each parser in its default configuration):

Corpus mdparser ops/sec Best pure-PHP ops/sec Speedup
200 B ~530,000 ~26,000 (Parsedown) ~20Γ—
1.8 KB ~110,000 ~6,000 (cebe/GitHub) ~19Γ—
200 KB ~980 ~95 (cebe/GitHub) ~10Γ—

~10-20Γ— faster across the corpora (up to ~45Γ— vs the slowest), from small messages to full 200 KB spec documents. bench/README.md is the source of truth: methodology, all parsers, caveats, league/commonmark notes, and how to reproduce. (Always benchmark a clean optimized PHP build β€” a debug/ASan build inflates these numbers.)

✨ Feature matrix

Comparison with the major pure-PHP Markdown libraries. "via ext" means the feature exists but requires opting in to a non-default extension; "Extra" means the feature ships in the library's Markdown Extra dialect, not its base mode; "βœ—" means the feature is not supported at all.

Feature mdparser Parsedown league/cm core cebe GFM michelf Extra Ciconia
CommonMark core βœ“ partial βœ“ partial partial partial
Fenced code blocks βœ“ βœ“ βœ“ βœ“ βœ“ βœ“
GFM tables βœ“ βœ“ via ext βœ“ via Extra βœ“
Strikethrough βœ“ βœ“ via ext βœ“ βœ— βœ“
Task lists βœ“ βœ— via ext βœ— βœ— βœ“
Autolinks (bare URL) βœ“ βœ“ via ext βœ“ βœ— βœ“
<script> tag filter βœ“ (tagfilter) βœ“ (escaped) via ext partial βœ— βœ—
Smart punctuation βœ“ (Options::smart) βœ— via ext βœ— βœ— βœ—
Footnotes βœ“ (Options::footnotes) Extra via ext βœ— βœ“ Extra plugin
Hardbreaks/nobreaks βœ“ βœ— βœ— βœ— βœ— βœ—
Sourcepos βœ— βœ— βœ“ βœ— βœ— βœ—
Heading anchors βœ“ (Options::headingAnchors) βœ— via ext βœ— βœ— βœ—
rel="nofollow" βœ“ (Options::nofollowLinks) βœ— via ext βœ— βœ— βœ—
HTML output βœ“ βœ“ βœ“ βœ“ βœ“ βœ“
XML output βœ“ βœ— βœ— βœ— βœ— βœ—
AST output βœ“ (arrays) βœ— βœ“ (objects) βœ— βœ— βœ—

Opt-in dialect extensions

Beyond CommonMark + GFM, md4c ships several dialect extensions, each exposed as an opt-in Options flag (all default off, so the standard CommonMark + GFM parse is unaffected): latexMath ($inline$, $$block$$), wikiLinks ([[target]]), spoilers (||text||), underline, highlight (==text==), superscript (^text^), subscript (~text~), and admonitions (GitHub-style > [!NOTE] alert blocks). Plus parser-behavior toggles (noIndentedCodeBlocks, permissiveAtxHeadings, collapseWhitespace). See docs/options.md for behavior and edge cases.

What we don't cover

mdparser is deliberately scoped to CommonMark core plus the GFM extensions. It does not cover the "Markdown Extra" family of features that Parsedown Extra, michelf Markdown Extra, and league/commonmark's optional extensions offer. If you need any of the following, reach for league/commonmark, the most actively-maintained pure-PHP option for extended Markdown:

  • Definition lists (Term :: definition)
  • Abbreviations (*[HTML]: ...)
  • Attribute syntax ({.class #id key="val"})
  • Permalink anchor markup (we emit heading id slugs; we don't inject the inner <a class="anchor"> element GitHub uses for permalinks)
  • Table of contents
  • YAML front matter
  • Mentions (@user)
  • Emoji (:smile:)
  • Fenced admonition containers (::: warning); GitHub-style > [!NOTE] alert blocks are supported via Options::admonitions

These are real features. They're just out of scope for a CommonMark+GFM core parser.

A note on unsafe: true

Options::unsafe = true tells the renderer to pass raw HTML through verbatim instead of escaping or stripping it. The contract for this mode is that you own the input: it is yours, or it comes from a pipeline you trust. headingAnchors and nofollowLinks are applied in-stream as md4c parses the source, so they touch only Markdown-derived nodes; raw HTML you write directly is emitted verbatim and is never rewritten:

  • Heading anchors apply to Markdown headings only. A # heading gets an id slug. A raw <h1>x</h1> block written directly in the source (possible under unsafe: true, tagfilter: false) is raw HTML, not a parsed heading node, so it is emitted untouched and gets no id. A raw heading and a later Markdown heading with the same text do not collide.
  • nofollowLinks applies to Markdown links only. Inline links, reference links, and autolinks get rel="nofollow noopener noreferrer"; in-document fragment anchors (href="#...", including footnote references and backrefs) are skipped. A raw <a href="..."> written directly in the source is passed through verbatim rather than rewritten β€” sanitize raw HTML yourself if you allow it.

Structural outputs are unsanitized

Parser::toXml() and Parser::toAst() return structural representations of the parsed document. Link / image url fields and html_block / html_inline literal text are preserved; XML output escapes those bytes as XML text, while AST output returns them byte-for-byte. The unsafe, tagfilter, and URL-scheme defenses do not make these structural outputs safe to transform back into HTML. If you build HTML out of XML or AST data yourself, you own the sanitization: apply a URL scheme allowlist before emitting href, and run HTML through a sanitizer before emitting raw html_block / html_inline literal text. See docs/ast.md for examples.

πŸ”— PHP Performance Toolkit

Companion native PHP extensions for high-throughput PHP workloads:

  • php_excel: native Excel I/O. 7-10Γ— faster than PhpSpreadsheet, full XLS/XLSX with formulas, formatting, and styling. Powered by LibXL.
  • php_clickhouse: native ClickHouse client speaking the wire protocol directly. Picks up where SeasClick left off.
  • fastchart: native chart-rendering extension. 26 chart types behind one fluent OO API, SVG-canonical with PNG/JPG/WebP output (no libgd dependency).

πŸ“š Read more

Full background, design rationale, and benchmark methodology in the launch post: mdparser: A Native CommonMark + GFM Parser for PHP.

License

  • Wrapper code (mdparser*.c, php_mdparser.h) under BSD 3-Clause.
  • Embedded md4c sources under the MIT license. See LICENSE for aggregated notices.

Follow @iliaa on X β€’ Blog β€’ If this sped up your stack, ⭐ star it!

About

Native C CommonMark + GitHub Flavored Markdown parser for PHP. 100% spec compliance, 15-30x faster than pure-PHP parsers, zero runtime dependencies.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors