NAME

Markdown::Simple - Markdown to HTML

VERSION

Version 0.22

SYNOPSIS

use Markdown::Simple;

# Functional interface. GFM by default (tables, strikethrough,
# task lists, autolinks, disallow-raw-html). Output is
# CommonMark/GFM-conformant HTML.
my $html = markdown_to_html($markdown);

# Strict CommonMark.
my $cm = markdown_to_html($markdown, { gfm => 0 });

# Opt-out an individual extension.
my $no_tables = markdown_to_html($markdown, { tables => 0 });

# Extras off by default.
my $hb = markdown_to_html($markdown, { hard_breaks => 1 });

my $plain = strip_markdown($markdown);

# Object interface — keeps the parser's arena warm between
# render() calls. Use this when converting many documents in a
# loop with the same options.
my $md = Markdown::Simple->new({ gfm => 1 });
for my $doc (@docs) {
    print $md->render($doc);
}

DESCRIPTION

Markdown::Simple is a Perl XS module that converts Markdown text to HTML. The default rendering mode is GitHub Flavored Markdown (GFM); options let you switch to strict CommonMark or toggle individual extensions.

Two entry points are provided: the exported procedural function "markdown_to_html", and the object-oriented "new" / "render" pair, which is preferable when you intend to render many documents in the same process because it reuses the underlying parser arena.

FUNCTIONS

markdown_to_html

markdown_to_html($markdown);
markdown_to_html($markdown, \%options);

Converts the given Markdown text to HTML. The optional second argument is a hash reference of feature flags.

