1、建立一个基本类 Hyperdown

<?phpnamespace jplt\markdown;/*** Parser** @copyright Copyright (c) 2012 SegmentFault Team. (http://segmentfault.com)* @author Joyqi <joyqi@segmentfault.com>* @license BSD License*/
class Hyperdown
{/*** _whiteList** @var string*/public $_commonWhiteList = 'kbd|b|i|strong|em|sup|sub|br|code|del|a|hr|small';/*** html tags** @var string*/public $_blockHtmlTags = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption|svg|script|noscript';/*** _specialWhiteList** @var mixed* @access private*/public $_specialWhiteList = array('table' => 'table|tbody|thead|tfoot|tr|td|th');/*** _footnotes** @var array*/public $_footnotes;/*** @var bool*/public $_html = false;/*** @var bool*/public $_line = false;/*** @var array*/public $blockParsers = array(array('code', 10),array('shtml', 20),array('pre', 30),array('ahtml', 40),array('list', 50),array('math', 60),array('html', 70),array('footnote', 80),array('definition', 90),array('quote', 100),array('table', 110),array('sh', 120),array('mh', 130),array('hr', 140),array('default', 9999));/*** _blocks** @var array*/private $_blocks;/*** _current** @var string*/private $_current;/*** _pos** @var int*/private $_pos;/*** _definitions** @var array*/public $_definitions;/*** @var array*/private $_hooks = array();/*** @var array*/private $_holders;/*** @var string*/private $_uniqid;/*** @var int*/private $_id;/*** @var array*/private $_parsers = array();/*** makeHtml** @param mixed $text* @return string*/public function makeHtml($text){$this->_footnotes = array();$this->_definitions = array();$this->_holders = array();$this->_uniqid = md5(uniqid());$this->_id = 0;usort($this->blockParsers, function ($a, $b) {return $a[1] < $b[1] ? -1 : 1;});foreach ($this->blockParsers as $parser) {list ($name) = $parser;if (isset($parser[2])) {$this->_parsers[$name] = $parser[2];} else {$this->_parsers[$name] = array($this, 'parseBlock' . ucfirst($name));}}$text = $this->initText($text);$html = $this->parse($text);$html = $this->makeFootnotes($html);$html = $this->optimizeLines($html);return $this->call('makeHtml', $html);}/*** @param $html*/public function enableHtml($html = true){$this->_html = $html;}/*** @param bool $line*/public function enableLine($line = true){$this->_line = $line;}/*** @param $type* @param $callback*/public function hook($type, $callback){$this->_hooks[$type][] = $callback;}/*** @param $str* @return string*/public function makeHolder($str){$key = "\r" . $this->_uniqid . $this->_id . "\r";$this->_id++;$this->_holders[$key] = $str;return $key;}/*** @param $text* @return mixed*/private function initText($text){$text = str_replace(array("\t", "\r"), array('    ', ''), $text);return $text;}/*** @param $html* @return string*/private function makeFootnotes($html){if (count($this->_footnotes) > 0) {$html .= '<div class="footnotes"><hr><ol>';$index = 1;while ($val = array_shift($this->_footnotes)) {if (is_string($val)) {$val .= " <a href=\"#fnref-{$index}\" class=\"footnote-backref\">&#8617;</a>";} else {$val[count($val) - 1] .= " <a href=\"#fnref-{$index}\" class=\"footnote-backref\">&#8617;</a>";$val = count($val) > 1 ? $this->parse(implode("\n", $val)) : $this->parseInline($val[0]);}$html .= "<li id=\"fn-{$index}\">{$val}</li>";$index++;}$html .= '</ol></div>';}return $html;}/*** parse** @param string $text* @param bool $inline* @param int $offset* @return string*/private function parse($text, $inline = false, $offset = 0){$blocks = $this->parseBlock($text, $lines);$html = '';// inline mode for single normal blockif ($inline && count($blocks) == 1 && $blocks[0][0] == 'normal') {$blocks[0][3] = true;}foreach ($blocks as $block) {list ($type, $start, $end, $value) = $block;$extract = array_slice($lines, $start, $end - $start + 1);$method = 'parse' . ucfirst($type);$extract = $this->call('before' . ucfirst($method), $extract, $value);$result = $this->{$method}($extract, $value, $start + $offset, $end + $offset);$result = $this->call('after' . ucfirst($method), $result, $value);$html .= $result;}return $html;}/*** @param $text* @param $clearHolders* @return string*/private function releaseHolder($text, $clearHolders = true){$deep = 0;while (strpos($text, "\r") !== false && $deep < 10) {$text = str_replace(array_keys($this->_holders), array_values($this->_holders), $text);$deep++;}if ($clearHolders) {$this->_holders = array();}return $text;}/*** @param $start* @param int $end* @return string*/public function markLine($start, $end = -1){if ($this->_line) {$end = $end < 0 ? $start : $end;return '<span class="line" data-start="' . $start. '" data-end="' . $end . '" data-id="' . $this->_uniqid . '"></span>';}return '';}/*** @param array $lines* @param $start* @return string*/public function markLines(array $lines, $start){$i = -1;$self = $this;return $this->_line ? array_map(function ($line) use ($self, $start, &$i) {$i++;return $self->markLine($start + $i) . $line;}, $lines) : $lines;}/*** @param $html* @return string*/public function optimizeLines($html){$last = 0;return $this->_line ?preg_replace_callback("/class=\"line\" data\-start=\"([0-9]+)\" data\-end=\"([0-9]+)\" (data\-id=\"{$this->_uniqid}\")/",function ($matches) use (&$last) {if ($matches[1] != $last) {$replace = 'class="line" data-start="' . $last . '" data-start-original="' . $matches[1] . '" data-end="' . $matches[2] . '" ' . $matches[3];} else {$replace = $matches[0];}$last = $matches[2] + 1;return $replace;}, $html) : $html;}/*** @param $type* @param $value* @return mixed*/public function call($type, $value){if (empty($this->_hooks[$type])) {return $value;}$args = func_get_args();$args = array_slice($args, 1);foreach ($this->_hooks[$type] as $callback) {$value = call_user_func_array($callback, $args);$args[0] = $value;}return $value;}/*** parseInline** @param string $text* @param string $whiteList* @param bool $clearHolders* @param bool $enableAutoLink* @return string*/public function parseInline($text, $whiteList = '', $clearHolders = true, $enableAutoLink = true){$self = $this;$text = $this->call('beforeParseInline', $text);// code$text = preg_replace_callback("/(^|[^\\\])(`+)(.+?)\\2/",function ($matches) use ($self) {return $matches[1] . $self->makeHolder('<code>' . htmlspecialchars($matches[3]) . '</code>');},$text);// mathjax$text = preg_replace_callback("/(^|[^\\\])(\\$+)(.+?)\\2/",function ($matches) use ($self) {return $matches[1] . $self->makeHolder($matches[2] . htmlspecialchars($matches[3]) . $matches[2]);},$text);// escape$text = preg_replace_callback("/\\\(.)/u",function ($matches) use ($self) {$escaped = htmlspecialchars($matches[1]);$escaped = str_replace('$', '&dollar;', $escaped);return $self->makeHolder($escaped);},$text);// link$text = preg_replace_callback("/<(https?:\/\/.+)>/i",function ($matches) use ($self) {$url = $self->cleanUrl($matches[1]);$link = $self->call('parseLink', $matches[1]);return $self->makeHolder("<a href=\"{$url}\">{$link}</a>");},$text);// encode unsafe tags$text = preg_replace_callback("/<(\/?)([a-z0-9-]+)(\s+[^>]*)?>/i",function ($matches) use ($self, $whiteList) {if ($self->_html || false !== stripos('|' . $self->_commonWhiteList . '|' . $whiteList . '|', '|' . $matches[2] . '|')) {return $self->makeHolder($matches[0]);} else {return htmlspecialchars($matches[0]);}},$text);if ($this->_html) {$text = preg_replace_callback("/<!\-\-(.*?)\-\->/", function ($matches) use ($self) {return $self->makeHolder($matches[0]);}, $text);}$text = str_replace(array('<', '>'), array('&lt;', '&gt;'), $text);// footnote$text = preg_replace_callback("/\[\^((?:[^\]]|\\\\\]|\\\\\[)+?)\]/",function ($matches) use ($self) {$id = array_search($matches[1], $self->_footnotes);if (false === $id) {$id = count($self->_footnotes) + 1;$self->_footnotes[$id] = $self->parseInline($matches[1], '', false);}return $self->makeHolder("<sup id=\"fnref-{$id}\"><a href=\"#fn-{$id}\" class=\"footnote-ref\">{$id}</a></sup>");},$text);// image$text = preg_replace_callback("/!\[((?:[^\]]|\\\\\]|\\\\\[)*?)\]\(((?:[^\)]|\\\\\)|\\\\\()+?)\)/",function ($matches) use ($self) {$escaped = htmlspecialchars($self->escapeBracket($matches[1]));$url = $self->escapeBracket($matches[2]);$url = $self->cleanUrl($url);return $self->makeHolder("<img src=\"{$url}\" alt=\"{$escaped}\" title=\"{$escaped}\">");},$text);$text = preg_replace_callback("/!\[((?:[^\]]|\\\\\]|\\\\\[)*?)\]\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]/",function ($matches) use ($self) {$escaped = htmlspecialchars($self->escapeBracket($matches[1]));$result = isset($self->_definitions[$matches[2]]) ?"<img src=\"{$self->_definitions[$matches[2]]}\" alt=\"{$escaped}\" title=\"{$escaped}\">": $escaped;return $self->makeHolder($result);},$text);// link$text = preg_replace_callback("/\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]\(((?:[^\)]|\\\\\)|\\\\\()+?)\)/",function ($matches) use ($self) {$escaped = $self->parseInline($self->escapeBracket($matches[1]), '', false, false);$url = $self->escapeBracket($matches[2]);$url = $self->cleanUrl($url);return $self->makeHolder("<a href=\"{$url}\">{$escaped}</a>");},$text);$text = preg_replace_callback("/\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]/",function ($matches) use ($self) {$escaped = $self->parseInline($self->escapeBracket($matches[1]), '', false);$result = isset($self->_definitions[$matches[2]]) ?"<a href=\"{$self->_definitions[$matches[2]]}\">{$escaped}</a>": $escaped;return $self->makeHolder($result);},$text);// strong and em and some fuck$text = $this->parseInlineCallback($text);$text = preg_replace("/<([_a-z0-9-\.\+]+@[^@]+\.[a-z]{2,})>/i","<a href=\"mailto:\\1\">\\1</a>",$text);// autolink urlif ($enableAutoLink) {$text = preg_replace_callback("/(^|[^\"])((https?):[\p{L}_0-9-\.\/%#!@\?\+=~\|\,&\(\)]+)($|[^\"])/iu",function ($matches) use ($self) {$link = $self->call('parseLink', $matches[2]);return "{$matches[1]}<a href=\"{$matches[2]}\">{$link}</a>{$matches[4]}";},$text);}$text = $this->call('afterParseInlineBeforeRelease', $text);$text = $this->releaseHolder($text, $clearHolders);$text = $this->call('afterParseInline', $text);return $text;}/*** @param $text* @return mixed*/public function parseInlineCallback($text){$self = $this;$text = preg_replace_callback("/(\*{3})(.+?)\\1/",function ($matches) use ($self) {return '<strong><em>' .$self->parseInlineCallback($matches[2]) .'</em></strong>';},$text);$text = preg_replace_callback("/(\*{2})(.+?)\\1/",function ($matches) use ($self) {return '<strong>' .$self->parseInlineCallback($matches[2]) .'</strong>';},$text);$text = preg_replace_callback("/(\*)(.+?)\\1/",function ($matches) use ($self) {return '<em>' .$self->parseInlineCallback($matches[2]) .'</em>';},$text);$text = preg_replace_callback("/(\s+|^)(_{3})(.+?)\\2(\s+|$)/",function ($matches) use ($self) {return $matches[1] . '<strong><em>' .$self->parseInlineCallback($matches[3]) .'</em></strong>' . $matches[4];},$text);$text = preg_replace_callback("/(\s+|^)(_{2})(.+?)\\2(\s+|$)/",function ($matches) use ($self) {return $matches[1] . '<strong>' .$self->parseInlineCallback($matches[3]) .'</strong>' . $matches[4];},$text);$text = preg_replace_callback("/(\s+|^)(_)(.+?)\\2(\s+|$)/",function ($matches) use ($self) {return $matches[1] . '<em>' .$self->parseInlineCallback($matches[3]) .'</em>' . $matches[4];},$text);$text = preg_replace_callback("/(~{2})(.+?)\\1/",function ($matches) use ($self) {return '<del>' .$self->parseInlineCallback($matches[2]) .'</del>';},$text);return $text;}/*** parseBlock** @param string $text* @param array $lines* @return array*/private function parseBlock($text, &$lines){$lines = explode("\n", $text);$this->_blocks = array();$this->_current = 'normal';$this->_pos = -1;$state = array('special' => implode("|", array_keys($this->_specialWhiteList)),'empty'   => 0,'html'    => false);// analyze by lineforeach ($lines as $key => $line) {$block = $this->getBlock();$args = array($block, $key, $line, &$state, $lines);if ($this->_current != 'normal') {$pass = call_user_func_array($this->_parsers[$this->_current], $args);if (!$pass) {continue;}}foreach ($this->_parsers as $name => $parser) {if ($name != $this->_current) {$pass = call_user_func_array($parser, $args);if (!$pass) {break;}}}}return $this->optimizeBlocks($this->_blocks, $lines);}/*** @param $block* @param $key* @param $line* @param $state* @return bool*/private function parseBlockList($block, $key, $line, &$state){if (preg_match("/^(\s*)((?:[0-9]+\.)|\-|\+|\*)\s+/i", $line, $matches)) {$space = strlen($matches[1]);$state['empty'] = 0;// openedif ($this->isBlock('list')) {$this->setBlock($key, $space);} else {$this->startBlock('list', $key, $space);}return false;} else if ($this->isBlock('list') && !preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line)) {if ($state['empty'] <= 1&& preg_match("/^(\s+)/", $line, $matches)&& strlen($matches[1]) > $block[3]) {$state['empty'] = 0;$this->setBlock($key);return false;} else if (preg_match("/^(\s*)$/", $line) && $state['empty'] == 0) {$state['empty']++;$this->setBlock($key);return false;}}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockCode($block, $key, $line){if (preg_match("/^(\s*)(~{3,}|`{3,})([^`~]*)$/i", $line, $matches)) {if ($this->isBlock('code')) {$isAfterList = $block[3][2];if ($isAfterList) {$this->combineBlock()->setBlock($key);} else {$this->setBlock($key)->endBlock();}} else {$isAfterList = false;if ($this->isBlock('list')) {$space = $block[3];$isAfterList = ($space > 0 && strlen($matches[1]) >= $space)|| strlen($matches[1]) > $space;}$this->startBlock('code', $key, array($matches[1], $matches[3], $isAfterList));}return false;} else if ($this->isBlock('code')) {$this->setBlock($key);return false;}return true;}/*** @param $block* @param $key* @param $line* @param $state* @return bool*/private function parseBlockShtml($block, $key, $line, &$state){if ($this->_html) {if (preg_match("/^(\s*)!!!(\s*)$/", $line, $matches)) {if ($this->isBlock('shtml')) {$this->setBlock($key)->endBlock();} else {$this->startBlock('shtml', $key);}return false;} else if ($this->isBlock('shtml')) {$this->setBlock($key);return false;}}return true;}/*** @param $block* @param $key* @param $line* @param $state* @return bool*/private function parseBlockAhtml($block, $key, $line, &$state){if ($this->_html) {if (preg_match("/^\s*<({$this->_blockHtmlTags})(\s+[^>]*)?>/i", $line, $matches)) {if ($this->isBlock('ahtml')) {$this->setBlock($key);return false;} else if (empty($matches[2]) || $matches[2] != '/') {$this->startBlock('ahtml', $key);preg_match_all("/<({$this->_blockHtmlTags})(\s+[^>]*)?>/i", $line, $allMatches);$lastMatch = $allMatches[1][count($allMatches[0]) - 1];if (strpos($line, "</{$lastMatch}>") !== false) {$this->endBlock();} else {$state['html'] = $lastMatch;}return false;}} else if (!!$state['html'] && strpos($line, "</{$state['html']}>") !== false) {$this->setBlock($key)->endBlock();$state['html'] = false;return false;} else if ($this->isBlock('ahtml')) {$this->setBlock($key);return false;} else if (preg_match("/^\s*<!\-\-(.*?)\-\->\s*$/", $line, $matches)) {$this->startBlock('ahtml', $key)->endBlock();return false;}}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockMath($block, $key, $line){if (preg_match("/^(\s*)\\$\\$(\s*)$/", $line, $matches)) {if ($this->isBlock('math')) {$this->setBlock($key)->endBlock();} else {$this->startBlock('math', $key);}return false;} else if ($this->isBlock('math')) {$this->setBlock($key);return false;}return true;}/*** @param $block* @param $key* @param $line* @param $state* @return bool*/private function parseBlockPre($block, $key, $line, &$state){if (preg_match("/^ {4}/", $line)) {if ($this->isBlock('pre')) {$this->setBlock($key);} else {$this->startBlock('pre', $key);}return false;} else if ($this->isBlock('pre') && preg_match("/^\s*$/", $line)) {$this->setBlock($key);return false;}return true;}/*** @param $block* @param $key* @param $line* @param $state* @return bool*/private function parseBlockHtml($block, $key, $line, &$state){if (preg_match("/^\s*<({$state['special']})(\s+[^>]*)?>/i", $line, $matches)) {$tag = strtolower($matches[1]);if (!$this->isBlock('html', $tag) && !$this->isBlock('pre')) {$this->startBlock('html', $key, $tag);}return false;} else if (preg_match("/<\/({$state['special']})>\s*$/i", $line, $matches)) {$tag = strtolower($matches[1]);if ($this->isBlock('html', $tag)) {$this->setBlock($key)->endBlock();}return false;} else if ($this->isBlock('html')) {$this->setBlock($key);return false;}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockFootnote($block, $key, $line){if (preg_match("/^\[\^((?:[^\]]|\\]|\\[)+?)\]:/", $line, $matches)) {$space = strlen($matches[0]) - 1;$this->startBlock('footnote', $key, array($space, $matches[1]));return false;}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockDefinition($block, $key, $line){if (preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line, $matches)) {$this->_definitions[$matches[1]] = $this->cleanUrl($matches[2]);$this->startBlock('definition', $key)->endBlock();return false;}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockQuote($block, $key, $line){if (preg_match("/^(\s*)>/", $line, $matches)) {if ($this->isBlock('list') && strlen($matches[1]) > 0) {$this->setBlock($key);} else if ($this->isBlock('quote')) {$this->setBlock($key);} else {$this->startBlock('quote', $key);}return false;}return true;}/*** @param $block* @param $key* @param $line* @param $state* @param $lines* @return bool*/private function parseBlockTable($block, $key, $line, &$state, $lines){if (preg_match("/^((?:(?:(?:\||\+)(?:[ :]*\-+[ :]*)(?:\||\+))|(?:(?:[ :]*\-+[ :]*)(?:\||\+)(?:[ :]*\-+[ :]*))|(?:(?:[ :]*\-+[ :]*)(?:\||\+))|(?:(?:\||\+)(?:[ :]*\-+[ :]*)))+)$/", $line, $matches)) {if ($this->isBlock('table')) {$block[3][0][] = $block[3][2];$block[3][2]++;$this->setBlock($key, $block[3]);} else {$head = 0;if (empty($block) ||$block[0] != 'normal' ||preg_match("/^\s*$/", $lines[$block[2]])) {$this->startBlock('table', $key);} else {$head = 1;$this->backBlock(1, 'table');}if ($matches[1][0] == '|') {$matches[1] = substr($matches[1], 1);if ($matches[1][strlen($matches[1]) - 1] == '|') {$matches[1] = substr($matches[1], 0, -1);}}$rows = preg_split("/(\+|\|)/", $matches[1]);$aligns = array();foreach ($rows as $row) {$align = 'none';if (preg_match("/^\s*(:?)\-+(:?)\s*$/", $row, $matches)) {if (!empty($matches[1]) && !empty($matches[2])) {$align = 'center';} else if (!empty($matches[1])) {$align = 'left';} else if (!empty($matches[2])) {$align = 'right';}}$aligns[] = $align;}$this->setBlock($key, array(array($head), $aligns, $head + 1));}return false;}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockSh($block, $key, $line){if (preg_match("/^(#+)(.*)$/", $line, $matches)) {$num = min(strlen($matches[1]), 6);$this->startBlock('sh', $key, $num)->endBlock();return false;}return true;}/*** @param $block* @param $key* @param $line* @param $state* @param $lines* @return bool*/private function parseBlockMh($block, $key, $line, &$state, $lines){if (preg_match("/^\s*((=|-){2,})\s*$/", $line, $matches)&& ($block && $block[0] == "normal" && !preg_match("/^\s*$/", $lines[$block[2]]))) {    // check if last line isn't emptyif ($this->isBlock('normal')) {$this->backBlock(1, 'mh', $matches[1][0] == '=' ? 1 : 2)->setBlock($key)->endBlock();} else {$this->startBlock('normal', $key);}return false;}return true;}/*** @param $block* @param $key* @param $line* @return bool*/private function parseBlockHr($block, $key, $line){if (preg_match("/^[-\*]{3,}\s*$/", $line)) {$this->startBlock('hr', $key)->endBlock();return false;}return true;}/*** @param $block* @param $key* @param $line* @param $state* @return bool*/private function parseBlockDefault($block, $key, $line, &$state){if ($this->isBlock('footnote')) {preg_match("/^(\s*)/", $line, $matches);if (strlen($matches[1]) >= $block[3][0]) {$this->setBlock($key);} else {$this->startBlock('normal', $key);}} else if ($this->isBlock('table')) {if (false !== strpos($line, '|')) {$block[3][2]++;$this->setBlock($key, $block[3]);} else {$this->startBlock('normal', $key);}} else if ($this->isBlock('quote')) {if (!preg_match("/^(\s*)$/", $line)) { // empty line$this->setBlock($key);} else {$this->startBlock('normal', $key);}} else {if (empty($block) || $block[0] != 'normal') {$this->startBlock('normal', $key);} else {$this->setBlock($key);}}return true;}/*** @param array $blocks* @param array $lines* @return array*/private function optimizeBlocks(array $blocks, array $lines){$blocks = $this->call('beforeOptimizeBlocks', $blocks, $lines);$key = 0;while (isset($blocks[$key])) {$moved = false;$block = &$blocks[$key];$prevBlock = isset($blocks[$key - 1]) ? $blocks[$key - 1] : NULL;$nextBlock = isset($blocks[$key + 1]) ? $blocks[$key + 1] : NULL;list ($type, $from, $to) = $block;if ('pre' == $type) {$isEmpty = array_reduce($lines, function ($result, $line) {return preg_match("/^\s*$/", $line) && $result;}, true);if ($isEmpty) {$block[0] = $type = 'normal';}}if ('normal' == $type) {// combine two blocks$types = array('list', 'quote');if ($from == $to && preg_match("/^\s*$/", $lines[$from])&& !empty($prevBlock) && !empty($nextBlock)) {if ($prevBlock[0] == $nextBlock[0] && in_array($prevBlock[0], $types)) {// combine 3 blocks$blocks[$key - 1] = array($prevBlock[0], $prevBlock[1], $nextBlock[2], NULL);array_splice($blocks, $key, 2);// do not move$moved = true;}}}if (!$moved) {$key++;}}return $this->call('afterOptimizeBlocks', $blocks, $lines);}/*** parseCode** @param array $lines* @param array $parts* @param int $start* @return string*/private function parseCode(array $lines, array $parts, $start){list ($blank, $lang) = $parts;$lang = trim($lang);$count = strlen($blank);if (!preg_match("/^[_a-z0-9-\+\#\:\.]+$/i", $lang)) {$lang = NULL;} else {$parts = explode(':', $lang);if (count($parts) > 1) {list ($lang, $rel) = $parts;$lang = trim($lang);$rel = trim($rel);}}$isEmpty = true;$lines = array_map(function ($line) use ($count, &$isEmpty) {$line = preg_replace("/^[ ]{{$count}}/", '', $line);if ($isEmpty && !preg_match("/^\s*$/", $line)) {$isEmpty = false;}return htmlspecialchars($line);}, array_slice($lines, 1, -1));$str = implode("\n", $this->markLines($lines, $start + 1));return $isEmpty ? '' :'<pre><code' . (!empty($lang) ? " class=\"{$lang}\"" : ''). (!empty($rel) ? " rel=\"{$rel}\"" : '') . '>'. $str . '</code></pre>';}/*** parsePre** @param array $lines* @param mixed $value* @param int $start* @return string*/private function parsePre(array $lines, $value, $start){foreach ($lines as &$line) {$line = htmlspecialchars(substr($line, 4));}$str = implode("\n", $this->markLines($lines, $start));return preg_match("/^\s*$/", $str) ? '' : '<pre><code>' . $str . '</code></pre>';}/*** parseAhtml** @param array $lines* @param mixed $value* @param int $start* @return string*/private function parseAhtml(array $lines, $value, $start){return trim(implode("\n", $this->markLines($lines, $start)));}/*** parseShtml** @param array $lines* @param mixed $value* @param int $start* @return string*/private function parseShtml(array $lines, $value, $start){return trim(implode("\n", $this->markLines(array_slice($lines, 1, -1), $start + 1)));}/*** parseMath** @param array $lines* @param mixed $value* @param int $start* @param int $end* @return string*/private function parseMath(array $lines, $value, $start, $end){return '<p>' . $this->markLine($start, $end) . htmlspecialchars(implode("\n", $lines)) . '</p>';}/*** parseSh** @param array $lines* @param int $num* @param int $start* @param int $end* @return string*/private function parseSh(array $lines, $num, $start, $end){$line = $this->markLine($start, $end) . $this->parseInline(trim($lines[0], '# '));return preg_match("/^\s*$/", $line) ? '' : "<h{$num}>{$line}</h{$num}>";}/*** parseMh** @param array $lines* @param int $num* @param int $start* @param int $end* @return string*/private function parseMh(array $lines, $num, $start, $end){return $this->parseSh($lines, $num, $start, $end);}/*** parseQuote** @param array $lines* @param mixed $value* @param int $start* @return string*/private function parseQuote(array $lines, $value, $start){foreach ($lines as &$line) {$line = preg_replace("/^\s*> ?/", '', $line);}$str = implode("\n", $lines);return preg_match("/^\s*$/", $str) ? '' : '<blockquote>' . $this->parse($str, true, $start) . '</blockquote>';}/*** parseList** @param array $lines* @param mixed $value* @param int $start* @return string*/private function parseList(array $lines, $value, $start){$html = '';$minSpace = 99999;$secondMinSpace = 99999;$found = false;$secondFound = false;$rows = array();// count levelsforeach ($lines as $key => $line) {if (preg_match("/^(\s*)((?:[0-9]+\.?)|\-|\+|\*)(\s+)(.*)$/i", $line, $matches)) {$space = strlen($matches[1]);$type = false !== strpos('+-*', $matches[2]) ? 'ul' : 'ol';$minSpace = min($space, $minSpace);$found = true;if ($space > 0) {$secondMinSpace = min($space, $secondMinSpace);$secondFound = true;}$rows[] = array($space, $type, $line, $matches[4]);} else {$rows[] = $line;if (preg_match("/^(\s*)/", $line, $matches)) {$space = strlen($matches[1]);if ($space > 0) {$secondMinSpace = min($space, $secondMinSpace);$secondFound = true;}}}}$minSpace = $found ? $minSpace : 0;$secondMinSpace = $secondFound ? $secondMinSpace : $minSpace;$lastType = '';$leftLines = array();$leftStart = 0;foreach ($rows as $key => $row) {if (is_array($row)) {list ($space, $type, $line, $text) = $row;if ($space != $minSpace) {$leftLines[] = preg_replace("/^\s{" . $secondMinSpace . "}/", '', $line);} else {if (!empty($leftLines)) {$html .= "<li>" . $this->parse(implode("\n", $leftLines), true, $start + $leftStart) . "</li>";}if ($lastType != $type) {if (!empty($lastType)) {$html .= "</{$lastType}>";}$html .= "<{$type}>";}$leftStart = $key;$leftLines = array($text);$lastType = $type;}} else {$leftLines[] = preg_replace("/^\s{" . $secondMinSpace . "}/", '', $row);}}if (!empty($leftLines)) {$html .= "<li>" . $this->parse(implode("\n", $leftLines), true, $start + $leftStart) . "</li></{$lastType}>";}return $html;}/*** @param array $lines* @param array $value* @param int $start* @return string*/private function parseTable(array $lines, array $value, $start){list ($ignores, $aligns) = $value;$head = count($ignores) > 0 && array_sum($ignores) > 0;$html = '<table>';$body = $head ? NULL : true;$output = false;foreach ($lines as $key => $line) {if (in_array($key, $ignores)) {if ($head && $output) {$head = false;$body = true;}continue;}$line = trim($line);$output = true;if ($line[0] == '|') {$line = substr($line, 1);if ($line[strlen($line) - 1] == '|') {$line = substr($line, 0, -1);}}$rows = array_map(function ($row) {if (preg_match("/^\s*$/", $row)) {return ' ';} else {return trim($row);}}, explode('|', $line));$columns = array();$last = -1;foreach ($rows as $row) {if (strlen($row) > 0) {$last++;$columns[$last] = array(isset($columns[$last]) ? $columns[$last][0] + 1 : 1, $row);} else if (isset($columns[$last])) {$columns[$last][0]++;} else {$columns[0] = array(1, $row);}}if ($head) {$html .= '<thead>';} else if ($body) {$html .= '<tbody>';}$html .= '<tr' . ($this->_line ? ' class="line" data-start="'. ($start + $key) . '" data-end="' . ($start + $key). '" data-id="' . $this->_uniqid . '"' : '') . '>';foreach ($columns as $key => $column) {list ($num, $text) = $column;$tag = $head ? 'th' : 'td';$html .= "<{$tag}";if ($num > 1) {$html .= " colspan=\"{$num}\"";}if (isset($aligns[$key]) && $aligns[$key] != 'none') {$html .= " align=\"{$aligns[$key]}\"";}$html .= '>' . $this->parseInline($text) . "</{$tag}>";}$html .= '</tr>';if ($head) {$html .= '</thead>';} else if ($body) {$body = false;}}if ($body !== NULL) {$html .= '</tbody>';}$html .= '</table>';return $html;}/*** parseHr** @param array $lines* @param array $value* @param int $start* @return string*/private function parseHr($lines, $value, $start){return $this->_line ? '<hr class="line" data-start="' . $start . '" data-end="' . $start . '">' : '<hr>';}/*** parseNormal** @param array $lines* @param bool $inline* @param int $start* @return string*/private function parseNormal(array $lines, $inline = false, $start){$from = $start;foreach ($lines as $key => &$line) {$line = $this->parseInline($line);if (!preg_match("/^\s*$/", $line)) {$end = $start + $key;$line = $this->markLine($from, $end) . $line;$from = $end + 1;}}$str = trim(implode("\n", $lines));$str = preg_replace("/(\n\s*){2,}/", "</p><p>", $str);$str = preg_replace("/\n/", "<br>", $str);return preg_match("/^\s*$/", $str) ? '' : ($inline ? $str : "<p>{$str}</p>");}/*** parseFootnote** @param array $lines* @param array $value* @return string*/private function parseFootnote(array $lines, array $value){list($space, $note) = $value;$index = array_search($note, $this->_footnotes);if (false !== $index) {$lines[0] = preg_replace("/^\[\^((?:[^\]]|\\]|\\[)+?)\]:/", '', $lines[0]);$this->_footnotes[$index] = $lines;}return '';}/*** parseDefine** @return string*/private function parseDefinition(){return '';}/*** parseHtml** @param array $lines* @param string $type* @param int $start* @return string*/private function parseHtml(array $lines, $type, $start){foreach ($lines as &$line) {$line = $this->parseInline($line,isset($this->_specialWhiteList[$type]) ? $this->_specialWhiteList[$type] : '');}return implode("\n", $this->markLines($lines, $start));}/*** @param $url* @return string*/public function cleanUrl($url){if (preg_match("/^\s*((http|https|ftp|mailto):[x80-xff_a-z0-9-\.\/%#!@\?\+=~\|\,&\(\)]+)/i", $url, $matches)) {return $matches[1];} else if (preg_match("/^\s*([x80-xff_a-z0-9-\.\/%#!@\?\+=~\|\,&]+)/i", $url, $matches)) {return $matches[1];} else {return '#';}}/*** @param $str* @return mixed*/public function escapeBracket($str){return str_replace(array('\[', '\]', '\(', '\)'), array('[', ']', '(', ')'), $str);}/*** startBlock** @param mixed $type* @param mixed $start* @param mixed $value* @return $this*/private function startBlock($type, $start, $value = NULL){$this->_pos++;$this->_current = $type;$this->_blocks[$this->_pos] = array($type, $start, $start, $value);return $this;}/*** endBlock** @return $this*/private function endBlock(){$this->_current = 'normal';return $this;}/*** isBlock** @param mixed $type* @param mixed $value* @return bool*/private function isBlock($type, $value = NULL){return $this->_current == $type&& (NULL === $value ? true : $this->_blocks[$this->_pos][3] == $value);}/*** getBlock** @return array*/private function getBlock(){return isset($this->_blocks[$this->_pos]) ? $this->_blocks[$this->_pos] : NULL;}/*** setBlock** @param mixed $to* @param mixed $value* @return $this*/private function setBlock($to = NULL, $value = NULL){if (NULL !== $to) {$this->_blocks[$this->_pos][2] = $to;}if (NULL !== $value) {$this->_blocks[$this->_pos][3] = $value;}return $this;}/*** backBlock** @param mixed $step* @param mixed $type* @param mixed $value* @return $this*/private function backBlock($step, $type, $value = NULL){if ($this->_pos < 0) {return $this->startBlock($type, 0, $value);}$last = $this->_blocks[$this->_pos][2];$this->_blocks[$this->_pos][2] = $last - $step;if ($this->_blocks[$this->_pos][1] <= $this->_blocks[$this->_pos][2]) {$this->_pos++;}$this->_current = $type;$this->_blocks[$this->_pos] = array($type, $last - $step + 1, $last, $value);return $this;}/*** @return $this*/private function combineBlock(){if ($this->_pos < 1) {return $this;}$prev = $this->_blocks[$this->_pos - 1];$current = $this->_blocks[$this->_pos];$prev[2] = $current[2];$this->_blocks[$this->_pos - 1] = $prev;$this->_current = $prev[0];unset($this->_blocks[$this->_pos]);$this->_pos--;return $this;}
}

2、建立解析类Markdown

<?php
namespace jplt\markdown;class Markdown extends Hyperdown
{public static function text($text){static $parser;if (empty($parser)) {$parser = new HyperDown();}$html = $parser->makeHtml($text);$index = [];$html = preg_replace_callback("/<h(\d+)>(.*?)<\/h\\1>/i", function ($item) use (&$index) {$index[$item[1]] = isset($index[$item[1]]) ? $index[$item[1]] + 1 : 1;$item[2] = strip_tags($item[2]);return "<h{$item[1]} id=\"{$item[2]}-{$index[$item[1]]}\">{$item[2]}</h{$item[1]}>";}, $html);return $html;}}

3、引用并使用

<?phpnamespace app\index\controller;use app\common\controller\Frontend;
use jplt\Service;
use jplt\markdown\Markdown;use think\Config;
use think\Exception;
class Index extends Frontend
{protected $noNeedLogin = '*';protected $noNeedRight = '*';protected $layout = '';// 文档首页public function index(){ echo '  <link rel="stylesheet" href="/docs/css/page.css">';//获取md内容 并转义$md="./index.md";$Macontent = file_get_contents(  $md);if (!$Macontent) {return null;}//解析 Markdown$content=Markdown::text($Macontent) ;var_dump($content);die;}
}

4、样式库page.css

@font-face {font-family: "Source Sans Pro";src: local("Source Sans Pro"), url("../fonts/Source_Sans_Pro/SourceSansPro-Regular.ttf");
}@font-face {font-family: "Source Sans Pro";src: local("Source Sans Pro Light"), url("../fonts/Source_Sans_Pro/SourceSansPro-Light.ttf");font-weight: 300;
}@font-face {font-family: "Source Sans Pro";src: local("Source Sans Pro Semibold"), url("../fonts/Source_Sans_Pro/SourceSansPro-Semibold.ttf");font-weight: 600;
}@font-face {font-family: "Roboto Mono";src: local("Roboto Mono"), url("../fonts/Roboto_Mono/RobotoMono-Regular.ttf");
}.gutter pre {color: #999;
}pre {color: #525252;
}pre .function .keyword,
pre .constant {color: #0092db;
}pre .keyword,
pre .attribute {color: #e96900;
}pre .number,
pre .literal {color: #ae81ff;
}pre .tag,
pre .tag .title,
pre .change,
pre .winutils,
pre .flow,
pre .lisp .title,
pre .clojure .built_in,
pre .nginx .title,
pre .tex .special {color: #2973b7;
}pre .class .title {color: #fff;
}pre .symbol,
pre .symbol .string,
pre .value,
pre .regexp {color: #18bc9c;
}pre .title {color: #a6e22e;
}pre .tag .value,
pre .string,
pre .subst,
pre .haskell .type,
pre .preprocessor,
pre .ruby .class .parent,
pre .built_in,
pre .sql .aggregate,
pre .django .template_tag,
pre .django .variable,
pre .smalltalk .class,
pre .javadoc,
pre .django .filter .argument,
pre .smalltalk .localvars,
pre .smalltalk .array,
pre .attr_selector,
pre .pseudo,
pre .addition,
pre .stream,
pre .envvar,
pre .apache .tag,
pre .apache .cbracket,
pre .tex .command,
pre .prompt {color: #18bc9c;
}pre .comment,
pre .java .annotation,
pre .python .decorator,
pre .template_comment,
pre .pi,
pre .doctype,
pre .deletion,
pre .shebang,
pre .apache .sqbracket,
pre .tex .formula {color: #b3b3b3;
}pre .coffeescript .javascript,
pre .javascript .xml,
pre .tex .formula,
pre .xml .javascript,
pre .xml .vbscript,
pre .xml .css,
pre .xml .cdata {opacity: 0.5;
}body {font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;font-size: 16px;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;color: #34495e;background-color: #fff;margin: 0;
}body.docs {padding-top: 61px;
}@media screen and (max-width: 900px) {body.docs {padding-top: 0;}
}a {text-decoration: none;color: #34495e;
}img {border: none;
}h1,
h2,
h3,
h4,
strong {font-weight: 600;color: #2c3e50;
}code,
pre {font-family: 'Roboto Mono', Monaco, courier, monospace;font-size: 0.8em;background-color: #f8f8f8;-webkit-font-smoothing: initial;-moz-osx-font-smoothing: initial;
}code {color: #dd4b39;padding: 3px 5px;margin: 0 2px;border-radius: 2px;line-height: 1.5em;
}code.hljs {white-space: inherit;padding: 1.5em;
}em {color: #7f8c8d;
}p {word-spacing: 0.05em;
}a.button {padding: 0.75em 2em;border-radius: 2em;display: inline-block;color: #fff;background-color: #1bd1ae;transition: all 0.15s ease;box-sizing: border-box;border: 1px solid #1bd1ae;
}a.button.white {background-color: #fff;color: #18bc9c;
}.highlight {overflow-x: auto;position: relative;padding: 0;background-color: #f8f8f8;padding: 0.8em 0.8em 0.4em;line-height: 1.1em;border-radius: 2px;
}.highlight table,
.highlight tr,
.highlight td {width: 100%;border-collapse: collapse;padding: 0;margin: 0;
}.highlight .gutter {width: 1.5em;
}.highlight .code pre {padding: 1.2em 1.4em;line-height: 1.5em;margin: 0;
}.highlight .code .line {min-height: 1.5em;
}.highlight.html .code:after,
.highlight.js .code:after,
.highlight.bash .code:after,
.highlight.css .code:after {position: absolute;top: 0;right: 0;color: #ccc;text-align: right;font-size: 0.75em;padding: 5px 10px 0;line-height: 15px;height: 15px;font-weight: 600;
}.highlight.html .code:after {content: 'HTML';
}.highlight.js .code:after {content: 'JS';
}.highlight.bash .code:after {content: 'Shell';
}.highlight.css .code:after {content: 'CSS';
}#main {position: relative;z-index: 1;padding: 0 60px 30px;overflow-x: hidden;
}#ad {width: 125px;position: fixed;z-index: 99;bottom: 10px;right: 10px;padding: 10px;background-color: #fff;border-radius: 3px;font-size: 13px;
}#ad a {display: inline-block;color: #7f8c8d;font-weight: normal;
}#ad span {color: #7f8c8d;display: inline-block;margin-bottom: 5px;
}#ad img {width: 125px;
}#ad .carbon-img,
#ad .carbon-text {display: block;margin-bottom: 6px;font-weight: normal;color: #34495e;
}#ad .carbon-poweredby {color: #aaa;font-weight: normal;
}#nav .nav-link {cursor: pointer;
}#nav .nav-dropdown-container .nav-link:hover {border-bottom: none;
}#nav .nav-dropdown-container:hover .nav-dropdown {display: block;
}#nav .nav-dropdown-container.language {margin-left: 20px;
}#nav .nav-dropdown-container .arrow {pointer-events: none;
}#nav .nav-dropdown {display: none;box-sizing: border-box;max-height: calc(100vh - 61px);overflow-y: scroll;position: absolute;top: 100%;right: -15px;background-color: #fff;padding: 10px 0;border: 1px solid #ddd;border-bottom-color: #ccc;text-align: left;border-radius: 4px;white-space: nowrap;
}#nav .nav-dropdown li {line-height: 1.8em;margin: 0;display: block;
}#nav .nav-dropdown li > ul {padding-left: 0;
}#nav .nav-dropdown li:first-child h4 {margin-top: 0;padding-top: 0;border-top: 0;
}#nav .nav-dropdown a,
#nav .nav-dropdown h4 {padding: 0 24px 0 20px;
}#nav .nav-dropdown h4 {margin: 0.45em 0 0;padding-top: 0.45em;border-top: 1px solid #eee;
}#nav .nav-dropdown a {color: #3a5169;font-size: 0.9em;display: block;
}#nav .nav-dropdown a:hover {color: #18bc9c;
}#nav .arrow {display: inline-block;vertical-align: middle;margin-top: -1px;margin-left: 6px;margin-right: -14px;width: 0;height: 0;border-left: 4px solid transparent;border-right: 4px solid transparent;border-top: 5px solid #ccc;
}#header {-webkit-backdrop-filter: saturate(180%) blur(20px);backdrop-filter: saturate(180%) blur(20px);background-color: rgba(255, 255, 255, 0.7);padding: 10px 60px;position: relative;z-index: 2;
}body.docs #header {position: fixed;width: 100%;top: 0;box-sizing: border-box;
}body.docs #nav {position: fixed;
}#nav {list-style-type: none;margin: 0;padding: 0;position: absolute;right: 60px;top: 10px;height: 40px;line-height: 40px;
}#nav .break {display: none;
}#nav > li {display: inline-block;position: relative;margin: 0 0.6em;
}.nav-link {padding-bottom: 3px;
}.nav-link:hover,
.nav-link.current {border-bottom: 3px solid #18bc9c;
}.search-query {height: 30px;line-height: 30px;box-sizing: border-box;padding: 0 15px 0 30px;border: 1px solid #e3e3e3;color: #2c3e50;outline: none;border-radius: 15px;margin-right: 10px;transition: border-color 0.2s ease;background: #fff url("../images/search.png") 8px 5px no-repeat;background-size: 20px;vertical-align: middle !important;
}.search-query:focus {border-color: #18bc9c;
}#logo {display: inline-block;font-size: 1.5em;line-height: 40px;color: #18bc9c;font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;font-weight: 500;
}#logo img {vertical-align: middle;margin-right: 6px;max-width: 200px;height: 40px;
}#mobile-bar {position: fixed;top: 0;left: 0;width: 100%;height: 40px;text-align: center;background-color: #fff;z-index: 9;display: none;box-shadow: 0 0 2px rgba(0, 0, 0, 0.25);
}#mobile-bar .menu-button {position: absolute;width: 24px;height: 24px;top: 8px;left: 12px;background: url("../images/menu.png") center center no-repeat;background-size: 24px;
}#mobile-bar .logo {font-size: 1.5em;line-height: 40px;color: #18bc9c;
}#mobile-bar .logo img {height: 30px;margin-top: 5px;max-width: 200px;
}#demo,
.demo {border: 1px solid #eee;border-radius: 2px;padding: 25px 35px;margin-top: 1em;margin-bottom: 40px;font-size: 1.2em;line-height: 1.5em;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;overflow-x: auto;
}#demo h1,
.demo h1 {margin: 0 0 0.5em;font-size: 1.8em;
}#demo ul,
.demo ul,
#demo ol,
.demo ol {padding-left: 1.5em;padding-bottom: 0.2em !important;
}#demo ul:first-child,
.demo ul:first-child,
#demo ol:first-child,
.demo ol:first-child {margin-top: 0;
}#demo ul:last-child,
.demo ul:last-child,
#demo ol:last-child,
.demo ol:last-child {margin-bottom: 0;
}#demo li,
.demo li {color: #34495e;
}#demo li.done,
.demo li.done {color: #7f8c8d;text-decoration: line-through;
}#demo p,
.demo p {margin: 0 !important;padding: 0 !important;
}#demo p:first-child,
.demo p:first-child {margin-top: 0;
}#demo p:last-child,
.demo p:last-child {margin-bottom: 0;
}#demo textarea,
.demo textarea {width: 100%;resize: vertical;
}ul#demo li,
ul.demo li {margin-left: 1.5em;
}@media screen and (max-width: 900px) {#demo,.demo {margin-left: 0;}
}.benchmark-table {margin: 0 auto;text-align: center;
}.benchmark-table tbody > tr > th {text-align: right;
}.benchmark-table th,
.benchmark-table td {padding: 3px 7px;
}.content.docs[class*="migration"] h2 > sup,
.content.docs[class*="migration"] h3 > sup {margin-left: 0.3em;color: #b9465c;
}.content.docs[class*="migration"] .upgrade-path {padding: 2em;background: rgba(73, 195, 140, 0.1);border-radius: 2px;
}.content.docs[class*="migration"] .upgrade-path > h4 {margin-top: 0;
}.content.docs[class*="migration"] .upgrade-path > p:last-child {margin-bottom: 0;
}.sidebar {position: absolute;z-index: 10;top: 61px;left: 0;bottom: 0;padding: 40px 0 30px 60px;width: 260px;margin-right: 20px;overflow-x: hidden;overflow-y: auto;-webkit-overflow-scrolling: touch;-ms-overflow-style: none;
}.sidebar h2 {margin-top: 0.2em;
}.sidebar ul {list-style-type: none;margin: 0;line-height: 1.9em;padding-left: 1em;
}.sidebar .version-select {vertical-align: middle;margin-left: 5px;
}.sidebar .menu-root {padding-left: 0;
}.sidebar .menu-sub {font-size: 0.85em;
}.sidebar .sidebar-link {color: #7f8c8d;
}.sidebar .sidebar-link.current {font-weight: 600;color: #18bc9c;
}.sidebar .sidebar-link.new:after {content: "NEW";display: inline-block;font-size: 10px;font-weight: 600;color: #fff;background-color: #18bc9c;line-height: 14px;padding: 0 4px;border-radius: 3px;margin-left: 5px;vertical-align: middle;position: relative;top: -1px;
}.sidebar .sidebar-link:hover {border-bottom: 2px solid #18bc9c;
}.sidebar .section-link.active {font-weight: bold;color: #18bc9c;
}.sidebar .main-menu {margin-bottom: 20px;display: none;padding-left: 0;
}.sidebar .main-sponsor {color: #7f8c8d;font-size: 0.85em;
}.sidebar .main-sponsor a {margin: 10px 0;
}.sidebar .main-sponsor img,
.sidebar .main-sponsor a {width: 125px;display: inline-block;
}.sidebar .become-backer {border: 1px solid #18bc9c;border-radius: 2em;display: inline-block;color: #18bc9c;font-size: 0.8em;width: 125px;padding: 4px 0;text-align: center;margin-bottom: 20px;
}.sidebar .nav-dropdown h4 {font-weight: normal;margin: 0;
}@media screen and (max-width: 900px) {.sidebar {position: fixed;z-index: 8;background-color: #f9f9f9;height: 100%;top: 0;left: 0;padding: 60px 30px 20px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);box-sizing: border-box;transition: all 0.4s cubic-bezier(0.4, 0, 0, 1);-webkit-transform: translate(-280px, 0);transform: translate(-280px, 0);}.sidebar .search-query {width: 200px;margin-bottom: 10px;}.sidebar .main-menu {display: block;}.sidebar.open {-webkit-transform: translate(0, 0);transform: translate(0, 0);}
}#header {box-shadow: 0 0 1px rgba(0, 0, 0, 0.25);transition: background-color 0.3s ease-in-out;
}.content {position: relative;padding: 2.2em 0;max-width: 800px;margin: 0 auto;
}.content.api > a:first-of-type > h2 {margin-top: 0;padding-top: 0;
}.content.api ul {padding-left: 1.25em;line-height: 1.4em;
}.content.api ul ul,
.content.api ul p {margin: 0.6em 0;
}.content a.button {font-size: 0.9em;color: #fff;margin: 0.2em 0;width: 180px;text-align: center;padding: 12px 24px;display: inline-block;vertical-align: middle;
}.content img {max-width: 100%;
}.content span.light {color: #7f8c8d;
}.content span.info {font-size: 0.85em;display: inline-block;vertical-align: middle;width: 280px;margin-left: 20px;
}.content h1 {margin: 0 0 1em;
}.content h2:before,
.content h3:before {content: '';display: block;margin-top: -91px;height: 91px;visibility: hidden;
}.content h2 {margin: 45px 0 0.8em;padding-bottom: 0.7em;border-bottom: 1px solid #ddd;z-index: -1;
}.content h3 {margin: 52px 0 1.2em;position: relative;z-index: -1;
}.content h3:after {content: "#";color: #18bc9c;position: absolute;left: -0.7em;bottom: -2px;font-size: 1.2em;font-weight: bold;
}.content figure {margin: 1.2em 0;
}.content p,
.content ul,
.content ol {line-height: 1.6em;margin: 1.2em 0 -1.2em;padding-bottom: 1.2em;position: relative;z-index: 1;
}.content blockquote p,
.content blockquote ul,
.content blockquote ol {padding-bottom: 0;
}.content ul,
.content ol {padding-left: 1.5em;
}.content a {color: #18bc9c;font-weight: 400;
}.content blockquote {margin: 2em 0;padding-left: 20px;border-left: 4px solid #18bc9c;
}.content blockquote p {margin-left: 0;
}.content iframe {margin: 1em 0;
}.content > table {border: 2px solid #fff;margin: 1.2em auto;width: 100%;
}.content > table td,
.content > table th {line-height: 1.6em;padding: 0.5em 1.4em;border: none;
}.content > table td {background-color: #fcfcfc;
}.content > table th {background-color: #18bc9c;color: #fff;padding-top: 0.85em;padding-bottom: 0.85em;text-align: left;
}.content > table tbody code {background-color: #efefef;
}.docs-links {margin-top: 2em;height: 1em;
}.footer {color: #7f8c8d;margin-top: 2em;padding-top: 2em;border-top: 1px solid #e5e5e5;font-size: 0.9em;
}#main.fix-sidebar .sidebar {position: fixed;
}@media screen and (min-width: 1590px) {#header {background-color: rgba(255, 255, 255, 0.4);}
}@media screen and (max-width: 1500px) {.content.with-sidebar {margin-left: 280px;}#ad {z-index: 7;position: relative;padding: 0;bottom: 0;right: 0;float: right;padding: 0 0 20px 30px;}
}@media screen and (max-width: 900px) {body {-webkit-text-size-adjust: none;font-size: 14px;}#header {display: none;}#logo {display: none;}.nav-link {padding-bottom: 1px;}.nav-link:hover,.nav-link.current {border-bottom: 2px solid #18bc9c;}#mobile-bar {display: block;}#main {padding: 2em 1.4em 0;}.highlight pre {padding: 1.2em 1em;}.content.with-sidebar {margin: auto;}.content h2:before,.content h3:before {content: '';display: block;margin-top: -70px;height: 70px;visibility: hidden;}.footer {margin-left: 0;text-align: center;}
}@media screen and (max-width: 560px) {#downloads {text-align: center;margin-bottom: 25px;}#downloads .info {margin-top: 5px;margin-left: 0;}iframe {margin: 0 !important;}
}

