Skip to content

Instantly share code, notes, and snippets.

@jbroadway
Last active February 5, 2024 10:43
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);
}
}
@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!

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