The available options are:

  • gfm - preset switch. Defaults to enabled. gfm => 0 selects strict CommonMark (turns tables, strikethrough, task lists, autolink, and disallow-raw-html off). Individual feature toggles below still override.

  • tables - GFM tables (default: on in GFM mode)

  • strikethrough - GFM ~~strike~~ rendering (default: on in GFM mode)

  • tasklist - GFM - [ ] task list items (default: on in GFM mode)

  • autolink - GFM bare-URL autolink scanner (default: on in GFM mode)

  • disallow_raw_html - escape disallowed raw HTML tags (default: on in GFM mode)

  • hard_breaks - emit <br /> for soft line breaks (default: off)

  • unsafe - allow javascript:, vbscript:, data: URLs and other otherwise-stripped dangerous content (default: off)

  • no_simd - disable SIMD fast paths (default: off; useful for debugging)

  • strict_utf8 - reject input that is not well-formed UTF-8 with a fatal croak instead of silently producing best-effort output (default: off)

  • highlight - syntax-highlight fenced code blocks that carry a language tag (e.g. ```perl). Recognised tokens are wrapped in <span class="esh-X"> elements; the content remains fully HTML-safe. Requires Eshu to be installed. Blocks with no language tag are unaffected. (default: off)

  • heading_ids - give every heading an anchor, as <h2 id="the-slug">. The slug follows GitHub's rule so that existing cross-document links keep working: the heading's plain text lowercased, ASCII punctuation dropped, runs of whitespace collapsed to a single hyphen. Inline markup does not reach the slug, so ## The **bold** bit anchors as the-bold-bit. Bytes above ASCII pass through unchanged rather than being mangled by a partial case fold. Slugs are unique within a document; a repeat gets -1, -2 and so on. (default: off)

Per-syntax disables

Pass any of the following with a false value (feature => 0) to make the corresponding Markdown construct fall through as literal text instead of being recognised. All default to enabled.

  • headers - ATX (#) and Setext (===/---) headings

  • thematic_break - ---, ***, ___ horizontal rules

  • fenced_code - ``` / ~~~ fenced code blocks

  • indented_code - 4-space indented code blocks

  • blockquote - > quoted blocks

  • ordered_lists - 1., 2. ordered lists

  • unordered_lists - -, *, + bullet lists

  • html - raw HTML blocks and inline HTML

  • references - link reference definitions ([id]: url)

  • bold - **strong** / __strong__

  • italic - *em* / _em_

  • code - inline `code` spans

  • links - [text](url) and reference links

  • images - ![alt](src)

strip_markdown

strip_markdown($markdown);

Removes all Markdown formatting from the given text, returning plain text. List markers, table pipes, and table separator rows are preserved so that the output stays scan-readable; bold/italic/strike/code/link/image delimiters are stripped while their textual content is retained.

OBJECT INTERFACE

For workloads that render many documents in succession, the object interface keeps the parser's internal arena and scratch buffers warm between calls, eliminating the per-render malloc/free traffic incurred by the procedural entry point.

new

my $md = Markdown::Simple->new(\%options);
my $md = Markdown::Simple->new;            # GFM defaults

Constructs a persistent renderer. \%options is the same hash reference accepted by "markdown_to_html"; flags are decoded once at construction and reused on every render call.

render

my $html = $md->render($markdown);

Converts $markdown to HTML using the options bound at construction time. The arena's head page is reset (not freed) between calls so that allocations within a typical document complete without touching the system allocator.

render_with_toc

my ($html, $headings) = $md->render_with_toc($markdown);

Renders as "render" does, and additionally returns the document's headings as an arrayref of hashrefs, in document order:

[ { level => 1, text => 'Title',     id => 'title'     },
  { level => 2, text => 'First bit', id => 'first-bit' } ]

text is the heading's plain text with inline markup stripped, and id is the anchor that was emitted on the <hN> tag, so a table of contents built from this list links into the document without re-deriving anything.

Asking for the list turns "heading_ids" on for that render whatever the session was constructed with, since a list of anchors that were never emitted would be of no use.

DESTROY

Releases the C-side arena and scratch buffers. Called automatically by Perl when the object goes out of scope; you do not need to invoke it explicitly.

EXAMPLES

use Markdown::Simple qw(markdown_to_html);

my $markdown = "This is **bold** text and this is *italic* text.";
my $html = markdown_to_html($markdown);
print $html; # <p>This is <strong>bold</strong> text and this is <em>italic</em> text.</p>

# Switch to strict CommonMark (no GFM extensions).
my $cm = markdown_to_html("see http://x\n", { gfm => 0 });

# Reuse a single parser for many documents.
my $md = Markdown::Simple->new;
print $md->render($_) for @docs;

# Anchors and a table of contents to link into them.
my ($html, $toc) = Markdown::Simple->new->render_with_toc($markdown);
print qq{<a href="#$_->{id}">$_->{text}</a>\n} for @$toc;

C ABI

An XS module can render through the parser without a Perl frame in between. include/mds_abi.h declares a function-pointer table with five entries:

  • flags_from_hv - decode an options hashref into the MDS_FLAG_* bitmask, applying the same defaults "markdown_to_html" does. A NULL hash gives the GFM preset.

  • session_of - the opaque session behind a blessed Markdown::Simple object. Returns NULL for anything that is not one rather than croaking, so it is safe to probe with on a fall-back path.

  • session_render - render through a session, reusing its warm arena and scratch buffers. Takes an optional AV * which, when supplied, collects one { level, text, id } hashref per heading and turns anchors on for that render.

  • render - the same with no session: a local arena, allocated and freed around the call.

  • strip - "strip_markdown", for search indexing and result snippets, where indexing the HTML would mean indexing the tags.

The table is resolved at runtime through Markdown::Simple::_abi_ptr and gated on its abi_version, so there is no link-time coupling and the two distributions upgrade independently; entries are only ever appended. Reach the header with ExtUtils::Depends:

my $pkg = ExtUtils::Depends->new('My::Module', 'Markdown::Simple');

Nothing in the table croaks. Errors arrive through an SV **err out-parameter, so a consumer can turn a bad document into its own error response rather than an exception it has to catch.

Hold a reference to the object and call session_of on each render rather than caching the session pointer, so an object that was replaced or cloned cannot leave you with a stale handle. The lookup is a walk of the object's magic chain, which is cheap enough to mean that.

Every entry deals in raw bytes and none of them sets the UTF-8 flag on anything. The parser is byte-oriented and the caller is the one who knows whether its input was characters or octets, so the flag is the caller's to set. For a consumer writing an HTTP response body this is the behaviour you want, since the body must be bytes and Content-Length counts bytes.

Note that a template engine which encodes its own output will double encode anything you hand it from here. Decode on the way in and let it do the single encode.

CONCURRENCY

The renderer keeps a file-scope image-nesting stack, so two interpreters must not render concurrently. One session per thread or worker is the supported shape, and is what the object interface is for.

AUTHOR

LNATION, <email at lnation.org>

BUGS

Please report any bugs or feature requests to bug-markdown-simple at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Markdown-Simple. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Markdown::Simple

You can also look for information at:

LICENSE AND COPYRIGHT

This software is Copyright (c) 2025 by LNATION.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)