5、加载自己的代码高亮效果、这里不给你

php进行Markdown解析相关推荐

  1. 刘光瑞php,PHP Markdown 解析器 HyperDown

    软件介绍 HyperDown 是 SegmentFault 开发的一个结构清晰.易于维护.现代的 PHP Markdown 解析器. Markdown已经面世许多年了,国内外许多大大小小的网站都在用它 ...

  2. Java如何解析markdown_使用Java实现的一款Markdown解析器md2x

    使用Java实现的一款Markdown解析器md2x 前段时间在写自己的博客程序的时候,在前台使用了marked.js来解析自己的markdown文章,然后发现在进入文章页面的时候总会闪烁一下(前台解 ...

  3. markdown解析

    markdown解析 demo 1.解析markdown里的所有代码块. 通过创建visitor,收集markdown里的代码片段并分类. public static void main(String ...

  4. Android 平台下的原生 Markdown 解析器

    Markdown 项目地址:zzhoujay/Markdown 简介:Android 平台下的原生 Markdown 解析器 Android 平台的原生 Markdown 解析器,已整合进 RichT ...

  5. 小笔记:表 - 各种语言的 CommonMark Markdown解析器 实现

    表:各种语言的 CommonMark Markdown解析器 实现 本文地址:https://blog.csdn.net/qq_28550263/article/details/128735962 1 ...

  6. 制作简易Markdown解析器

    目录 资源 思路 过程 总结 思路 1.简易markdown部分语法与html中的对应关系 符号 标签内容 # <h1></h1> ## <h2></h2&g ...

  7. Go语言——快速使用Markdown解析库

    目录 go解析markdown markdown解析库 使用: 安全过滤: 例子: 关于Markdown的语法:Markdown语法文档(翻译) go解析markdown Markdown 是一种轻量 ...

  8. Vue3解析markdown解析并实现代码高亮显示

    Vue实现博客前端,需要实现markdown的解析,如果有代码则需要实现代码的高亮. Vue的markdown解析库有很多,如markdown-it.vue-markdown-loader.marke ...

  9. 对par.markdown解析进行完善

    2019独角兽企业重金招聘Python工程师标准>>> par (https://github.com/limodou/par) 是我创建的一个用来进行结构化文本解析的项目,目前支持 ...

  10. ios markdown 解析_Shortcuts 教程:正则表达式修改 Markdown 链接

    这是我的「写作流」Shortcuts. 上一篇文章中,我谈到由于官方编辑器完全不支持 Markdown 语法,我不得不利用 Shortcut 以及公众号 Web API 处理 Markdown 文本的 ...

最新文章

  1. 人工智能军事对抗技术发展趋势
  2. PHPMailer 发送邮件
  3. openfiler的iSCSI配置(二)
  4. [国密算法]一文了解国密算法
  5. 大咖面对面 | 陈果果博士谈智能语音
  6. python random_Python random() 函数
  7. 原来你是这样的JAVA[01]-基础一瞥
  8. iOS开发CGRectGetMidX. CGRectGetMidY.CGRectGetMinY. CGRectGetMaxY. CGRectGetMinX. CGRectGetMaxX的使用...
  9. 【IoT】产品模型:基于 ARM 的音视频采集与传输系统
  10. AGPBI: {“kind“:“error“,“text“:“Program type already present: android.support.v4.os.ResultReceiver$1“
  11. esxi添加硬盘驱动
  12. Hadoop生态系统完整组件及其在架构中的作用
  13. 【微信红包封面】最新!最全!
  14. 【区块链新手快速入门】如何构建一个区块链
  15. 工控系统设计(八)组态功能开发
  16. 判断和循环(实战收尾篇2—猜数字游戏)
  17. Python入门基础篇 No.8 —— 时间的表示_unix时间点_毫秒_time模块
  18. 从零构建区块链量化交易平台课程总结-思维模型和方法论提炼
  19. python趣味编程-python趣味入门——写几个常玩的游戏
  20. 智慧城市规划数字化管理:数字孪生技术的创新应用

热门文章

  1. 一款 Material Design 风格的妹子福利 App.
  2. 《仙剑奇侠传3》全攻略
  3. Git三大特色之Stage(暂存区)
  4. python小数点后任意位_Python计算开方、立方、圆周率,精确到小数点后任意位的方法...
  5. Kettle 常用的转换组件都在这里
  6. 浅析STM32H7 FDCAN(二)
  7. 大风起兮云飞扬 —记2011年的中国云计算
  8. 前程似锦 如鱼得水 藏头诗
  9. maven多模块编译子包
  10. Advanced Download Manager(ADM) – 来自俄罗斯的 Android 下载神器,支持下载 BT 种子