.
class Markdown
{
// Matches a markdown code block
protected $blockCodeRegex = '/```([\s\S]+?)```/S';
// Matches a paragraph/double line break
protected $paragraphRegex = '/(?:\r\n|\r|\n){2}/S';
// Matches markdown inline code
protected $inlineCodeRegex = '/(^|[^a-z0-9`])`((?!`)[\s\S]+?)`([^a-z0-9`]|$)/iS';
// Array for inline code and code block markers
protected $codeMarkers = array (
'block' => array ('marks' => array (), 'count' => 0),
'inline' => array ('marks' => array (), 'count' => 0)
);
// Markdown patterns to search for
protected $search = array (
// Matches **bold** text
'/\*\*([^ *])([\s\S]+?)([^ *])\*\*/S',
// Matches *italic* text
'/\*([^ *])([\s\S]+?)([^ *])\*/S',
// Matches _underlined_ text
'/(^|\W)_((?!_)[\s\S]+?)_(\W|$)/S',
// Matches forced __underlined__ text
'/__([^ _])([\s\S]+?)([^ _])__/S',
// Matches ~~strikethrough~~ text
'/~~([^ ~])([\s\S]+?)([^ ~])~~/S'
);
// HTML replacements for markdown patterns
protected $replace = array (
'\\1\\2\\3',
'\\1\\2\\3',
'\\1\\2\\3',
'\\1\\2\\3',
'\\1\\2\\3'
);
// Replaces markdown for inline code with a marker
protected function codeReplace ($grp, $display)
{
$markName = 'CODE_' . strtoupper ($display);
$markCount = $this->codeMarkers[$display]['count']++;
if ($display !== 'block') {
$codeMarker = $grp[1] . $markName . '[' . $markCount . ']' . $grp[3];
$this->codeMarkers[$display]['marks'][$markCount] = trim ($grp[2], "\r\n");
} else {
$codeMarker = $markName . '[' . $markCount . ']';
$this->codeMarkers[$display]['marks'][$markCount] = trim ($grp[1], "\r\n");
}
return $codeMarker;
}
// Replaces markdown for code block with a marker
protected function blockCodeReplace ($grp)
{
return $this->codeReplace ($grp, 'block');
}
// Replaces markdown for inline code with a marker
protected function inlineCodeReplace ($grp)
{
return $this->codeReplace ($grp, 'inline');
}
// Returns the original inline markdown code with HTML replacement
protected function inlineCodeReturn ($grp)
{
return '' . $this->codeMarkers['inline']['marks'][($grp[1])] . '
';
}
// Returns the original markdown code block with HTML replacement
protected function blockCodeReturn ($grp)
{
return '' . $this->codeMarkers['block']['marks'][($grp[1])] . '
';
}
// Parses a string as markdown
public function parseMarkdown ($string)
{
// Reset marker arrays
$this->codeMarkers = array (
'block' => array ('marks' => array (), 'count' => 0),
'inline' => array ('marks' => array (), 'count' => 0)
);
// Replace code blocks with markers
$string = preg_replace_callback ($this->blockCodeRegex, 'self::blockCodeReplace', $string);
// Break string into paragraphs
$paragraphs = preg_split ($this->paragraphRegex, $string);
// Run through each paragraph
for ($i = 0, $il = count ($paragraphs); $i < $il; $i++) {
// Replace inline code with markers
$paragraphs[$i] = preg_replace_callback ($this->inlineCodeRegex, 'self::inlineCodeReplace', $paragraphs[$i]);
// Replace markdown patterns
$paragraphs[$i] = preg_replace ($this->search, $this->replace, $paragraphs[$i]);
// Replace markers with original markdown code
$paragraphs[$i] = preg_replace_callback ('/CODE_INLINE\[([0-9]+)\]/S', 'self::inlineCodeReturn', $paragraphs[$i]);
}
// Join paragraphs
$string = implode (PHP_EOL . PHP_EOL, $paragraphs);
// Replace code block markers with original markdown code
$string = preg_replace_callback ('/CODE_BLOCK\[([0-9]+)\]/S', 'self::blockCodeReturn', $string);
return $string;
}
}