diff --git a/Parser.php b/Parser.php
index 857d3f2..dce3226 100644
--- a/Parser.php
+++ b/Parser.php
@@ -16,14 +16,14 @@ class Parser
      *
      * @var string
      */
-    public $_commonWhiteList = 'kbd|b|i|strong|em|sup|sub|br|code|del|a|hr|small';
+    private $_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';
+    private $_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
@@ -31,48 +31,48 @@ class Parser
      * @var mixed
      * @access private
      */
-    public $_specialWhiteList = array(
-        'table'  =>  'table|tbody|thead|tfoot|tr|td|th'
-    );
+    private $_specialWhiteList = [
+        'table' => 'table|tbody|thead|tfoot|tr|td|th'
+    ];
 
     /**
      * _footnotes
      *
      * @var array
      */
-    public $_footnotes;
+    private $_footnotes;
 
     /**
      * @var bool
      */
-    public $_html = false;
+    private $_html = false;
 
     /**
      * @var bool
      */
-    public $_line = false;
+    private $_line = false;
 
     /**
      * @var array
      */
-    public $blockParsers = array(
-        array('code', 10),
-        array('shtml', 20),
-        array('pre', 30),
-        array('ahtml', 40),
-        array('shr', 50),
-        array('list', 60),
-        array('math', 70),
-        array('html', 80),
-        array('footnote', 90),
-        array('definition', 100),
-        array('quote', 110),
-        array('table', 120),
-        array('sh', 130),
-        array('mh', 140),
-        array('dhr', 150),
-        array('default', 9999)
-    );
+    private $blockParsers = [
+        ['code', 10],
+        ['shtml', 20],
+        ['pre', 30],
+        ['ahtml', 40],
+        ['shr', 50],
+        ['list', 60],
+        ['math', 70],
+        ['html', 80],
+        ['footnote', 90],
+        ['definition', 100],
+        ['quote', 110],
+        ['table', 120],
+        ['sh', 130],
+        ['mh', 140],
+        ['dhr', 150],
+        ['default', 9999]
+    ];
 
     /**
      * _blocks
@@ -100,12 +100,12 @@ class Parser
      *
      * @var array
      */
-    public $_definitions;
+    private $_definitions;
 
     /**
      * @var array
      */
-    private $_hooks = array();
+    private $_hooks = [];
 
     /**
      * @var array
@@ -125,19 +125,20 @@ class Parser
     /**
      * @var array
      */
-    private $_parsers = array();
+    private $_parsers = [];
 
     /**
      * makeHtml
      *
      * @param mixed $text
+     *
      * @return string
      */
-    public function makeHtml($text)
+    public function makeHtml($text): string
     {
-        $this->_footnotes = array();
-        $this->_definitions = array();
-        $this->_holders = array();
+        $this->_footnotes = [];
+        $this->_definitions = [];
+        $this->_holders = [];
         $this->_uniqid = md5(uniqid());
         $this->_id = 0;
 
@@ -146,12 +147,12 @@ public function makeHtml($text)
         });
 
         foreach ($this->blockParsers as $parser) {
-            list($name) = $parser;
+            [$name] = $parser;
 
             if (isset($parser[2])) {
                 $this->_parsers[$name] = $parser[2];
             } else {
-                $this->_parsers[$name] = array($this, 'parseBlock' . ucfirst($name));
+                $this->_parsers[$name] = [$this, 'parseBlock' . ucfirst($name)];
             }
         }
 
@@ -164,9 +165,9 @@ public function makeHtml($text)
     }
 
     /**
-     * @param $html
+     * @param bool $html
      */
-    public function enableHtml($html = true)
+    public function enableHtml(bool $html = true)
     {
         $this->_html = $html;
     }
@@ -174,48 +175,51 @@ public function enableHtml($html = true)
     /**
      * @param bool $line
      */
-    public function enableLine($line = true)
+    public function enableLine(bool $line = true)
     {
         $this->_line = $line;
     }
 
     /**
-     * @param $type
-     * @param $callback
+     * @param string $type
+     * @param callable $callback
      */
-    public function hook($type, $callback)
+    public function hook(string $type, callable $callback)
     {
         $this->_hooks[$type][] = $callback;
     }
 
     /**
-     * @param $str
+     * @param string $str
+     *
      * @return string
      */
-    public function makeHolder($str)
+    public function makeHolder(string $str): string
     {
         $key = "\r" . $this->_uniqid . $this->_id . "\r";
-        $this->_id ++;
+        $this->_id++;
         $this->_holders[$key] = $str;
 
         return $key;
     }
 
     /**
-     * @param $text
-     * @return mixed
+     * @param string $text
+     *
+     * @return string
      */
-    private function initText($text)
+    private function initText(string $text): string
     {
-        $text = str_replace(array("\t", "\r"),  array('    ', ''),  $text);
+        $text = str_replace(["\t", "\r"], ['    ', ''], $text);
         return $text;
     }
 
     /**
-     * @param $html
+     * @param string $html
+     *
      * @return string
      */
-    private function makeFootnotes($html)
+    private function makeFootnotes(string $html): string
     {
         if (count($this->_footnotes) > 0) {
             $html .= '<div class="footnotes"><hr><ol>';
@@ -230,7 +234,7 @@ private function makeFootnotes($html)
                 }
 
                 $html .= "<li id=\"fn-{$index}\">{$val}</li>";
-                $index ++;
+                $index++;
             }
 
             $html .= '</ol></div>';
@@ -245,9 +249,10 @@ private function makeFootnotes($html)
      * @param string $text
      * @param bool $inline
      * @param int $offset
+     *
      * @return string
      */
