. 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; } }