1、建立一个基本类 Hyperdown
<?php
namespace 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\">↩</a>";
} else {
$val[count($val) - 1] .= " <a href=\"#fnref-{$index}\" class=\"footnote-backref\">↩</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 block
if ($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('$', '$', $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('<', '>'), $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 url
if ($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 line
foreach ($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;
// opened
if ($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 empty
if ($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 levels
foreach ($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、引用并使用
<?php
namespace 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、加载自己的代码高亮效果、这里不给你