-    private function parse($text, $inline = false, $offset = 0)
+    private function parse(string $text, bool $inline = false, int $offset = 0): string
     {
         $blocks = $this->parseBlock($text, $lines);
         $html = '';
@@ -258,7 +263,7 @@ private function parse($text, $inline = false, $offset = 0)
         }
 
         foreach ($blocks as $block) {
-            list($type, $start, $end, $value) = $block;
+            [$type, $start, $end, $value] = $block;
             $extract = array_slice($lines, $start, $end - $start + 1);
             $method = 'parse' . ucfirst($type);
 
@@ -273,31 +278,33 @@ private function parse($text, $inline = false, $offset = 0)
     }
 
     /**
-     * @param $text
-     * @param $clearHolders
+     * @param string $text
+     * @param bool $clearHolders
+     *
      * @return string
      */
-    private function releaseHolder($text, $clearHolders = true)
+    private function releaseHolder(string $text, bool $clearHolders = true): string
     {
         $deep = 0;
         while (strpos($text, "\r") !== false && $deep < 10) {
             $text = str_replace(array_keys($this->_holders), array_values($this->_holders), $text);
-            $deep ++;
+            $deep++;
         }
 
         if ($clearHolders) {
-            $this->_holders = array();
+            $this->_holders = [];
         }
 
         return $text;
     }
 
     /**
-     * @param $start
+     * @param int $start
      * @param int $end
+     *
      * @return string
      */
