网站链接: element-ui dtcms
当前位置: 首页 > 技术博文  > 技术博文

php进行Markdown解析

2021/4/19 4:50:46 人评论

1、建立一个基本类 Hyperdown <?phpnamespace jplt\markdown;/*** Parser** copyright Copyright (c) 2012 SegmentFault Team. (http://segmentfault.com)* author Joyqi <joyqisegmentfault.com>* license BSD License*/ class Hyperdown {/*** _whiteList** var …

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\">&#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 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('$', '&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 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、加载自己的代码高亮效果、这里不给你

相关资讯

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?