-    public function markLine($start, $end = -1)
+    private function markLine(int $start, int $end = -1): string
     {
         if ($this->_line) {
             $end = $end < 0 ? $start : $end;
@@ -310,25 +317,26 @@ public function markLine($start, $end = -1)
 
     /**
      * @param array $lines
-     * @param $start
+     * @param int $start
+     *
      * @return string[]
      */
-    public function markLines(array $lines, $start)
+    private function markLines(array $lines, int $start): array
     {
         $i = -1;
-        $self = $this;
 
-        return $this->_line ? array_map(function ($line) use ($self, $start, &$i) {
-            $i ++;
-            return $self->markLine($start + $i) . $line;
+        return $this->_line ? array_map(function ($line) use ($start, &$i) {
+            $i++;
+            return $this->markLine($start + $i) . $line;
         }, $lines) : $lines;
     }
 
     /**
-     * @param $html
+     * @param string $html
+     *
      * @return string
      */
-    public function optimizeLines($html)
+    private function optimizeLines(string $html): string
     {
         $last = 0;
 
@@ -347,19 +355,19 @@ function ($matches) use (&$last) {
     }
 
     /**
-     * @param $type
-     * @param $value
+     * @param string $type
+     * @param ...$args
+     *
      * @return mixed
      */
-    public function call($type, $value)
+    private function call(string $type, ...$args)
     {
+        $value = $args[0];
+
         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;
@@ -375,20 +383,25 @@ public function call($type, $value)
      * @param string $whiteList
      * @param bool $clearHolders
      * @param bool $enableAutoLink
+     *
      * @return string
      */
-    public function parseInline($text, $whiteList = '', $clearHolders = true, $enableAutoLink = true)
+    private function parseInline(
+        string $text,
+        string $whiteList = '',
+        bool   $clearHolders = true,
+        bool   $enableAutoLink = true
+    ): string
     {
-        $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>'
-                );
+            function ($matches) {
+                return $matches[1] . $this->makeHolder(
+                        '<code>' . htmlspecialchars($matches[3]) . '</code>'
+                    );
             },
             $text
         );
@@ -396,10 +409,10 @@ function ($matches) use ($self) {
         // mathjax
         $text = preg_replace_callback(
             "/(^|[^\\\])(\\$+)(.+?)\\2/",
-            function ($matches) use ($self) {
-                return  $matches[1] . $self->makeHolder(
-                    $matches[2] . htmlspecialchars($matches[3]) . $matches[2]
-                );
+            function ($matches) {
+                return $matches[1] . $this->makeHolder(
+                        $matches[2] . htmlspecialchars($matches[3]) . $matches[2]
+                    );
             },
             $text
         );
@@ -407,23 +420,23 @@ function ($matches) use ($self) {
         // escape
         $text = preg_replace_callback(
             "/\\\(.)/u",
-            function ($matches) use ($self) {
+            function ($matches) {
                 $prefix = preg_match("/^[-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]$/", $matches[1]) ? '' : '\\';
                 $escaped = htmlspecialchars($matches[1]);
                 $escaped = str_replace('$', '&dollar;', $escaped);
-                return  $self->makeHolder($prefix . $escaped);
+                return $this->makeHolder($prefix . $escaped);
             },
             $text
         );
 
         // link
         $text = preg_replace_callback(
-            "/<(https?:\/\/.+)>/i",
-            function ($matches) use ($self) {
-                $url = $self->cleanUrl($matches[1]);
-                $link = $self->call('parseLink', $matches[1]);
+            "/<(https?:\/\/.+|(?:mailto:)?[_a-z0-9-\.\+]+@[_\w-]+(?:\.[a-z]{2,})+)>/i",
+            function ($matches) {
+                $url = $this->cleanUrl($matches[1]);
+                $link = $this->call('parseLink', $url);
 
-                return $self->makeHolder(
+                return $this->makeHolder(
                     "<a href=\"{$url}\">{$link}</a>"
                 );
             },
@@ -433,38 +446,38 @@ function ($matches) use ($self) {
         // 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]);
+            function ($matches) use ($whiteList) {
+                if ($this->_html || false !== stripos(
+                        '|' . $this->_commonWhiteList . '|' . $whiteList . '|', '|' . $matches[2] . '|'
+                    )) {
+                    return $this->makeHolder($matches[0]);
                 } else {
-                    return $self->makeHolder(htmlspecialchars($matches[0]));
+                    return $this->makeHolder(htmlspecialchars($matches[0]));
                 }
             },
             $text
         );
 
         if ($this->_html) {
-            $text = preg_replace_callback("/<!\-\-(.*?)\-\->/", function ($matches) use ($self) {
-                return $self->makeHolder($matches[0]);
+            $text = preg_replace_callback("/<!\-\-(.*?)\-\->/", function ($matches) {
+                return $this->makeHolder($matches[0]);
             }, $text);
         }
 
-        $text = str_replace(array('<', '>'),  array('&lt;', '&gt;'),  $text);
+        $text = str_replace(['<', '>'], ['&lt;', '&gt;'], $text);
 
         // footnote
         $text = preg_replace_callback(
             "/\[\^((?:[^\]]|\\\\\]|\\\\\[)+?)\]/",
-            function ($matches) use ($self) {
-                $id = array_search($matches[1], $self->_footnotes);
+            function ($matches) {
+                $id = array_search($matches[1], $this->_footnotes);
 
                 if (false === $id) {
-                    $id = count($self->_footnotes) + 1;
-                    $self->_footnotes[$id] = $self->parseInline($matches[1], '', false);
+                    $id = count($this->_footnotes) + 1;
+                    $this->_footnotes[$id] = $this->parseInline($matches[1], '', false);
                 }
 
-                return $self->makeHolder(
+                return $this->makeHolder(
                     "<sup id=\"fnref-{$id}\"><a href=\"#fn-{$id}\" class=\"footnote-ref\">{$id}</a></sup>"
                 );
             },
@@ -474,12 +487,14 @@ function ($matches) use ($self) {
         // 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}\">"
+            function ($matches) {
+                $escaped = htmlspecialchars($this->escapeBracket($matches[1]));
+                $url = $this->escapeBracket($matches[2]);
+                [$url, $title] = $this->cleanUrl($url, true);
+                $title = empty($title) ? $escaped : " title=\"{$title}\"";
+
+                return $this->makeHolder(
+                    "<img src=\"{$url}\" alt=\"{$title}\" title=\"{$title}\">"
                 );
             },
             $text
@@ -487,43 +502,45 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/!\[((?:[^\]]|\\\\\]|\\\\\[)*?)\]\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]/",
-            function ($matches) use ($self) {
-                $escaped = htmlspecialchars($self->escapeBracket($matches[1]));
+            function ($matches) {
+                $escaped = htmlspecialchars($this->escapeBracket($matches[1]));
 
-                $result = isset( $self->_definitions[$matches[2]] ) ?
-                    "<img src=\"{$self->_definitions[$matches[2]]}\" alt=\"{$escaped}\" title=\"{$escaped}\">"
+                $result = isset($this->_definitions[$matches[2]]) ?
+                    "<img src=\"{$this->_definitions[$matches[2]]}\" alt=\"{$escaped}\" title=\"{$escaped}\">"
                     : $escaped;
 
-                return $self->makeHolder($result);
+                return $this->makeHolder($result);
             },
             $text
         );
 
         // link
         $text = preg_replace_callback(
-            "/\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]\(((?:[^\)]|\\\\\)|\\\\\()+?)\)/",
-            function ($matches) use ($self) {
-                $escaped = $self->parseInline(
-                    $self->escapeBracket($matches[1]),  '',  false, false
+            "/\[([^\]]*(?:\\\\.[^\]]*)*)\]\(([^)]*(?:\\\\.[^)]*)*)\)/",
+            function ($matches) {
+                $escaped = $this->parseInline(
+                    $this->escapeBracket($matches[1]), '', false, false
                 );
-                $url = $self->escapeBracket($matches[2]);
-                $url = $self->cleanUrl($url);
-                return $self->makeHolder("<a href=\"{$url}\">{$escaped}</a>");
+                $url = $this->escapeBracket($matches[2]);
+                [$url, $title] = $this->cleanUrl($url, true);
+                $title = empty($title) ? '' : " title=\"{$title}\"";
+
+                return $this->makeHolder("<a href=\"{$url}\"{$title}>{$escaped}</a>");
             },
             $text
         );
 
         $text = preg_replace_callback(
             "/\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]/",
-            function ($matches) use ($self) {
-                $escaped = $self->parseInline(
-                    $self->escapeBracket($matches[1]),  '',  false
+            function ($matches) {
+                $escaped = $this->parseInline(
+                    $this->escapeBracket($matches[1]), '', false
                 );
-                $result = isset( $self->_definitions[$matches[2]] ) ?
-                    "<a href=\"{$self->_definitions[$matches[2]]}\">{$escaped}</a>"
+                $result = isset($this->_definitions[$matches[2]]) ?
+                    "<a href=\"{$this->_definitions[$matches[2]]}\">{$escaped}</a>"
                     : $escaped;
 
-                return $self->makeHolder($result);
+                return $this->makeHolder($result);
             },
             $text
         );
@@ -539,10 +556,11 @@ function ($matches) use ($self) {
         // autolink url
         if ($enableAutoLink) {
             $text = preg_replace_callback(
-                "/(^|[^\"])((https?):\S+)($|[^\"])/i",
-                function ($matches) use ($self) {
-                    $link = $self->call('parseLink', $matches[2]);
-                    return "{$matches[1]}<a href=\"{$matches[2]}\">{$link}</a>{$matches[4]}";
+                "/(^|[^\"])(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\b([-a-zA-Z0-9@:%_\+.~#?&\/=]*)|(?:mailto:)?[_a-z0-9-\.\+]+@[_\w-]+(?:\.[a-z]{2,})+)($|[^\"])/",
+                function ($matches) {
+                    $url = $this->cleanUrl($matches[2]);
+                    $link = $this->call('parseLink', $matches[2]);
+                    return "{$matches[1]}<a href=\"{$url}\">{$link}</a>{$matches[5]}";
                 },
                 $text
             );
@@ -557,18 +575,17 @@ function ($matches) use ($self) {
     }
 
     /**
-     * @param $text
-     * @return mixed
+     * @param string $text
+     *
+     * @return string
      */
-    public function parseInlineCallback($text)
+    private function parseInlineCallback(string $text): string
     {
-        $self = $this;
-
         $text = preg_replace_callback(
             "/(\*{3})(.+?)\\1/",
-            function ($matches) use ($self) {
-                return  '<strong><em>' .
-                    $self->parseInlineCallback($matches[2]) .
+            function ($matches) {
+                return '<strong><em>' .
+                    $this->parseInlineCallback($matches[2]) .
                     '</em></strong>';
             },
             $text
@@ -576,9 +593,9 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/(\*{2})(.+?)\\1/",
-            function ($matches) use ($self) {
-                return  '<strong>' .
-                    $self->parseInlineCallback($matches[2]) .
+            function ($matches) {
+                return '<strong>' .
+                    $this->parseInlineCallback($matches[2]) .
                     '</strong>';
             },
             $text
@@ -586,9 +603,9 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/(\*)(.+?)\\1/",
-            function ($matches) use ($self) {
-                return  '<em>' .
-                    $self->parseInlineCallback($matches[2]) .
+            function ($matches) {
+                return '<em>' .
+                    $this->parseInlineCallback($matches[2]) .
                     '</em>';
             },
             $text
@@ -596,9 +613,9 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/(\s+|^)(_{3})(.+?)\\2(\s+|$)/",
-            function ($matches) use ($self) {
-                return  $matches[1] . '<strong><em>' .
-                    $self->parseInlineCallback($matches[3]) .
+            function ($matches) {
+                return $matches[1] . '<strong><em>' .
+                    $this->parseInlineCallback($matches[3]) .
                     '</em></strong>' . $matches[4];
             },
             $text
@@ -606,9 +623,9 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/(\s+|^)(_{2})(.+?)\\2(\s+|$)/",
-            function ($matches) use ($self) {
-                return  $matches[1] . '<strong>' .
-                    $self->parseInlineCallback($matches[3]) .
+            function ($matches) {
+                return $matches[1] . '<strong>' .
+                    $this->parseInlineCallback($matches[3]) .
                     '</strong>' . $matches[4];
             },
             $text
@@ -616,9 +633,9 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/(\s+|^)(_)(.+?)\\2(\s+|$)/",
-            function ($matches) use ($self) {
-                return  $matches[1] . '<em>' .
-                    $self->parseInlineCallback($matches[3]) .
+            function ($matches) {
+                return $matches[1] . '<em>' .
+                    $this->parseInlineCallback($matches[3]) .
                     '</em>' . $matches[4];
             },
             $text
@@ -626,9 +643,9 @@ function ($matches) use ($self) {
 
         $text = preg_replace_callback(
             "/(~{2})(.+?)\\1/",
-            function ($matches) use ($self) {
-                return  '<del>' .
-                    $self->parseInlineCallback($matches[2]) .
+            function ($matches) {
+                return '<del>' .
+                    $this->parseInlineCallback($matches[2]) .
                     '</del>';
             },
             $text
@@ -641,26 +658,27 @@ function ($matches) use ($self) {
      * parseBlock
      *
      * @param string $text
-     * @param array $lines
+     * @param array|null $lines
+     *
      * @return array
      */
-    private function parseBlock($text, &$lines)
+    private function parseBlock(string $text, ?array &$lines): array
     {
         $lines = explode("\n", $text);
-        $this->_blocks = array();
+        $this->_blocks = [];
         $this->_current = 'normal';
         $this->_pos = -1;
 
-        $state = array(
-            'special'   =>  implode("|", array_keys($this->_specialWhiteList)),
-            'empty'     =>  0,
-            'html'      =>  false
-        );
+        $state = [
+            '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);
+            $args = [$block, $key, $line, &$state, $lines];
 
             if ($this->_current != 'normal') {
                 $pass = call_user_func_array($this->_parsers[$this->_current], $args);
@@ -685,13 +703,14 @@ private function parseBlock($text, &$lines)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockList($block, $key, $line, &$state)
+    private function parseBlockList(?array $block, int $key, string $line, ?array &$state): bool
     {
         if ($this->isBlock('list') && !preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line)) {
             if (preg_match("/^(\s*)(~{3,}|`{3,})([^`~]*)$/i", $line)) {
@@ -705,7 +724,7 @@ private function parseBlockList($block, $key, $line, &$state)
                 $this->setBlock($key);
                 return false;
             } elseif (preg_match("/^(\s*)$/", $line) && $state['empty'] == 0) {
-                $state['empty'] ++;
+                $state['empty']++;
                 $this->setBlock($key);
                 return false;
             }
@@ -735,19 +754,26 @@ private function parseBlockList($block, $key, $line, &$state)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockCode($block, $key, $line, $state)
+    private function parseBlockCode(?array $block, int $key, string $line, ?array &$state): bool
     {
         if (preg_match("/^(\s*)(~{3,}|`{3,})([^`~]*)$/i", $line, $matches)) {
             if ($this->isBlock('code')) {
+                if ($state['code'] != $matches[2]) {
+                    $this->setBlock($key);
+                    return false;
+                }
+
                 $isAfterList = $block[3][2];
 
                 if ($isAfterList) {
+                    $state['empty'] = 0;
                     $this->combineBlock()
                         ->setBlock($key);
                 } else {
@@ -763,9 +789,11 @@ private function parseBlockCode($block, $key, $line, $state)
                     $isAfterList = strlen($matches[1]) >= $space + $state['empty'];
                 }
 
-                $this->startBlock('code', $key, array(
-                    $matches[1],  $matches[3],  $isAfterList
-                ));
+                $state['code'] = $matches[2];
+
+                $this->startBlock('code', $key, [
+                    $matches[1], $matches[3], $isAfterList
+                ]);
             }
 
             return false;
@@ -778,13 +806,14 @@ private function parseBlockCode($block, $key, $line, $state)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockShtml($block, $key, $line, &$state)
+    private function parseBlockShtml(?array $block, int $key, string $line, ?array &$state): bool
     {
         if ($this->_html) {
             if (preg_match("/^(\s*)!!!(\s*)$/", $line, $matches)) {
@@ -805,13 +834,14 @@ private function parseBlockShtml($block, $key, $line, &$state)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockAhtml($block, $key, $line, &$state)
+    private function parseBlockAhtml(?array $block, int $key, string $line, ?array &$state): bool
     {
         if ($this->_html) {
             if (preg_match("/^\s*<({$this->_blockHtmlTags})(\s+[^>]*)?>/i", $line, $matches)) {
@@ -847,12 +877,13 @@ private function parseBlockAhtml($block, $key, $line, &$state)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockMath($block, $key, $line)
+    private function parseBlockMath(?array $block, int $key, string $line): bool
     {
         if (preg_match("/^(\s*)\\$\\$(\s*)$/", $line, $matches)) {
             if ($this->isBlock('math')) {
@@ -871,13 +902,14 @@ private function parseBlockMath($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockPre($block, $key, $line, &$state)
+    private function parseBlockPre(?array $block, int $key, string $line, ?array &$state): bool
     {
         if (preg_match("/^ {4}/", $line)) {
             if ($this->isBlock('pre')) {
@@ -896,13 +928,14 @@ private function parseBlockPre($block, $key, $line, &$state)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockHtml($block, $key, $line, &$state)
+    private function parseBlockHtml(?array $block, int $key, string $line, ?array &$state): bool
     {
         if (preg_match("/^\s*<({$state['special']})(\s+[^>]*)?>/i", $line, $matches)) {
             $tag = strtolower($matches[1]);
@@ -929,18 +962,19 @@ private function parseBlockHtml($block, $key, $line, &$state)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockFootnote($block, $key, $line)
+    private function parseBlockFootnote(?array $block, int $key, string $line): bool
     {
         if (preg_match("/^\[\^((?:[^\]]|\\]|\\[)+?)\]:/", $line, $matches)) {
             $space = strlen($matches[0]) - 1;
-            $this->startBlock('footnote', $key, array(
+            $this->startBlock('footnote', $key, [
                 $space, $matches[1]
-            ));
+            ]);
 
             return false;
         }
@@ -949,12 +983,13 @@ private function parseBlockFootnote($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockDefinition($block, $key, $line)
+    private function parseBlockDefinition(?array $block, int $key, string $line): bool
     {
         if (preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line, $matches)) {
             $this->_definitions[$matches[1]] = $this->cleanUrl($matches[2]);
@@ -968,12 +1003,13 @@ private function parseBlockDefinition($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockQuote($block, $key, $line)
+    private function parseBlockQuote(?array $block, int $key, string $line): bool
     {
         if (preg_match("/^(\s*)>/", $line, $matches)) {
             if ($this->isBlock('list') && strlen($matches[1]) > 0) {
@@ -991,74 +1027,77 @@ private function parseBlockQuote($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
-     * @param $lines
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     * @param array|null $lines
+     *
      * @return bool
      */
-    private function parseBlockTable($block, $key, $line, &$state, $lines)
+    private function parseBlockTable(?array $block, int $key, string $line, ?array &$state, array $lines): bool
     {
-        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);
+        if (preg_match("/^\s*(\|?[ :]*-{2,}[ :]*(?:[\|\+][ :]*-{2,}[ :]*)*\|?)\s*$/", $line, $matches)) {
+            if (strpos($matches[1], '|') !== false || strpos($matches[1], '+') !== false) {
+                if ($this->isBlock('table')) {
+                    $block[3][0][] = $block[3][2];
+                    $block[3][2]++;
+                    $this->setBlock($key, $block[3]);
                 } else {
-                    $head = 1;
-                    $this->backBlock(1, 'table');
-                }
+                    $head = 0;
 
-                if ($matches[1][0] == '|') {
-                    $matches[1] = substr($matches[1], 1);
+                    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][strlen($matches[1]) - 1] == '|') {
-                        $matches[1] = substr($matches[1], 0, -1);
+                    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';
-                        } elseif (!empty($matches[1])) {
-                            $align = 'left';
-                        } elseif (!empty($matches[2])) {
-                            $align = 'right';
+                    $rows = preg_split("/(\+|\|)/", $matches[1]);
+                    $aligns = [];
+                    foreach ($rows as $row) {
+                        $align = 'none';
+
+                        if (preg_match("/^\s*(:?)\-+(:?)\s*$/", $row, $matches)) {
+                            if (!empty($matches[1]) && !empty($matches[2])) {
+                                $align = 'center';
+                            } elseif (!empty($matches[1])) {
+                                $align = 'left';
+                            } elseif (!empty($matches[2])) {
+                                $align = 'right';
+                            }
                         }
+
+                        $aligns[] = $align;
                     }
 
-                    $aligns[] = $align;
+                    $this->setBlock($key, [[$head], $aligns, $head + 1]);
                 }
-
-                $this->setBlock($key, array(array($head), $aligns, $head + 1));
+                return false;
             }
-
-            return false;
         }
 
         return true;
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockSh($block, $key, $line)
+    private function parseBlockSh(?array $block, int $key, string $line): bool
     {
         if (preg_match("/^(#+)(.*)$/", $line, $matches)) {
             $num = min(strlen($matches[1]), 6);
@@ -1072,17 +1111,18 @@ private function parseBlockSh($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
-     * @param $lines
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     * @param array $lines
+     *
      * @return bool
      */
-    private function parseBlockMh($block, $key, $line, &$state, $lines)
+    private function parseBlockMh(?array $block, int $key, string $line, ?array &$state, array $lines): bool
     {
         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
+            && ($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)
@@ -1098,14 +1138,15 @@ private function parseBlockMh($block, $key, $line, &$state, $lines)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockShr($block, $key, $line)
+    private function parseBlockShr(?array $block, int $key, string $line): bool
     {
-        if (preg_match("/^(\* *){3,}\s*$/", $line)) {
+        if (preg_match("/^\*{3,}\s*$/", $line)) {
             $this->startBlock('hr', $key)
                 ->endBlock();
 
@@ -1116,14 +1157,15 @@ private function parseBlockShr($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     *
      * @return bool
      */
-    private function parseBlockDhr($block, $key, $line)
+    private function parseBlockDhr(?array $block, int $key, string $line): bool
     {
-        if (preg_match("/^(- *){3,}\s*$/", $line)) {
+        if (preg_match("/^-{3,}\s*$/", $line)) {
             $this->startBlock('hr', $key)
                 ->endBlock();
 
@@ -1134,13 +1176,14 @@ private function parseBlockDhr($block, $key, $line)
     }
 
     /**
-     * @param $block
-     * @param $key
-     * @param $line
-     * @param $state
+     * @param array|null $block
+     * @param int $key
+     * @param string $line
+     * @param array|null $state
+     *
      * @return bool
      */
-    private function parseBlockDefault($block, $key, $line, &$state)
+    private function parseBlockDefault(?array $block, int $key, string $line, ?array &$state): bool
     {
         if ($this->isBlock('footnote')) {
             preg_match("/^(\s*)/", $line, $matches);
@@ -1151,7 +1194,7 @@ private function parseBlockDefault($block, $key, $line, &$state)
             }
         } elseif ($this->isBlock('table')) {
             if (false !== strpos($line, '|')) {
-                $block[3][2] ++;
+                $block[3][2]++;
                 $this->setBlock($key, $block[3]);
             } else {
                 $this->startBlock('normal', $key);
@@ -1176,9 +1219,10 @@ private function parseBlockDefault($block, $key, $line, &$state)
     /**
      * @param array $blocks
      * @param array $lines
+     *
      * @return array
      */
-    private function optimizeBlocks(array $blocks, array $lines)
+    private function optimizeBlocks(array $blocks, array $lines): array
     {
         $blocks = $this->call('beforeOptimizeBlocks', $blocks, $lines);
 
@@ -1187,10 +1231,10 @@ private function optimizeBlocks(array $blocks, array $lines)
             $moved = false;
 
             $block = &$blocks[$key];
-            $prevBlock = isset($blocks[$key - 1]) ? $blocks[$key - 1] : NULL;
-            $nextBlock = isset($blocks[$key + 1]) ? $blocks[$key + 1] : NULL;
+            $prevBlock = $blocks[$key - 1] ?? null;
+            $nextBlock = $blocks[$key + 1] ?? null;
 
-            list($type, $from, $to) = $block;
+            [$type, $from, $to] = $block;
 
             if ('pre' == $type) {
                 $isEmpty = array_reduce(
@@ -1208,7 +1252,7 @@ function ($result, $line) {
 
             if ('normal' == $type) {
                 // combine two blocks
-                $types = array('list', 'quote');
+                $types = ['list', 'quote'];
 
                 if ($from == $to && preg_match("/^\s*$/", $lines[$from])
                     && !empty($prevBlock) && !empty($nextBlock)) {
@@ -1216,9 +1260,9 @@ function ($result, $line) {
                         && ($prevBlock[0] != 'list'
                             || ($prevBlock[3][0] == $nextBlock[3][0] && $prevBlock[3][1] == $nextBlock[3][1]))) {
                         // combine 3 blocks
-                        $blocks[$key - 1] = array(
-                            $prevBlock[0],  $prevBlock[1],  $nextBlock[2], $prevBlock[3] ?? null
-                        );
+                        $blocks[$key - 1] = [
+                            $prevBlock[0], $prevBlock[1], $nextBlock[2], $prevBlock[3] ?? null
+                        ];
                         array_splice($blocks, $key, 2);
 
                         // do not move
@@ -1228,7 +1272,7 @@ function ($result, $line) {
             }
 
             if (!$moved) {
-                $key ++;
+                $key++;
             }
         }
 
@@ -1241,20 +1285,21 @@ function ($result, $line) {
      * @param array $lines
      * @param array $parts
      * @param int $start
+     *
      * @return string
      */
-    private function parseCode(array $lines, array $parts, $start)
+    private function parseCode(array $lines, array $parts, int $start): string
     {
-        list($blank, $lang) = $parts;
+        [$blank, $lang] = $parts;
         $lang = trim($lang);
         $count = strlen($blank);
 
         if (!preg_match("/^[_a-z0-9-\+\#\:\.]+$/i", $lang)) {
-            $lang = NULL;
+            $lang = null;
         } else {
             $parts = explode(':', $lang);
             if (count($parts) > 1) {
-                list($lang, $rel) = $parts;
+                [$lang, $rel] = $parts;
                 $lang = trim($lang);
                 $rel = trim($rel);
             }
@@ -1284,9 +1329,10 @@ private function parseCode(array $lines, array $parts, $start)
      * @param array $lines
      * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parsePre(array $lines, $value, $start)
+    private function parsePre(array $lines, $value, int $start): string
     {
         foreach ($lines as &$line) {
             $line = htmlspecialchars(substr($line, 4));
@@ -1302,9 +1348,10 @@ private function parsePre(array $lines, $value, $start)
      * @param array $lines
      * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parseAhtml(array $lines, $value, $start)
+    private function parseAhtml(array $lines, $value, int $start): string
     {
         return trim(implode("\n", $this->markLines($lines, $start)));
     }
@@ -1315,9 +1362,10 @@ private function parseAhtml(array $lines, $value, $start)
      * @param array $lines
      * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parseShtml(array $lines, $value, $start)
+    private function parseShtml(array $lines, $value, int $start): string
     {
         return trim(implode("\n", $this->markLines(array_slice($lines, 1, -1), $start + 1)));
     }
@@ -1329,9 +1377,10 @@ private function parseShtml(array $lines, $value, $start)
      * @param mixed $value
      * @param int $start
      * @param int $end
+     *
      * @return string
      */
-    private function parseMath(array $lines, $value, $start, $end)
+    private function parseMath(array $lines, $value, int $start, int $end): string
     {
         return '<p>' . $this->markLine($start, $end) . htmlspecialchars(implode("\n", $lines)) . '</p>';
     }
@@ -1343,9 +1392,10 @@ private function parseMath(array $lines, $value, $start, $end)
      * @param int $num
      * @param int $start
      * @param int $end
+     *
      * @return string
      */
-    private function parseSh(array $lines, $num, $start, $end)
+    private function parseSh(array $lines, int $num, int $start, int $end): string
     {
         $line = $this->markLine($start, $end) . $this->parseInline(trim($lines[0], '# '));
         return preg_match("/^\s*$/", $line) ? '' : "<h{$num}>{$line}</h{$num}>";
@@ -1358,9 +1408,10 @@ private function parseSh(array $lines, $num, $start, $end)
      * @param int $num
      * @param int $start
      * @param int $end
+     *
      * @return string
      */
-    private function parseMh(array $lines, $num, $start, $end)
+    private function parseMh(array $lines, int $num, int $start, int $end): string
     {
         return $this->parseSh($lines, $num, $start, $end);
     }
@@ -1371,9 +1422,10 @@ private function parseMh(array $lines, $num, $start, $end)
      * @param array $lines
      * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parseQuote(array $lines, $value, $start)
+    private function parseQuote(array $lines, $value, int $start): string
     {
         foreach ($lines as &$line) {
             $line = preg_replace("/^\s*> ?/", '', $line);
@@ -1389,23 +1441,24 @@ private function parseQuote(array $lines, $value, $start)
      * @param array $lines
      * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parseList(array $lines, $value, $start)
+    private function parseList(array $lines, $value, int $start): string
     {
         $html = '';
-        list($space, $type, $tab) = $value;
-        $rows = array();
+        [$space, $type, $tab] = $value;
+        $rows = [];
         $suffix = '';
         $last = 0;
 
         foreach ($lines as $key => $line) {
             if (preg_match("/^(\s{" . $space . "})((?:[0-9]+\.?)|\-|\+|\*)(\s+)(.*)$/i", $line, $matches)) {
                 if ($type == 'ol' && $key == 0) {
-                    $start = intval($matches[2]);
+                    $olStart = intval($matches[2]);
 
-                    if ($start != 1) {
-                        $suffix = ' start="' . $start . '"';
+                    if ($olStart != 1) {
+                        $suffix = ' start="' . $olStart . '"';
                     }
                 }
 
@@ -1426,17 +1479,18 @@ private function parseList(array $lines, $value, $start)
 
     /**
      * @param array $lines
-     * @param array $value
+     * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parseTable(array $lines, array $value, $start)
+    private function parseTable(array $lines, $value, int $start): string
     {
-        list($ignores, $aligns) = $value;
+        [$ignores, $aligns] = $value;
         $head = count($ignores) > 0 && array_sum($ignores) > 0;
 
         $html = '<table>';
-        $body = $head ? NULL : true;
+        $body = $head ? null : true;
         $output = false;
 
         foreach ($lines as $key => $line) {
@@ -1468,19 +1522,19 @@ private function parseTable(array $lines, array $value, $start)
                     return trim($row);
                 }
             }, explode('|', $line));
-            $columns = array();
+            $columns = [];
             $last = -1;
 
             foreach ($rows as $row) {
                 if (strlen($row) > 0) {
-                    $last ++;
-                    $columns[$last] = array(
-                        isset($columns[$last]) ? $columns[$last][0] + 1 : 1,  $row
-                    );
+                    $last++;
+                    $columns[$last] = [
+                        isset($columns[$last]) ? $columns[$last][0] + 1 : 1, $row
+                    ];
                 } elseif (isset($columns[$last])) {
-                    $columns[$last][0] ++;
+                    $columns[$last][0]++;
                 } else {
-                    $columns[0] = array(1, $row);
+                    $columns[0] = [1, $row];
                 }
             }
 
@@ -1495,7 +1549,7 @@ private function parseTable(array $lines, array $value, $start)
                     . '" data-id="' . $this->_uniqid . '"' : '') . '>';
 
             foreach ($columns as $key => $column) {
-                list($num, $text) = $column;
+                [$num, $text] = $column;
                 $tag = $head ? 'th' : 'td';
 
                 $html .= "<{$tag}";
@@ -1519,7 +1573,7 @@ private function parseTable(array $lines, array $value, $start)
             }
         }
 
-        if ($body !== NULL) {
+        if ($body !== null) {
             $html .= '</tbody>';
         }
 
@@ -1531,11 +1585,12 @@ private function parseTable(array $lines, array $value, $start)
      * parseHr
      *
      * @param array $lines
-     * @param array $value
+     * @param mixed $value
      * @param int $start
+     *
      * @return string
      */
-    private function parseHr($lines, $value, $start)
+    private function parseHr(array $lines, $value, int $start): string
     {
         return $this->_line ? '<hr class="line" data-start="' . $start . '" data-end="' . $start . '">' : '<hr>';
     }
@@ -1544,11 +1599,12 @@ private function parseHr($lines, $value, $start)
      * parseNormal
      *
      * @param array $lines
-     * @param bool $inline
+     * @param mixed $inline
      * @param int $start
+     *
      * @return string
      */
-    private function parseNormal(array $lines, $inline, $start)
+    private function parseNormal(array $lines, $inline, int $start): string
     {
         foreach ($lines as $key => &$line) {
             $line = $this->parseInline($line);
@@ -1573,11 +1629,12 @@ private function parseNormal(array $lines, $inline, $start)
      *
      * @param array $lines
      * @param array $value
+     *
      * @return string
      */
-    private function parseFootnote(array $lines, array $value)
+    private function parseFootnote(array $lines, array $value): string
     {
-        list($space, $note) = $value;
+        [$space, $note] = $value;
         $index = array_search($note, $this->_footnotes);
 
         if (false !== $index) {
@@ -1593,7 +1650,7 @@ private function parseFootnote(array $lines, array $value)
      *
      * @return string
      */
-    private function parseDefinition()
+    private function parseDefinition(): string
     {
         return '';
     }
@@ -1604,13 +1661,14 @@ private function parseDefinition()
      * @param array $lines
      * @param string $type
      * @param int $start
+     *
      * @return string
      */
-    private function parseHtml(array $lines, $type, $start)
+    private function parseHtml(array $lines, string $type, int $start): string
     {
         foreach ($lines as &$line) {
             $line = $this->parseInline($line,
-                isset($this->_specialWhiteList[$type]) ? $this->_specialWhiteList[$type] : '');
+                $this->_specialWhiteList[$type] ?? '');
         }
 
         return implode("\n", $this->markLines($lines, $start));
@@ -1618,27 +1676,48 @@ private function parseHtml(array $lines, $type, $start)
 
     /**
      * @param $url
-     * @return string
+     * @param bool $parseTitle
+     *
+     * @return mixed
      */
-    public function cleanUrl($url)
+    private function cleanUrl($url, bool $parseTitle = false)
     {
-        if (preg_match("/^\s*((http|https|ftp|mailto):\S+)/i", $url, $matches)) {
-            return $matches[1];
-        } elseif (preg_match("/^\s*(\S+)/i", $url, $matches)) {
-            return $matches[1];
-        } else {
+        $title = null;
+        $url = trim($url);
+
+        if ($parseTitle) {
+            $pos = strpos($url, ' ');
+
+            if ($pos !== false) {
+                $title = htmlspecialchars(trim(substr($url, $pos + 1), ' "\''));
+                $url = substr($url, 0, $pos);
+            }
+        }
+
+        $url = preg_replace("/[\"'<>\s]/", '', $url);
+
+        if (preg_match("/^(mailto:)?[_a-z0-9-\.\+]+@[_\w-]+(?:\.[a-z]{2,})+$/i", $url, $matches)) {
+            if (empty($matches[1])) {
+                $url = 'mailto:' . $url;
+            }
+        }
+
+        if (preg_match("/^\w+:/i", $url) && !preg_match("/^(https?|mailto):/i", $url)) {
             return '#';
         }
+
+        return $parseTitle ? [$url, $title] : $url;
     }
 
     /**
      * @param $str
-     * @return mixed
+     *
+     * @return string
      */
-    public function escapeBracket($str)
+    private function escapeBracket($str): string
     {
         return str_replace(
-            array('\[', '\]', '\(', '\)'),  array('[', ']', '(', ')'),  $str
+            ['\[', '\]', '\(', '\)'], ['[', ']', '(', ')'], $str
         );
     }
 
@@ -1648,14 +1727,15 @@ public function escapeBracket($str)
      * @param mixed $type
      * @param mixed $start
      * @param mixed $value
+     *
      * @return $this
      */
-    private function startBlock($type, $start, $value = NULL)
+    private function startBlock($type, $start, $value = null): Parser
     {
-        $this->_pos ++;
+        $this->_pos++;
         $this->_current = $type;
 
-        $this->_blocks[$this->_pos] = array($type, $start, $start, $value);
+        $this->_blocks[$this->_pos] = [$type, $start, $start, $value];
 
         return $this;
     }
@@ -1665,7 +1745,7 @@ private function startBlock($type, $start, $value = NULL)
      *
      * @return $this
      */
-    private function endBlock()
+    private function endBlock(): Parser
     {
         $this->_current = 'normal';
         return $this;
@@ -1676,12 +1756,13 @@ private function endBlock()
      *
      * @param mixed $type
      * @param mixed $value
+     *
      * @return bool
      */
-    private function isBlock($type, $value = NULL)
+    private function isBlock($type, $value = null): bool
     {
         return $this->_current == $type
-            && (NULL === $value ? true : $this->_blocks[$this->_pos][3] == $value);
+            && (null === $value || $this->_blocks[$this->_pos][3] == $value);
     }
 
     /**
@@ -1689,9 +1770,9 @@ private function isBlock($type, $value = NULL)
      *
      * @return array
      */
-    private function getBlock()
+    private function getBlock(): ?array
     {
-        return isset($this->_blocks[$this->_pos]) ? $this->_blocks[$this->_pos] : NULL;
+        return $this->_blocks[$this->_pos] ?? null;
     }
 
     /**
@@ -1699,15 +1780,16 @@ private function getBlock()
      *
      * @param mixed $to
      * @param mixed $value
+     *
      * @return $this
      */
-    private function setBlock($to = NULL, $value = NULL)
+    private function setBlock($to = null, $value = null): Parser
     {
-        if (NULL !== $to) {
+        if (null !== $to) {
             $this->_blocks[$this->_pos][2] = $to;
         }
 
-        if (NULL !== $value) {
+        if (null !== $value) {
             $this->_blocks[$this->_pos][3] = $value;
         }
 
@@ -1720,9 +1802,10 @@ private function setBlock($to = NULL, $value = NULL)
      * @param mixed $step
      * @param mixed $type
      * @param mixed $value
+     *
      * @return $this
      */
-    private function backBlock($step, $type, $value = NULL)
+    private function backBlock($step, $type, $value = null): Parser
     {
         if ($this->_pos < 0) {
             return $this->startBlock($type, 0, $value);
@@ -1732,13 +1815,13 @@ private function backBlock($step, $type, $value = NULL)
         $this->_blocks[$this->_pos][2] = $last - $step;
 
         if ($this->_blocks[$this->_pos][1] <= $this->_blocks[$this->_pos][2]) {
-            $this->_pos ++;
+            $this->_pos++;
         }
 
         $this->_current = $type;
-        $this->_blocks[$this->_pos] = array(
-            $type,  $last - $step + 1,  $last,  $value
-        );
+        $this->_blocks[$this->_pos] = [
+            $type, $last - $step + 1, $last, $value
+        ];
 
         return $this;
     }
@@ -1746,7 +1829,7 @@ private function backBlock($step, $type, $value = NULL)
     /**
      * @return $this
      */
-    private function combineBlock()
+    private function combineBlock(): Parser
     {
         if ($this->_pos < 1) {
             return $this;
@@ -1759,7 +1842,7 @@ private function combineBlock()
         $this->_blocks[$this->_pos - 1] = $prev;
         $this->_current = $prev[0];
         unset($this->_blocks[$this->_pos]);
-        $this->_pos --;
+        $this->_pos--;
 
         return $this;
     }
