diff --git a/README.md b/README.md
index 4fb2273..f7deb58 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,27 @@ woytu Soft:https://www.woytu.com
 └ ......
 ```
 
+* [https://www.php.net/manual/zh](https://www.php.net/manual/zh)
+
+---
+
+* [https://github.com/erusev/parsedown](https://github.com/erusev/parsedown)
+
+* [https://github.com/erusev/parsedown-extra](https://github.com/erusev/parsedown-extra)
+
+* [https://github.com/taufik-nurrohman/parsedown-extra-plugin](https://github.com/taufik-nurrohman/parsedown-extra-plugin)
+
+* [https://github.com/mrgeneralgoo/typecho-markdown](https://github.com/mrgeneralgoo/typecho-markdown)
+
+---
+
+* [https://getcomposer.org](https://getcomposer.org)
+
+* [https://github.com/michelf/php-markdown](https://github.com/michelf/php-markdown)
+
+* [https://github.com/thephpleague/commonmark](https://github.com/thephpleague/commonmark)
+
+
 ## 注意事项:
 
 ### 不显示文件和目录
diff --git a/resources/DirectoryLister.php b/resources/DirectoryLister.php
index 05950d3..6137d48 100644
--- a/resources/DirectoryLister.php
+++ b/resources/DirectoryLister.php
@@ -2,6 +2,8 @@
 // https://github.com/erusev/parsedown
 // 导入Parsedown依赖
 require_once('Parsedown.php');
+require_once('ParsedownExtra.php');
+require_once('ParsedownExtraPlugin.php');
 /**
  * 一个简单的基于PHP的目录列表器,列出了内容
  * 目录及其所有子目录,并且允许轻松
@@ -464,11 +466,32 @@ public function getReadme()
         // 如果配置为空,默认使用Markdown方式
         if ($readme_mode == "" || strtoupper($readme_mode) != "HTML") {
             $md_path =  $md_path . "/README.md";
-            // https://github.com/erusev/parsedown
-            $Parsedown = new Parsedown();
+
             if (!file_exists($md_path)) {
                 return "";
             }
+            // https://github.com/erusev/parsedown
+            // https://github.com/erusev/parsedown-extra
+            // https://github.com/tovic/parsedown-extra-plugin
+            $Parsedown = new ParsedownExtraPlugin();
+
+            $Parsedown->headerAttributes = function ($Text, $Attributes, &$Element, $Level) {
+                $Id = $Attributes['id'] ?? trim(
+                    preg_replace(['/[^a-z\d\x{4e00}-\x{9fa5}]+/u'], '-', strtolower($Text)),
+                    '-'
+                );
+                return ['id' => $Id];
+            };
+            $Parsedown->linkAttributes = function ($Text, $Attributes, &$Element, $Internal) {
+                $href = strtolower($Attributes['href']);
+                // https://www.chrisyue.com/the-fastest-way-to-implement-starts-with-in-php.html
+                if (!$Internal && strpos($href, "https") === 0 && strpos($href, "http") === 0) {
+                    return [
+                        'target' => '_blank'
+                    ];
+                }
+                return [];
+            };
             return $Parsedown->text(file_get_contents($md_path));
         } else {
             $md_path =  $md_path . "/README.html";
diff --git a/resources/Parsedown.php b/resources/Parsedown.php
index a34b44f..1832db7 100644
--- a/resources/Parsedown.php
+++ b/resources/Parsedown.php
@@ -17,11 +17,24 @@ class Parsedown
 {
     # ~
 
-    const version = '1.7.3';
+    const version = '1.8.0-beta-7';
 
     # ~
 
     function text($text)
+    {
+        $Elements = $this->textElements($text);
+
+        # convert to markup
+        $markup = $this->elements($Elements);
+
+        # trim line breaks
+        $markup = trim($markup, "\n");
+
+        return $markup;
+    }
+
+    protected function textElements($text)
     {
         # make sure no definitions are set
         $this->DefinitionData = array();
@@ -36,12 +49,7 @@ function text($text)
         $lines = explode("\n", $text);
 
         # iterate through lines to identify blocks
-        $markup = $this->lines($lines);
-
-        # trim line breaks
-        $markup = trim($markup, "\n");
-
-        return $markup;
+        return $this->linesElements($lines);
     }
 
     #
@@ -84,12 +92,22 @@ function setSafeMode($safeMode)
 
     protected $safeMode;
 
+    function setStrictMode($strictMode)
+    {
+        $this->strictMode = (bool) $strictMode;
+
+        return $this;
+    }
+
+    protected $strictMode;
+
     protected $safeLinksWhitelist = array(
         'http://',
         'https://',
         'ftp://',
         'ftps://',
         'mailto:',
+        'tel:',
         'data:image/png;base64,',
         'data:image/gif;base64,',
         'data:image/jpeg;base64,',
@@ -143,6 +161,12 @@ function setSafeMode($safeMode)
 
     protected function lines(array $lines)
     {
+        return $this->elements($this->linesElements($lines));
+    }
+
+    protected function linesElements(array $lines)
+    {
+        $Elements = array();
         $CurrentBlock = null;
 
         foreach ($lines as $line)
@@ -151,35 +175,25 @@ protected function lines(array $lines)
             {
                 if (isset($CurrentBlock))
                 {
-                    $CurrentBlock['interrupted'] = true;
+                    $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
+                        ? $CurrentBlock['interrupted'] + 1 : 1
+                    );
                 }
 
                 continue;
             }
 
-            if (strpos($line, "\t") !== false)
+            while (($beforeTab = strstr($line, "\t", true)) !== false)
             {
-                $parts = explode("\t", $line);
-
-                $line = $parts[0];
-
-                unset($parts[0]);
+                $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
 
-                foreach ($parts as $part)
-                {
-                    $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
-
-                    $line .= str_repeat(' ', $shortage);
-                    $line .= $part;
-                }
+                $line = $beforeTab
+                    . str_repeat(' ', $shortage)
+                    . substr($line, strlen($beforeTab) + 1)
+                ;
             }
 
-            $indent = 0;
-
-            while (isset($line[$indent]) and $line[$indent] === ' ')
-            {
-                $indent ++;
-            }
+            $indent = strspn($line, ' ');
 
             $text = $indent > 0 ? substr($line, $indent) : $line;
 
@@ -191,7 +205,8 @@ protected function lines(array $lines)
 
             if (isset($CurrentBlock['continuable']))
             {
-                $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+                $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
+                $Block = $this->$methodName($Line, $CurrentBlock);
 
                 if (isset($Block))
                 {
@@ -203,7 +218,8 @@ protected function lines(array $lines)
                 {
                     if ($this->isBlockCompletable($CurrentBlock['type']))
                     {
-                        $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+                        $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+                        $CurrentBlock = $this->$methodName($CurrentBlock);
                     }
                 }
             }
@@ -229,7 +245,7 @@ protected function lines(array $lines)
 
             foreach ($blockTypes as $blockType)
             {
-                $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+                $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
 
                 if (isset($Block))
                 {
@@ -237,7 +253,10 @@ protected function lines(array $lines)
 
                     if ( ! isset($Block['identified']))
                     {
-                        $Blocks []= $CurrentBlock;
+                        if (isset($CurrentBlock))
+                        {
+                            $Elements[] = $this->extractElement($CurrentBlock);
+                        }
 
                         $Block['identified'] = true;
                     }
@@ -255,13 +274,21 @@ protected function lines(array $lines)
 
             # ~
 
-            if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+            if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
             {
-                $CurrentBlock['element']['text'] .= "\n".$text;
+                $Block = $this->paragraphContinue($Line, $CurrentBlock);
+            }
+
+            if (isset($Block))
+            {
+                $CurrentBlock = $Block;
             }
             else
             {
-                $Blocks []= $CurrentBlock;
+                if (isset($CurrentBlock))
+                {
+                    $Elements[] = $this->extractElement($CurrentBlock);
+                }
 
                 $CurrentBlock = $this->paragraph($Line);
 
@@ -273,45 +300,47 @@ protected function lines(array $lines)
 
         if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
         {
-            $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+            $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+            $CurrentBlock = $this->$methodName($CurrentBlock);
         }
 
         # ~
 
-        $Blocks []= $CurrentBlock;
-
-        unset($Blocks[0]);
+        if (isset($CurrentBlock))
+        {
+            $Elements[] = $this->extractElement($CurrentBlock);
+        }
 
         # ~
 
-        $markup = '';
+        return $Elements;
+    }
 
-        foreach ($Blocks as $Block)
+    protected function extractElement(array $Component)
+    {
+        if ( ! isset($Component['element']))
         {
-            if (isset($Block['hidden']))
+            if (isset($Component['markup']))
             {
-                continue;
+                $Component['element'] = array('rawHtml' => $Component['markup']);
+            }
+            elseif (isset($Component['hidden']))
+            {
+                $Component['element'] = array();
             }
-
-            $markup .= "\n";
-            $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
         }
 
-        $markup .= "\n";
-
-        # ~
-
-        return $markup;
+        return $Component['element'];
     }
 
     protected function isBlockContinuable($Type)
     {
-        return method_exists($this, 'block'.$Type.'Continue');
+        return method_exists($this, 'block' . $Type . 'Continue');
     }
 
     protected function isBlockCompletable($Type)
     {
-        return method_exists($this, 'block'.$Type.'Complete');
+        return method_exists($this, 'block' . $Type . 'Complete');
     }
 
     #
@@ -319,7 +348,7 @@ protected function isBlockCompletable($Type)
 
     protected function blockCode($Line, $Block = null)
     {
-        if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+        if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
         {
             return;
         }
@@ -331,8 +360,7 @@ protected function blockCode($Line, $Block = null)
             $Block = array(
                 'element' => array(
                     'name' => 'pre',
-                    'handler' => 'element',
-                    'text' => array(
+                    'element' => array(
                         'name' => 'code',
                         'text' => $text,
                     ),
@@ -349,16 +377,16 @@ protected function blockCodeContinue($Line, $Block)
         {
             if (isset($Block['interrupted']))
             {
-                $Block['element']['text']['text'] .= "\n";
+                $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
 
                 unset($Block['interrupted']);
             }
 
-            $Block['element']['text']['text'] .= "\n";
+            $Block['element']['element']['text'] .= "\n";
 
             $text = substr($Line['body'], 4);
 
-            $Block['element']['text']['text'] .= $text;
+            $Block['element']['element']['text'] .= $text;
 
             return $Block;
         }
@@ -366,10 +394,6 @@ protected function blockCodeContinue($Line, $Block)
 
     protected function blockCodeComplete($Block)
     {
-        $text = $Block['element']['text']['text'];
-
-        $Block['element']['text']['text'] = $text;
-
         return $Block;
     }
 
@@ -383,13 +407,16 @@ protected function blockComment($Line)
             return;
         }
 
-        if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+        if (strpos($Line['text'], '<!--') === 0)
         {
             $Block = array(
-                'markup' => $Line['body'],
+                'element' => array(
+                    'rawHtml' => $Line['body'],
+                    'autobreak' => true,
+                ),
             );
 
-            if (preg_match('/-->$/', $Line['text']))
+            if (strpos($Line['text'], '-->') !== false)
             {
                 $Block['closed'] = true;
             }
@@ -405,9 +432,9 @@ protected function blockCommentContinue($Line, array $Block)
             return;
         }
 
-        $Block['markup'] .= "\n" . $Line['body'];
+        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
 
-        if (preg_match('/-->$/', $Line['text']))
+        if (strpos($Line['text'], '-->') !== false)
         {
             $Block['closed'] = true;
         }
@@ -420,47 +447,56 @@ protected function blockCommentContinue($Line, array $Block)
 
     protected function blockFencedCode($Line)
     {
-        if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
+        $marker = $Line['text'][0];
+
+        $openerLength = strspn($Line['text'], $marker);
+
+        if ($openerLength < 3)
         {
-            $Element = array(
-                'name' => 'code',
-                'text' => '',
-            );
+            return;
+        }
 
-            if (isset($matches[1]))
-            {
-                /**
-                 * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
-                 * Every HTML element may have a class attribute specified.
-                 * The attribute, if specified, must have a value that is a set
-                 * of space-separated tokens representing the various classes
-                 * that the element belongs to.
-                 * [...]
-                 * The space characters, for the purposes of this specification,
-                 * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
-                 * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
-                 * U+000D CARRIAGE RETURN (CR).
-                 */
-                $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
-
-                $class = 'language-'.$language;
-
-                $Element['attributes'] = array(
-                    'class' => $class,
-                );
-            }
+        $infostring = trim(substr($Line['text'], $openerLength), "\t ");
 
-            $Block = array(
-                'char' => $Line['text'][0],
-                'element' => array(
-                    'name' => 'pre',
-                    'handler' => 'element',
-                    'text' => $Element,
-                ),
-            );
+        if (strpos($infostring, '`') !== false)
+        {
+            return;
+        }
 
-            return $Block;
+        $Element = array(
+            'name' => 'code',
+            'text' => '',
+        );
+
+        if ($infostring !== '')
+        {
+            /**
+             * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
+             * Every HTML element may have a class attribute specified.
+             * The attribute, if specified, must have a value that is a set
+             * of space-separated tokens representing the various classes
+             * that the element belongs to.
+             * [...]
+             * The space characters, for the purposes of this specification,
+             * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
+             * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
+             * U+000D CARRIAGE RETURN (CR).
+             */
+            $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
+
+            $Element['attributes'] = array('class' => "language-$language");
         }
+
+        $Block = array(
+            'char' => $marker,
+            'openerLength' => $openerLength,
+            'element' => array(
+                'name' => 'pre',
+                'element' => $Element,
+            ),
+        );
+
+        return $Block;
     }
 
     protected function blockFencedCodeContinue($Line, $Block)
@@ -472,31 +508,28 @@ protected function blockFencedCodeContinue($Line, $Block)
 
         if (isset($Block['interrupted']))
         {
-            $Block['element']['text']['text'] .= "\n";
+            $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
 
             unset($Block['interrupted']);
         }
 
-        if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
-        {
-            $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+        if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
+            and chop(substr($Line['text'], $len), ' ') === ''
+        ) {
+            $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
 
             $Block['complete'] = true;
 
             return $Block;
         }
 
-        $Block['element']['text']['text'] .= "\n".$Line['body'];
+        $Block['element']['element']['text'] .= "\n" . $Line['body'];
 
         return $Block;
     }
 
     protected function blockFencedCodeComplete($Block)
     {
-        $text = $Block['element']['text']['text'];
-
-        $Block['element']['text']['text'] = $text;
-
         return $Block;
     }
 
@@ -505,71 +538,103 @@ protected function blockFencedCodeComplete($Block)
 
     protected function blockHeader($Line)
     {
-        if (isset($Line['text'][1]))
+        $level = strspn($Line['text'], '#');
+
+        if ($level > 6)
         {
-            $level = 1;
+            return;
+        }
 
-            while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
-            {
-                $level ++;
-            }
+        $text = trim($Line['text'], '#');
 
-            if ($level > 6)
-            {
-                return;
-            }
+        if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
+        {
+            return;
+        }
 
-            $text = trim($Line['text'], '# ');
+        $text = trim($text, ' ');
 
-            $Block = array(
-                'element' => array(
-                    'name' => 'h' . min(6, $level),
-                    'text' => $text,
-                    'handler' => 'line',
-                ),
-            );
+        $Block = array(
+            'element' => array(
+                'name' => 'h' . $level,
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $text,
+                    'destination' => 'elements',
+                )
+            ),
+        );
 
-            return $Block;
-        }
+        return $Block;
     }
 
     #
     # List
 
-    protected function blockList($Line)
+    protected function blockList($Line, array $CurrentBlock = null)
     {
-        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
 
-        if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+        if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
         {
+            $contentIndent = strlen($matches[2]);
+
+            if ($contentIndent >= 5)
+            {
+                $contentIndent -= 1;
+                $matches[1] = substr($matches[1], 0, -$contentIndent);
+                $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
+            }
+            elseif ($contentIndent === 0)
+            {
+                $matches[1] .= ' ';
+            }
+
+            $markerWithoutWhitespace = strstr($matches[1], ' ', true);
+
             $Block = array(
                 'indent' => $Line['indent'],
                 'pattern' => $pattern,
+                'data' => array(
+                    'type' => $name,
+                    'marker' => $matches[1],
+                    'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
+                ),
                 'element' => array(
                     'name' => $name,
-                    'handler' => 'elements',
+                    'elements' => array(),
                 ),
             );
+            $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
 
-            if($name === 'ol')
+            if ($name === 'ol')
             {
-                $listStart = stristr($matches[0], '.', true);
+                $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
 
-                if($listStart !== '1')
+                if ($listStart !== '1')
                 {
+                    if (
+                        isset($CurrentBlock)
+                        and $CurrentBlock['type'] === 'Paragraph'
+                        and ! isset($CurrentBlock['interrupted'])
+                    ) {
+                        return;
+                    }
+
                     $Block['element']['attributes'] = array('start' => $listStart);
                 }
             }
 
             $Block['li'] = array(
                 'name' => 'li',
-                'handler' => 'li',
-                'text' => array(
-                    $matches[2],
-                ),
+                'handler' => array(
+                    'function' => 'li',
+                    'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
+                    'destination' => 'elements'
+                )
             );
 
-            $Block['element']['text'] []= & $Block['li'];
+            $Block['element']['elements'] []= & $Block['li'];
 
             return $Block;
         }
@@ -577,11 +642,27 @@ protected function blockList($Line)
 
     protected function blockListContinue($Line, array $Block)
     {
-        if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+        if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
         {
+            return null;
+        }
+
+        $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
+
+        if ($Line['indent'] < $requiredIndent
+            and (
+                (
+                    $Block['data']['type'] === 'ol'
+                    and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+                ) or (
+                    $Block['data']['type'] === 'ul'
+                    and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+                )
+            )
+        ) {
             if (isset($Block['interrupted']))
             {
-                $Block['li']['text'] []= '';
+                $Block['li']['handler']['argument'] []= '';
 
                 $Block['loose'] = true;
 
@@ -592,42 +673,54 @@ protected function blockListContinue($Line, array $Block)
 
             $text = isset($matches[1]) ? $matches[1] : '';
 
+            $Block['indent'] = $Line['indent'];
+
             $Block['li'] = array(
                 'name' => 'li',
-                'handler' => 'li',
-                'text' => array(
-                    $text,
-                ),
+                'handler' => array(
+                    'function' => 'li',
+                    'argument' => array($text),
+                    'destination' => 'elements'
+                )
             );
 
-            $Block['element']['text'] []= & $Block['li'];
+            $Block['element']['elements'] []= & $Block['li'];
 
             return $Block;
         }
+        elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
+        {
+            return null;
+        }
 
         if ($Line['text'][0] === '[' and $this->blockReference($Line))
         {
             return $Block;
         }
 
-        if ( ! isset($Block['interrupted']))
+        if ($Line['indent'] >= $requiredIndent)
         {
-            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+            if (isset($Block['interrupted']))
+            {
+                $Block['li']['handler']['argument'] []= '';
 
-            $Block['li']['text'] []= $text;
+                $Block['loose'] = true;
+
+                unset($Block['interrupted']);
+            }
+
+            $text = substr($Line['body'], $requiredIndent);
+
+            $Block['li']['handler']['argument'] []= $text;
 
             return $Block;
         }
 
-        if ($Line['indent'] > 0)
+        if ( ! isset($Block['interrupted']))
         {
-            $Block['li']['text'] []= '';
+            $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
 
-            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
-
-            $Block['li']['text'] []= $text;
-
-            unset($Block['interrupted']);
+            $Block['li']['handler']['argument'] []= $text;
 
             return $Block;
         }
@@ -637,11 +730,11 @@ protected function blockListComplete(array $Block)
     {
         if (isset($Block['loose']))
         {
-            foreach ($Block['element']['text'] as &$li)
+            foreach ($Block['element']['elements'] as &$li)
             {
-                if (end($li['text']) !== '')
+                if (end($li['handler']['argument']) !== '')
                 {
-                    $li['text'] []= '';
+                    $li['handler']['argument'] []= '';
                 }
             }
         }
@@ -654,13 +747,16 @@ protected function blockListComplete(array $Block)
 
     protected function blockQuote($Line)
     {
-        if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+        if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
         {
             $Block = array(
                 'element' => array(
                     'name' => 'blockquote',
-                    'handler' => 'lines',
-                    'text' => (array) $matches[1],
+                    'handler' => array(
+                        'function' => 'linesElements',
+                        'argument' => (array) $matches[1],
+                        'destination' => 'elements',
+                    )
                 ),
             );
 
@@ -670,23 +766,21 @@ protected function blockQuote($Line)
 
     protected function blockQuoteContinue($Line, array $Block)
     {
-        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+        if (isset($Block['interrupted']))
         {
-            if (isset($Block['interrupted']))
-            {
-                $Block['element']['text'] []= '';
-
-                unset($Block['interrupted']);
-            }
+            return;
+        }
 
-            $Block['element']['text'] []= $matches[1];
+        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+        {
+            $Block['element']['handler']['argument'] []= $matches[1];
 
             return $Block;
         }
 
         if ( ! isset($Block['interrupted']))
         {
-            $Block['element']['text'] []= $Line['text'];
+            $Block['element']['handler']['argument'] []= $Line['text'];
 
             return $Block;
         }
@@ -697,11 +791,13 @@ protected function blockQuoteContinue($Line, array $Block)
 
     protected function blockRule($Line)
     {
-        if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+        $marker = $Line['text'][0];
+
+        if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
         {
             $Block = array(
                 'element' => array(
-                    'name' => 'hr'
+                    'name' => 'hr',
                 ),
             );
 
@@ -714,12 +810,12 @@ protected function blockRule($Line)
 
     protected function blockSetextHeader($Line, array $Block = null)
     {
-        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
         {
             return;
         }
 
-        if (chop($Line['text'], $Line['text'][0]) === '')
+        if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
         {
             $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
 
@@ -737,7 +833,7 @@ protected function blockMarkup($Line)
             return;
         }
 
-        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+        if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
         {
             $element = strtolower($matches[1]);
 
@@ -748,72 +844,24 @@ protected function blockMarkup($Line)
 
             $Block = array(
                 'name' => $matches[1],
-                'depth' => 0,
-                'markup' => $Line['text'],
+                'element' => array(
+                    'rawHtml' => $Line['text'],
+                    'autobreak' => true,
+                ),
             );
 
-            $length = strlen($matches[0]);
-
-            $remainder = substr($Line['text'], $length);
-
-            if (trim($remainder) === '')
-            {
-                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
-                {
-                    $Block['closed'] = true;
-
-                    $Block['void'] = true;
-                }
-            }
-            else
-            {
-                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
-                {
-                    return;
-                }
-
-                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
-                {
-                    $Block['closed'] = true;
-                }
-            }
-
             return $Block;
         }
     }
 
     protected function blockMarkupContinue($Line, array $Block)
     {
-        if (isset($Block['closed']))
+        if (isset($Block['closed']) or isset($Block['interrupted']))
         {
             return;
         }
 
-        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
-        {
-            $Block['depth'] ++;
-        }
-
-        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
-        {
-            if ($Block['depth'] > 0)
-            {
-                $Block['depth'] --;
-            }
-            else
-            {
-                $Block['closed'] = true;
-            }
-        }
-
-        if (isset($Block['interrupted']))
-        {
-            $Block['markup'] .= "\n";
-
-            unset($Block['interrupted']);
-        }
-
-        $Block['markup'] .= "\n".$Line['body'];
+        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
 
         return $Block;
     }
@@ -823,24 +871,20 @@ protected function blockMarkupContinue($Line, array $Block)
 
     protected function blockReference($Line)
     {
-        if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
-        {
+        if (strpos($Line['text'], ']') !== false
+            and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
+        ) {
             $id = strtolower($matches[1]);
 
             $Data = array(
                 'url' => $matches[2],
-                'title' => null,
+                'title' => isset($matches[3]) ? $matches[3] : null,
             );
 
-            if (isset($matches[3]))
-            {
-                $Data['title'] = $matches[3];
-            }
-
             $this->DefinitionData['Reference'][$id] = $Data;
 
             $Block = array(
-                'hidden' => true,
+                'element' => array(),
             );
 
             return $Block;
@@ -852,109 +896,125 @@ protected function blockReference($Line)
 
     protected function blockTable($Line, array $Block = null)
     {
-        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
         {
             return;
         }
 
-        if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+        if (
+            strpos($Block['element']['handler']['argument'], '|') === false
+            and strpos($Line['text'], '|') === false
+            and strpos($Line['text'], ':') === false
+            or strpos($Block['element']['handler']['argument'], "\n") !== false
+        ) {
+            return;
+        }
+
+        if (chop($Line['text'], ' -:|') !== '')
         {
-            $alignments = array();
+            return;
+        }
 
-            $divider = $Line['text'];
+        $alignments = array();
 
-            $divider = trim($divider);
-            $divider = trim($divider, '|');
+        $divider = $Line['text'];
 
-            $dividerCells = explode('|', $divider);
+        $divider = trim($divider);
+        $divider = trim($divider, '|');
 
-            foreach ($dividerCells as $dividerCell)
-            {
-                $dividerCell = trim($dividerCell);
+        $dividerCells = explode('|', $divider);
 
-                if ($dividerCell === '')
-                {
-                    continue;
-                }
+        foreach ($dividerCells as $dividerCell)
+        {
+            $dividerCell = trim($dividerCell);
 
-                $alignment = null;
+            if ($dividerCell === '')
+            {
+                return;
+            }
 
-                if ($dividerCell[0] === ':')
-                {
-                    $alignment = 'left';
-                }
+            $alignment = null;
 
-                if (substr($dividerCell, - 1) === ':')
-                {
-                    $alignment = $alignment === 'left' ? 'center' : 'right';
-                }
+            if ($dividerCell[0] === ':')
+            {
+                $alignment = 'left';
+            }
 
-                $alignments []= $alignment;
+            if (substr($dividerCell, - 1) === ':')
+            {
+                $alignment = $alignment === 'left' ? 'center' : 'right';
             }
 
-            # ~
+            $alignments []= $alignment;
+        }
 
-            $HeaderElements = array();
+        # ~
 
-            $header = $Block['element']['text'];
+        $HeaderElements = array();
 
-            $header = trim($header);
-            $header = trim($header, '|');
+        $header = $Block['element']['handler']['argument'];
 
-            $headerCells = explode('|', $header);
+        $header = trim($header);
+        $header = trim($header, '|');
 
-            foreach ($headerCells as $index => $headerCell)
-            {
-                $headerCell = trim($headerCell);
+        $headerCells = explode('|', $header);
 
-                $HeaderElement = array(
-                    'name' => 'th',
-                    'text' => $headerCell,
-                    'handler' => 'line',
-                );
+        if (count($headerCells) !== count($alignments))
+        {
+            return;
+        }
 
-                if (isset($alignments[$index]))
-                {
-                    $alignment = $alignments[$index];
+        foreach ($headerCells as $index => $headerCell)
+        {
+            $headerCell = trim($headerCell);
 
-                    $HeaderElement['attributes'] = array(
-                        'style' => 'text-align: '.$alignment.';',
-                    );
-                }
+            $HeaderElement = array(
+                'name' => 'th',
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $headerCell,
+                    'destination' => 'elements',
+                )
+            );
 
-                $HeaderElements []= $HeaderElement;
+            if (isset($alignments[$index]))
+            {
+                $alignment = $alignments[$index];
+
+                $HeaderElement['attributes'] = array(
+                    'style' => "text-align: $alignment;",
+                );
             }
 
-            # ~
+            $HeaderElements []= $HeaderElement;
+        }
 
-            $Block = array(
-                'alignments' => $alignments,
-                'identified' => true,
-                'element' => array(
-                    'name' => 'table',
-                    'handler' => 'elements',
-                ),
-            );
+        # ~
 
-            $Block['element']['text'] []= array(
-                'name' => 'thead',
-                'handler' => 'elements',
-            );
+        $Block = array(
+            'alignments' => $alignments,
+            'identified' => true,
+            'element' => array(
+                'name' => 'table',
+                'elements' => array(),
+            ),
+        );
 
-            $Block['element']['text'] []= array(
-                'name' => 'tbody',
-                'handler' => 'elements',
-                'text' => array(),
-            );
+        $Block['element']['elements'] []= array(
+            'name' => 'thead',
+        );
 
-            $Block['element']['text'][0]['text'] []= array(
-                'name' => 'tr',
-                'handler' => 'elements',
-                'text' => $HeaderElements,
-            );
+        $Block['element']['elements'] []= array(
+            'name' => 'tbody',
+            'elements' => array(),
+        );
 
-            return $Block;
-        }
+        $Block['element']['elements'][0]['elements'] []= array(
+            'name' => 'tr',
+            'elements' => $HeaderElements,
+        );
+
+        return $Block;
     }
 
     protected function blockTableContinue($Line, array $Block)
@@ -964,7 +1024,7 @@ protected function blockTableContinue($Line, array $Block)
             return;
         }
 
-        if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+        if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
         {
             $Elements = array();
 
@@ -973,22 +1033,27 @@ protected function blockTableContinue($Line, array $Block)
             $row = trim($row);
             $row = trim($row, '|');
 
-            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
+
+            $cells = array_slice($matches[0], 0, count($Block['alignments']));
 
-            foreach ($matches[0] as $index => $cell)
+            foreach ($cells as $index => $cell)
             {
                 $cell = trim($cell);
 
                 $Element = array(
                     'name' => 'td',
-                    'handler' => 'line',
-                    'text' => $cell,
+                    'handler' => array(
+                        'function' => 'lineElements',
+                        'argument' => $cell,
+                        'destination' => 'elements',
+                    )
                 );
 
                 if (isset($Block['alignments'][$index]))
                 {
                     $Element['attributes'] = array(
-                        'style' => 'text-align: '.$Block['alignments'][$index].';',
+                        'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
                     );
                 }
 
@@ -997,11 +1062,10 @@ protected function blockTableContinue($Line, array $Block)
 
             $Element = array(
                 'name' => 'tr',
-                'handler' => 'elements',
-                'text' => $Elements,
+                'elements' => $Elements,
             );
 
-            $Block['element']['text'][1]['text'] []= $Element;
+            $Block['element']['elements'][1]['elements'] []= $Element;
 
             return $Block;
         }
@@ -1013,13 +1077,27 @@ protected function blockTableContinue($Line, array $Block)
 
     protected function paragraph($Line)
     {
-        $Block = array(
+        return array(
+            'type' => 'Paragraph',
             'element' => array(
                 'name' => 'p',
-                'text' => $Line['text'],
-                'handler' => 'line',
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $Line['text'],
+                    'destination' => 'elements',
+                ),
             ),
         );
+    }
+
+    protected function paragraphContinue($Line, array $Block)
+    {
+        if (isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        $Block['element']['handler']['argument'] .= "\n".$Line['text'];
 
         return $Block;
     }
@@ -1029,13 +1107,11 @@ protected function paragraph($Line)
     #
 
     protected $InlineTypes = array(
-        '"' => array('SpecialCharacter'),
         '!' => array('Image'),
         '&' => array('SpecialCharacter'),
         '*' => array('Emphasis'),
         ':' => array('Url'),
-        '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
-        '>' => array('SpecialCharacter'),
+        '<' => array('UrlTag', 'EmailTag', 'Markup'),
         '[' => array('Link'),
         '_' => array('Emphasis'),
         '`' => array('Code'),
@@ -1045,15 +1121,28 @@ protected function paragraph($Line)
 
     # ~
 
-    protected $inlineMarkerList = '!"*_&[:<>`~\\';
+    protected $inlineMarkerList = '!*_&[:<`~\\';
 
     #
     # ~
     #
 
-    public function line($text, $nonNestables=array())
+    public function line($text, $nonNestables = array())
     {
-        $markup = '';
+        return $this->elements($this->lineElements($text, $nonNestables));
+    }
+
+    protected function lineElements($text, $nonNestables = array())
+    {
+        # standardize line breaks
+        $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+        $Elements = array();
+
+        $nonNestables = (empty($nonNestables)
+            ? array()
+            : array_combine($nonNestables, $nonNestables)
+        );
 
         # $excerpt is based on the first occurrence of a marker
 
@@ -1061,7 +1150,7 @@ public function line($text, $nonNestables=array())
         {
             $marker = $excerpt[0];
 
-            $markerPosition = strpos($text, $marker);
+            $markerPosition = strlen($text) - strlen($excerpt);
 
             $Excerpt = array('text' => $excerpt, 'context' => $text);
 
@@ -1069,12 +1158,12 @@ public function line($text, $nonNestables=array())
             {
                 # check to see if the current inline type is nestable in the current context
 
-                if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
+                if (isset($nonNestables[$inlineType]))
                 {
                     continue;
                 }
 
-                $Inline = $this->{'inline'.$inlineType}($Excerpt);
+                $Inline = $this->{"inline$inlineType"}($Excerpt);
 
                 if ( ! isset($Inline))
                 {
@@ -1097,19 +1186,21 @@ public function line($text, $nonNestables=array())
 
                 # cause the new element to 'inherit' our non nestables
 
-                foreach ($nonNestables as $non_nestable)
-                {
-                    $Inline['element']['nonNestables'][] = $non_nestable;
-                }
+
+                $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
+                    ? array_merge($Inline['element']['nonNestables'], $nonNestables)
+                    : $nonNestables
+                ;
 
                 # the text that comes before the inline
                 $unmarkedText = substr($text, 0, $Inline['position']);
 
                 # compile the unmarked text
-                $markup .= $this->unmarkedText($unmarkedText);
+                $InlineText = $this->inlineText($unmarkedText);
+                $Elements[] = $InlineText['element'];
 
                 # compile the inline
-                $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+                $Elements[] = $this->extractElement($Inline);
 
                 # remove the examined text
                 $text = substr($text, $Inline['position'] + $Inline['extent']);
@@ -1121,28 +1212,57 @@ public function line($text, $nonNestables=array())
 
             $unmarkedText = substr($text, 0, $markerPosition + 1);
 
-            $markup .= $this->unmarkedText($unmarkedText);
+            $InlineText = $this->inlineText($unmarkedText);
+            $Elements[] = $InlineText['element'];
 
             $text = substr($text, $markerPosition + 1);
         }
 
-        $markup .= $this->unmarkedText($text);
+        $InlineText = $this->inlineText($text);
+        $Elements[] = $InlineText['element'];
 
-        return $markup;
+        foreach ($Elements as &$Element)
+        {
+            if ( ! isset($Element['autobreak']))
+            {
+                $Element['autobreak'] = false;
+            }
+        }
+
+        return $Elements;
     }
 
     #
     # ~
     #
 
+    protected function inlineText($text)
+    {
+        $Inline = array(
+            'extent' => strlen($text),
+            'element' => array(),
+        );
+
+        $Inline['element']['elements'] = self::pregReplaceElements(
+            $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
+            array(
+                array('name' => 'br'),
+                array('text' => "\n"),
+            ),
+            $text
+        );
+
+        return $Inline;
+    }
+
     protected function inlineCode($Excerpt)
     {
         $marker = $Excerpt['text'][0];
 
-        if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+        if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
         {
             $text = $matches[2];
-            $text = preg_replace("/[ ]*\n/", ' ', $text);
+            $text = preg_replace('/[ ]*+\n/', ' ', $text);
 
             return array(
                 'extent' => strlen($matches[0]),
@@ -1156,13 +1276,19 @@ protected function inlineCode($Excerpt)
 
     protected function inlineEmailTag($Excerpt)
     {
-        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
-        {
+        $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
+
+        $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
+            . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
+
+        if (strpos($Excerpt['text'], '>') !== false
+            and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
+        ){
             $url = $matches[1];
 
             if ( ! isset($matches[2]))
             {
-                $url = 'mailto:' . $url;
+                $url = "mailto:$url";
             }
 
             return array(
@@ -1204,8 +1330,11 @@ protected function inlineEmphasis($Excerpt)
             'extent' => strlen($matches[0]),
             'element' => array(
                 'name' => $emphasis,
-                'handler' => 'line',
-                'text' => $matches[1],
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $matches[1],
+                    'destination' => 'elements',
+                )
             ),
         );
     }
@@ -1215,7 +1344,7 @@ protected function inlineEscapeSequence($Excerpt)
         if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
         {
             return array(
-                'markup' => $Excerpt['text'][1],
+                'element' => array('rawHtml' => $Excerpt['text'][1]),
                 'extent' => 2,
             );
         }
@@ -1243,8 +1372,9 @@ protected function inlineImage($Excerpt)
                 'name' => 'img',
                 'attributes' => array(
                     'src' => $Link['element']['attributes']['href'],
-                    'alt' => $Link['element']['text'],
+                    'alt' => $Link['element']['handler']['argument'],
                 ),
+                'autobreak' => true,
             ),
         );
 
@@ -1259,9 +1389,12 @@ protected function inlineLink($Excerpt)
     {
         $Element = array(
             'name' => 'a',
-            'handler' => 'line',
+            'handler' => array(
+                'function' => 'lineElements',
+                'argument' => null,
+                'destination' => 'elements',
+            ),
             'nonNestables' => array('Url', 'Link'),
-            'text' => null,
             'attributes' => array(
                 'href' => null,
                 'title' => null,
@@ -1274,7 +1407,7 @@ protected function inlineLink($Excerpt)
 
         if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
         {
-            $Element['text'] = $matches[1];
+            $Element['handler']['argument'] = $matches[1];
 
             $extent += strlen($matches[0]);
 
@@ -1285,7 +1418,7 @@ protected function inlineLink($Excerpt)
             return;
         }
 
-        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
         {
             $Element['attributes']['href'] = $matches[1];
 
@@ -1300,14 +1433,14 @@ protected function inlineLink($Excerpt)
         {
             if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
             {
-                $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+                $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
                 $definition = strtolower($definition);
 
                 $extent += strlen($matches[0]);
             }
             else
             {
-                $definition = strtolower($Element['text']);
+                $definition = strtolower($Element['handler']['argument']);
             }
 
             if ( ! isset($this->DefinitionData['Reference'][$definition]))
@@ -1334,26 +1467,26 @@ protected function inlineMarkup($Excerpt)
             return;
         }
 
-        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
+        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
         {
             return array(
-                'markup' => $matches[0],
+                'element' => array('rawHtml' => $matches[0]),
                 'extent' => strlen($matches[0]),
             );
         }
 
-        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
+        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
         {
             return array(
-                'markup' => $matches[0],
+                'element' => array('rawHtml' => $matches[0]),
                 'extent' => strlen($matches[0]),
             );
         }
 
-        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
         {
             return array(
-                'markup' => $matches[0],
+                'element' => array('rawHtml' => $matches[0]),
                 'extent' => strlen($matches[0]),
             );
         }
@@ -1361,23 +1494,16 @@ protected function inlineMarkup($Excerpt)
 
     protected function inlineSpecialCharacter($Excerpt)
     {
-        if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
-        {
+        if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
+            and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
+        ) {
             return array(
-                'markup' => '&amp;',
-                'extent' => 1,
+                'element' => array('rawHtml' => '&' . $matches[1] . ';'),
+                'extent' => strlen($matches[0]),
             );
         }
 
-        $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
-
-        if (isset($SpecialCharacter[$Excerpt['text'][0]]))
-        {
-            return array(
-                'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
-                'extent' => 1,
-            );
-        }
+        return;
     }
 
     protected function inlineStrikethrough($Excerpt)
@@ -1393,8 +1519,11 @@ protected function inlineStrikethrough($Excerpt)
                 'extent' => strlen($matches[0]),
                 'element' => array(
                     'name' => 'del',
-                    'text' => $matches[1],
-                    'handler' => 'line',
+                    'handler' => array(
+                        'function' => 'lineElements',
+                        'argument' => $matches[1],
+                        'destination' => 'elements',
+                    )
                 ),
             );
         }
@@ -1407,8 +1536,9 @@ protected function inlineUrl($Excerpt)
             return;
         }
 
-        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
-        {
+        if (strpos($Excerpt['context'], 'http') !== false
+            and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
+        ) {
             $url = $matches[0][0];
 
             $Inline = array(
@@ -1429,7 +1559,7 @@ protected function inlineUrl($Excerpt)
 
     protected function inlineUrlTag($Excerpt)
     {
-        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
         {
             $url = $matches[1];
 
@@ -1450,22 +1580,111 @@ protected function inlineUrlTag($Excerpt)
 
     protected function unmarkedText($text)
     {
-        if ($this->breaksEnabled)
+        $Inline = $this->inlineText($text);
+        return $this->element($Inline['element']);
+    }
+
+    #
+    # Handlers
+    #
+
+    protected function handle(array $Element)
+    {
+        if (isset($Element['handler']))
         {
-            $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
+            if (!isset($Element['nonNestables']))
+            {
+                $Element['nonNestables'] = array();
+            }
+
+            if (is_string($Element['handler']))
+            {
+                $function = $Element['handler'];
+                $argument = $Element['text'];
+                unset($Element['text']);
+                $destination = 'rawHtml';
+            }
+            else
+            {
+                $function = $Element['handler']['function'];
+                $argument = $Element['handler']['argument'];
+                $destination = $Element['handler']['destination'];
+            }
+
+            $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
+
+            if ($destination === 'handler')
+            {
+                $Element = $this->handle($Element);
+            }
+
+            unset($Element['handler']);
         }
-        else
+
+        return $Element;
+    }
+
+    protected function handleElementRecursive(array $Element)
+    {
+        return $this->elementApplyRecursive(array($this, 'handle'), $Element);
+    }
+
+    protected function handleElementsRecursive(array $Elements)
+    {
+        return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
+    }
+
+    protected function elementApplyRecursive($closure, array $Element)
+    {
+        $Element = call_user_func($closure, $Element);
+
+        if (isset($Element['elements']))
+        {
+            $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
+        }
+        elseif (isset($Element['element']))
         {
-            $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
-            $text = str_replace(" \n", "\n", $text);
+            $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
         }
 
-        return $text;
+        return $Element;
     }
 
-    #
-    # Handlers
-    #
+    protected function elementApplyRecursiveDepthFirst($closure, array $Element)
+    {
+        if (isset($Element['elements']))
+        {
+            $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
+        }
+        elseif (isset($Element['element']))
+        {
+            $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
+        }
+
+        $Element = call_user_func($closure, $Element);
+
+        return $Element;
+    }
+
+    protected function elementsApplyRecursive($closure, array $Elements)
+    {
+        foreach ($Elements as &$Element)
+        {
+            $Element = $this->elementApplyRecursive($closure, $Element);
+        }
+
+        return $Elements;
+    }
+
+    protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
+    {
+        foreach ($Elements as &$Element)
+        {
+            $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
+        }
+
+        return $Elements;
+    }
 
     protected function element(array $Element)
     {
@@ -1474,42 +1693,76 @@ protected function element(array $Element)
             $Element = $this->sanitiseElement($Element);
         }
 
-        $markup = '<'.$Element['name'];
+        # identity map if element has no handler
+        $Element = $this->handle($Element);
+
+        $hasName = isset($Element['name']);
 
-        if (isset($Element['attributes']))
+        $markup = '';
+
+        if ($hasName)
         {
-            foreach ($Element['attributes'] as $name => $value)
+            $markup .= '<' . $Element['name'];
+
+            if (isset($Element['attributes']))
             {
-                if ($value === null)
+                foreach ($Element['attributes'] as $name => $value)
                 {
-                    continue;
-                }
+                    if ($value === null)
+                    {
+                        continue;
+                    }
 
-                $markup .= ' '.$name.'="'.self::escape($value).'"';
+                    $markup .= " $name=\"".self::escape($value).'"';
+                }
             }
         }
 
+        $permitRawHtml = false;
+
         if (isset($Element['text']))
         {
-            $markup .= '>';
+            $text = $Element['text'];
+        }
+        // very strongly consider an alternative if you're writing an
+        // extension
+        elseif (isset($Element['rawHtml']))
+        {
+            $text = $Element['rawHtml'];
+
+            $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
+            $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
+        }
+
+        $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
+
+        if ($hasContent)
+        {
+            $markup .= $hasName ? '>' : '';
 
-            if (!isset($Element['nonNestables'])) 
+            if (isset($Element['elements']))
             {
-                $Element['nonNestables'] = array();
+                $markup .= $this->elements($Element['elements']);
             }
-
-            if (isset($Element['handler']))
+            elseif (isset($Element['element']))
             {
-                $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
+                $markup .= $this->element($Element['element']);
             }
             else
             {
-                $markup .= self::escape($Element['text'], true);
+                if (!$permitRawHtml)
+                {
+                    $markup .= self::escape($text, true);
+                }
+                else
+                {
+                    $markup .= $text;
+                }
             }
 
-            $markup .= '</'.$Element['name'].'>';
+            $markup .= $hasName ? '</' . $Element['name'] . '>' : '';
         }
-        else
+        elseif ($hasName)
         {
             $markup .= ' />';
         }
@@ -1521,12 +1774,26 @@ protected function elements(array $Elements)
     {
         $markup = '';
 
+        $autoBreak = true;
+
         foreach ($Elements as $Element)
         {
-            $markup .= "\n" . $this->element($Element);
+            if (empty($Element))
+            {
+                continue;
+            }
+
+            $autoBreakNext = (isset($Element['autobreak'])
+                ? $Element['autobreak'] : isset($Element['name'])
+            );
+            // (autobreak === false) covers both sides of an element
+            $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
+
+            $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
+            $autoBreak = $autoBreakNext;
         }
 
-        $markup .= "\n";
+        $markup .= $autoBreak ? "\n" : '';
 
         return $markup;
     }
@@ -1535,21 +1802,49 @@ protected function elements(array $Elements)
 
     protected function li($lines)
     {
-        $markup = $this->lines($lines);
+        $Elements = $this->linesElements($lines);
+
+        if ( ! in_array('', $lines)
+            and isset($Elements[0]) and isset($Elements[0]['name'])
+            and $Elements[0]['name'] === 'p'
+        ) {
+            unset($Elements[0]['name']);
+        }
 
-        $trimmedMarkup = trim($markup);
+        return $Elements;
+    }
 
-        if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
+    #
+    # AST Convenience
+    #
+
+    /**
+     * Replace occurrences $regexp with $Elements in $text. Return an array of
+     * elements representing the replacement.
+     */
+    protected static function pregReplaceElements($regexp, $Elements, $text)
+    {
+        $newElements = array();
+
+        while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
         {
-            $markup = $trimmedMarkup;
-            $markup = substr($markup, 3);
+            $offset = $matches[0][1];
+            $before = substr($text, 0, $offset);
+            $after = substr($text, $offset + strlen($matches[0][0]));
+
+            $newElements[] = array('text' => $before);
 
-            $position = strpos($markup, "</p>");
+            foreach ($Elements as $Element)
+            {
+                $newElements[] = $Element;
+            }
 
-            $markup = substr_replace($markup, '', $position, 4);
+            $text = $after;
         }
 
-        return $markup;
+        $newElements[] = array('text' => $text);
+
+        return $newElements;
     }
 
     #
@@ -1571,6 +1866,12 @@ protected function sanitiseElement(array $Element)
             'img' => 'src',
         );
 
+        if ( ! isset($Element['name']))
+        {
+            unset($Element['attributes']);
+            return $Element;
+        }
+
         if (isset($safeUrlNameToAtt[$Element['name']]))
         {
             $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
@@ -1660,12 +1961,12 @@ static function instance($name = 'default')
     # Read-Only
 
     protected $specialCharacters = array(
-        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
     );
 
     protected $StrongRegex = array(
-        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
-        '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
+        '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
     );
 
     protected $EmRegex = array(
@@ -1673,7 +1974,7 @@ static function instance($name = 'default')
         '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
     );
 
-    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
 
     protected $voidElements = array(
         'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
@@ -1690,4 +1991,4 @@ static function instance($name = 'default')
                    'var', 'span',
                    'wbr', 'time',
     );
-}
+}
\ No newline at end of file
diff --git a/resources/ParsedownExtra.php b/resources/ParsedownExtra.php
new file mode 100644
index 0000000..2b32c6d
--- /dev/null
+++ b/resources/ParsedownExtra.php
@@ -0,0 +1,686 @@
+<?php
+
+#
+#
+# Parsedown Extra
+# https://github.com/erusev/parsedown-extra
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class ParsedownExtra extends Parsedown
+{
+    # ~
+
+    const version = '0.8.0-beta-1';
+
+    # ~
+
+    function __construct()
+    {
+        if (version_compare(parent::version, '1.7.1') < 0)
+        {
+            throw new Exception('ParsedownExtra requires a later version of Parsedown');
+        }
+
+        $this->BlockTypes[':'] []= 'DefinitionList';
+        $this->BlockTypes['*'] []= 'Abbreviation';
+
+        # identify footnote definitions before reference definitions
+        array_unshift($this->BlockTypes['['], 'Footnote');
+
+        # identify footnote markers before before links
+        array_unshift($this->InlineTypes['['], 'FootnoteMarker');
+    }
+
+    #
+    # ~
+
+    function text($text)
+    {
+        $Elements = $this->textElements($text);
+
+        # convert to markup
+        $markup = $this->elements($Elements);
+
+        # trim line breaks
+        $markup = trim($markup, "\n");
+
+        # merge consecutive dl elements
+
+        $markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
+
+        # add footnotes
+
+        if (isset($this->DefinitionData['Footnote']))
+        {
+            $Element = $this->buildFootnoteElement();
+
+            $markup .= "\n" . $this->element($Element);
+        }
+
+        return $markup;
+    }
+
+    #
+    # Blocks
+    #
+
+    #
+    # Abbreviation
+
+    protected function blockAbbreviation($Line)
+    {
+        if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
+        {
+            $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
+
+            $Block = array(
+                'hidden' => true,
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # Footnote
+
+    protected function blockFootnote($Line)
+    {
+        if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
+        {
+            $Block = array(
+                'label' => $matches[1],
+                'text' => $matches[2],
+                'hidden' => true,
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockFootnoteContinue($Line, $Block)
+    {
+        if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
+        {
+            return;
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            if ($Line['indent'] >= 4)
+            {
+                $Block['text'] .= "\n\n" . $Line['text'];
+
+                return $Block;
+            }
+        }
+        else
+        {
+            $Block['text'] .= "\n" . $Line['text'];
+
+            return $Block;
+        }
+    }
+
+    protected function blockFootnoteComplete($Block)
+    {
+        $this->DefinitionData['Footnote'][$Block['label']] = array(
+            'text' => $Block['text'],
+            'count' => null,
+            'number' => null,
+        );
+
+        return $Block;
+    }
+
+    #
+    # Definition List
+
+    protected function blockDefinitionList($Line, $Block)
+    {
+        if ( ! isset($Block) or $Block['type'] !== 'Paragraph')
+        {
+            return;
+        }
+
+        $Element = array(
+            'name' => 'dl',
+            'elements' => array(),
+        );
+
+        $terms = explode("\n", $Block['element']['handler']['argument']);
+
+        foreach ($terms as $term)
+        {
+            $Element['elements'] []= array(
+                'name' => 'dt',
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $term,
+                    'destination' => 'elements'
+                ),
+            );
+        }
+
+        $Block['element'] = $Element;
+
+        $Block = $this->addDdElement($Line, $Block);
+
+        return $Block;
+    }
+
+    protected function blockDefinitionListContinue($Line, array $Block)
+    {
+        if ($Line['text'][0] === ':')
+        {
+            $Block = $this->addDdElement($Line, $Block);
+
+            return $Block;
+        }
+        else
+        {
+            if (isset($Block['interrupted']) and $Line['indent'] === 0)
+            {
+                return;
+            }
+
+            if (isset($Block['interrupted']))
+            {
+                $Block['dd']['handler']['function'] = 'textElements';
+                $Block['dd']['handler']['argument'] .= "\n\n";
+
+                $Block['dd']['handler']['destination'] = 'elements';
+
+                unset($Block['interrupted']);
+            }
+
+            $text = substr($Line['body'], min($Line['indent'], 4));
+
+            $Block['dd']['handler']['argument'] .= "\n" . $text;
+
+            return $Block;
+        }
+    }
+
+    #
+    # Header
+
+    protected function blockHeader($Line)
+    {
+        $Block = parent::blockHeader($Line);
+
+        if (preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
+        {
+            $attributeString = $matches[1][0];
+
+            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
+
+            $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
+        }
+
+        return $Block;
+    }
+
+    #
+    # Markup
+
+    protected function blockMarkup($Line)
+    {
+        if ($this->markupEscaped or $this->safeMode)
+        {
+            return;
+        }
+
+        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+        {
+            $element = strtolower($matches[1]);
+
+            if (in_array($element, $this->textLevelElements))
+            {
+                return;
+            }
+
+            $Block = array(
+                'name' => $matches[1],
+                'depth' => 0,
+                'element' => array(
+                    'rawHtml' => $Line['text'],
+                    'autobreak' => true,
+                ),
+            );
+
+            $length = strlen($matches[0]);
+            $remainder = substr($Line['text'], $length);
+
+            if (trim($remainder) === '')
+            {
+                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+                {
+                    $Block['closed'] = true;
+                    $Block['void'] = true;
+                }
+            }
+            else
+            {
+                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+                {
+                    return;
+                }
+                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+                {
+                    $Block['closed'] = true;
+                }
+            }
+
+            return $Block;
+        }
+    }
+
+    protected function blockMarkupContinue($Line, array $Block)
+    {
+        if (isset($Block['closed']))
+        {
+            return;
+        }
+
+        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+        {
+            $Block['depth'] ++;
+        }
+
+        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+        {
+            if ($Block['depth'] > 0)
+            {
+                $Block['depth'] --;
+            }
+            else
+            {
+                $Block['closed'] = true;
+            }
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['element']['rawHtml'] .= "\n";
+            unset($Block['interrupted']);
+        }
+
+        $Block['element']['rawHtml'] .= "\n".$Line['body'];
+
+        return $Block;
+    }
+
+    protected function blockMarkupComplete($Block)
+    {
+        if ( ! isset($Block['void']))
+        {
+            $Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
+        }
+
+        return $Block;
+    }
+
+    #
+    # Setext
+
+    protected function blockSetextHeader($Line, array $Block = null)
+    {
+        $Block = parent::blockSetextHeader($Line, $Block);
+
+        if (preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
+        {
+            $attributeString = $matches[1][0];
+
+            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
+
+            $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
+        }
+
+        return $Block;
+    }
+
+    #
+    # Inline Elements
+    #
+
+    #
+    # Footnote Marker
+
+    protected function inlineFootnoteMarker($Excerpt)
+    {
+        if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
+        {
+            $name = $matches[1];
+
+            if ( ! isset($this->DefinitionData['Footnote'][$name]))
+            {
+                return;
+            }
+
+            $this->DefinitionData['Footnote'][$name]['count'] ++;
+
+            if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
+            {
+                $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
+            }
+
+            $Element = array(
+                'name' => 'sup',
+                'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
+                'element' => array(
+                    'name' => 'a',
+                    'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
+                    'text' => $this->DefinitionData['Footnote'][$name]['number'],
+                ),
+            );
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => $Element,
+            );
+        }
+    }
+
+    private $footnoteCount = 0;
+
+    #
+    # Link
+
+    protected function inlineLink($Excerpt)
+    {
+        $Link = parent::inlineLink($Excerpt);
+
+        $remainder = substr($Excerpt['text'], $Link['extent']);
+
+        if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
+        {
+            $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
+
+            $Link['extent'] += strlen($matches[0]);
+        }
+
+        return $Link;
+    }
+
+    #
+    # ~
+    #
+
+    private $currentAbreviation;
+    private $currentMeaning;
+
+    protected function insertAbreviation(array $Element)
+    {
+        if (isset($Element['text']))
+        {
+            $Element['elements'] = self::pregReplaceElements(
+                '/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
+                array(
+                    array(
+                        'name' => 'abbr',
+                        'attributes' => array(
+                            'title' => $this->currentMeaning,
+                        ),
+                        'text' => $this->currentAbreviation,
+                    )
+                ),
+                $Element['text']
+            );
+
+            unset($Element['text']);
+        }
+
+        return $Element;
+    }
+
+    protected function inlineText($text)
+    {
+        $Inline = parent::inlineText($text);
+
+        if (isset($this->DefinitionData['Abbreviation']))
+        {
+            foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
+            {
+                $this->currentAbreviation = $abbreviation;
+                $this->currentMeaning = $meaning;
+
+                $Inline['element'] = $this->elementApplyRecursiveDepthFirst(
+                    array($this, 'insertAbreviation'),
+                    $Inline['element']
+                );
+            }
+        }
+
+        return $Inline;
+    }
+
+    #
+    # Util Methods
+    #
+
+    protected function addDdElement(array $Line, array $Block)
+    {
+        $text = substr($Line['text'], 1);
+        $text = trim($text);
+
+        unset($Block['dd']);
+
+        $Block['dd'] = array(
+            'name' => 'dd',
+            'handler' => array(
+                'function' => 'lineElements',
+                'argument' => $text,
+                'destination' => 'elements'
+            ),
+        );
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['dd']['handler']['function'] = 'textElements';
+
+            unset($Block['interrupted']);
+        }
+
+        $Block['element']['elements'] []= & $Block['dd'];
+
+        return $Block;
+    }
+
+    protected function buildFootnoteElement()
+    {
+        $Element = array(
+            'name' => 'div',
+            'attributes' => array('class' => 'footnotes'),
+            'elements' => array(
+                array('name' => 'hr'),
+                array(
+                    'name' => 'ol',
+                    'elements' => array(),
+                ),
+            ),
+        );
+
+        uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
+
+        foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
+        {
+            if ( ! isset($DefinitionData['number']))
+            {
+                continue;
+            }
+
+            $text = $DefinitionData['text'];
+
+            $textElements = parent::textElements($text);
+
+            $numbers = range(1, $DefinitionData['count']);
+
+            $backLinkElements = array();
+
+            foreach ($numbers as $number)
+            {
+                $backLinkElements[] = array('text' => ' ');
+                $backLinkElements[] = array(
+                    'name' => 'a',
+                    'attributes' => array(
+                        'href' => "#fnref$number:$definitionId",
+                        'rev' => 'footnote',
+                        'class' => 'footnote-backref',
+                    ),
+                    'rawHtml' => '&#8617;',
+                    'allowRawHtmlInSafeMode' => true,
+                    'autobreak' => false,
+                );
+            }
+
+            unset($backLinkElements[0]);
+
+            $n = count($textElements) -1;
+
+            if ($textElements[$n]['name'] === 'p')
+            {
+                $backLinkElements = array_merge(
+                    array(
+                        array(
+                            'rawHtml' => '&#160;',
+                            'allowRawHtmlInSafeMode' => true,
+                        ),
+                    ),
+                    $backLinkElements
+                );
+
+                unset($textElements[$n]['name']);
+
+                $textElements[$n] = array(
+                    'name' => 'p',
+                    'elements' => array_merge(
+                        array($textElements[$n]),
+                        $backLinkElements
+                    ),
+                );
+            }
+            else
+            {
+                $textElements[] = array(
+                    'name' => 'p',
+                    'elements' => $backLinkElements
+                );
+            }
+
+            $Element['elements'][1]['elements'] []= array(
+                'name' => 'li',
+                'attributes' => array('id' => 'fn:'.$definitionId),
+                'elements' => array_merge(
+                    $textElements
+                ),
+            );
+        }
+
+        return $Element;
+    }
+
+    # ~
+
+    protected function parseAttributeData($attributeString)
+    {
+        $Data = array();
+
+        $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
+
+        foreach ($attributes as $attribute)
+        {
+            if ($attribute[0] === '#')
+            {
+                $Data['id'] = substr($attribute, 1);
+            }
+            else # "."
+            {
+                $classes []= substr($attribute, 1);
+            }
+        }
+
+        if (isset($classes))
+        {
+            $Data['class'] = implode(' ', $classes);
+        }
+
+        return $Data;
+    }
+
+    # ~
+
+    protected function processTag($elementMarkup) # recursive
+    {
+        # http://stackoverflow.com/q/1148928/200145
+        libxml_use_internal_errors(true);
+
+        $DOMDocument = new DOMDocument;
+
+        # http://stackoverflow.com/q/11309194/200145
+        $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
+
+        # http://stackoverflow.com/q/4879946/200145
+        $DOMDocument->loadHTML($elementMarkup);
+        $DOMDocument->removeChild($DOMDocument->doctype);
+        $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
+
+        $elementText = '';
+
+        if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
+        {
+            foreach ($DOMDocument->documentElement->childNodes as $Node)
+            {
+                $elementText .= $DOMDocument->saveHTML($Node);
+            }
+
+            $DOMDocument->documentElement->removeAttribute('markdown');
+
+            $elementText = "\n".$this->text($elementText)."\n";
+        }
+        else
+        {
+            foreach ($DOMDocument->documentElement->childNodes as $Node)
+            {
+                $nodeMarkup = $DOMDocument->saveHTML($Node);
+
+                if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
+                {
+                    $elementText .= $this->processTag($nodeMarkup);
+                }
+                else
+                {
+                    $elementText .= $nodeMarkup;
+                }
+            }
+        }
+
+        # because we don't want for markup to get encoded
+        $DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
+
+        $markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
+        $markup = str_replace('placeholder\x1A', $elementText, $markup);
+
+        return $markup;
+    }
+
+    # ~
+
+    protected function sortFootnotes($A, $B) # callback
+    {
+        return $A['number'] - $B['number'];
+    }
+
+    #
+    # Fields
+    #
+
+    protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
+}
\ No newline at end of file
diff --git a/resources/ParsedownExtraPlugin.php b/resources/ParsedownExtraPlugin.php
new file mode 100644
index 0000000..961d4e7
--- /dev/null
+++ b/resources/ParsedownExtraPlugin.php
@@ -0,0 +1,472 @@
+<?php
+
+#
+#
+# Parsedown Extra Plugin
+# https://github.com/tovic/parsedown-extra-plugin
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# (c) Taufik Nurrohman
+# https://mecha-cms.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class ParsedownExtraPlugin extends ParsedownExtra {
+
+    const version = '1.2.0-beta-3';
+
+
+    # config
+
+    public $abbreviationData = array();
+
+    public $blockCodeAttributes = array();
+
+    public $blockCodeAttributesOnParent = false;
+
+    public $blockCodeClassFormat = 'language-%s';
+
+    public $blockCodeHtml = null;
+
+    public $blockQuoteAttributes = array();
+
+    public $blockQuoteText = null;
+
+    public $codeAttributes = array();
+
+    public $codeHtml = null;
+
+    public $footnoteAttributes = array();
+
+    public $footnoteBackLinkAttributes = array();
+
+    public $footnoteBackLinkHtml = null;
+
+    public $footnoteBackReferenceAttributes = array();
+
+    public $footnoteLinkAttributes = array();
+
+    public $footnoteLinkHtml = null;
+
+    public $footnoteReferenceAttributes = array();
+
+    public $headerAttributes = array();
+
+    public $headerText = null;
+
+    public $imageAttributes = array();
+
+    public $linkAttributes = array();
+
+    public $referenceData = array();
+
+    public $tableAttributes = array();
+
+    public $tableColumnAttributes = array();
+
+    public $voidElementSuffix = ' />';
+
+    # config
+
+
+    protected $regexAttribute = '(?:[#.][-\w:\\\]+[ ]*|[-\w:\\\]+(?:=(?:["\'][^\n]*?["\']|[^\s]+)?)?[ ]*)';
+
+    # Method aliases for every configuration property
+    public function __call($key, array $arguments = array()) {
+        $property = lcfirst(substr($key, 3));
+        if (strpos($key, 'set') === 0 && property_exists($this, $property)) {
+            $this->{$property} = $arguments[0];
+            return $this;
+        }
+        throw new Exception('Method ' . $key . ' does not exists.');
+    }
+
+    public function __construct() {
+        if (version_compare(parent::version, '0.8.0-beta-1') < 0) {
+            throw new Exception('ParsedownExtraPlugin requires a later version of Parsedown');
+        }
+        parent::__construct();
+    }
+
+    protected function blockAbbreviation($Line) {
+        # Allow empty abbreviations
+        if (preg_match('/^\*\[(.+?)\]:[ ]*$/', $Line['text'], $matches)) {
+            $this->DefinitionData['Abbreviation'][$matches[1]] = null;
+            return array('hidden' => true);
+        }
+        return parent::blockAbbreviation($Line);
+    }
+
+    protected function blockCodeComplete($Block) {
+        $this->doSetAttributes($Block['element']['element'], $this->blockCodeAttributes);
+        $this->doSetContent($Block['element']['element'], $this->blockCodeHtml, true);
+        # Put code attributes on parent tag
+        if ($this->blockCodeAttributesOnParent) {
+            $Block['element']['attributes'] = $Block['element']['element']['attributes'];
+            unset($Block['element']['element']['attributes']);
+        }
+        $Block['element']['element']['rawHtml'] = $Block['element']['element']['text'];
+        $Block['element']['element']['allowRawHtmlInSafeMode'] = true;
+        unset($Block['element']['element']['text']);
+        return $Block;
+    }
+
+    protected function blockFencedCode($Line) {
+        # Re-enable the multiple class name feature
+        $Line['text'] = strtr(trim($Line['text']), array(
+            ' ' => "\x1A",
+            '.' => "\x1A."
+        ));
+        # Enable custom attribute syntax on code block
+        $Attributes = array();
+        if (strpos($Line['text'], '{') !== false && substr($Line['text'], -1) === '}') {
+            $Parts = explode('{', $Line['text'], 2);
+            $Attributes = $this->parseAttributeData(strtr(substr($Parts[1], 0, -1), "\x1A", ' '));
+            $Line['text'] = trim($Parts[0]);
+        }
+        if (!$Block = parent::blockFencedCode($Line)) {
+            return;
+        }
+        if ($Attributes) {
+            $Block['element']['element']['attributes'] = $Attributes;
+        } else if (isset($Block['element']['element']['attributes']['class'])) {
+            $Classes = explode("\x1A", strtr($Block['element']['element']['attributes']['class'], ' ', "\x1A"));
+            // `~~~ php` → `<pre><code class="language-php">`
+            // `~~~ php html` → `<pre><code class="language-php language-html">`
+            // `~~~ .php` → `<pre><code class="php">`
+            // `~~~ .php.html` → `<pre><code class="php html">`
+            // `~~~ .php html` → `<pre><code class="php language-html">`
+            // `~~~ {.php #foo}` → `<pre><code id="foo" class="php">`
+            $Results = [];
+            foreach ($Classes as $Class) {
+                if ($Class === "" || $Class === str_replace('%s', "", $this->blockCodeClassFormat)) {
+                    continue;
+                }
+                if ($Class[0] === '.') {
+                    $Results[] = substr($Class, 1);
+                } else {
+                    $Results[] = sprintf($this->blockCodeClassFormat, $Class);
+                }
+            }
+            $Block['element']['element']['attributes']['class'] = implode(' ', array_unique($Results));
+        }
+        return $Block;
+    }
+
+    protected function blockFencedCodeComplete($Block) {
+        return $this->blockCodeComplete($Block);
+    }
+
+    protected function blockHeader($Line) {
+        if (!$Block = parent::blockHeader($Line)) {
+            return;
+        }
+        $Level = strspn($Line['text'], '#');
+        $this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
+        $this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
+        return $Block;
+    }
+
+    protected function blockQuoteComplete($Block) {
+        $this->doSetAttributes($Block['element'], $this->blockQuoteAttributes);
+        $this->doSetContent($Block['element'], $this->blockQuoteText, false, 'arguments');
+        return $Block;
+    }
+
+    protected function blockSetextHeader($Line, array $Block = null) {
+        if (!$Block = parent::blockSetextHeader($Line, $Block)) {
+            return;
+        }
+        $Level = $Line['text'][0] === '=' ? 1 : 2;
+        $this->doSetAttributes($Block['element'], $this->headerAttributes, array($Level));
+        $this->doSetContent($Block['element'], $this->headerText, false, 'argument', array($Level));
+        return $Block;
+    }
+
+    protected function blockTableContinue($Line, array $Block) {
+        if (!$Block = parent::blockTableContinue($Line, $Block)) {
+            return;
+        }
+        $Aligns = $Block['alignments'];
+        // `<thead>` or `<tbody>`
+        foreach ($Block['element']['elements'] as $Index0 => &$Element0) {
+            // `<tr>`
+            foreach ($Element0['elements'] as $Index1 => &$Element1) {
+                // `<th>` or `<td>`
+                foreach ($Element1['elements'] as $Index2 => &$Element2) {
+                    $this->doSetAttributes($Element2, $this->tableColumnAttributes, array($Aligns[$Index2], $Index2, $Index1));
+                }
+            }
+        }
+        return $Block;
+    }
+
+    protected function blockTableComplete($Block) {
+        $this->doSetAttributes($Block['element'], $this->tableAttributes);
+        return $Block;
+    }
+
+    protected function buildFootnoteElement() {
+        $DefinitionData = $this->DefinitionData['Footnote'];
+        if (!$Footnotes = parent::buildFootnoteElement()) {
+            return;
+        }
+        $DefinitionKey = array_keys($DefinitionData);
+        $DefinitionData = array_values($DefinitionData);
+        $this->doSetAttributes($Footnotes, $this->footnoteAttributes);
+        foreach ($Footnotes['elements'][1]['elements'] as $Index0 => &$Element0) {
+            $Name = $DefinitionKey[$Index0];
+            $Count = $DefinitionData[$Index0]['count'];
+            $Args = array(is_numeric($Name) ? (float) $Name : $Name, $Count);
+            $this->doSetAttributes($Element0, $this->footnoteBackReferenceAttributes, $Args);
+            foreach ($Element0['elements'] as $Index1 => &$Element1) {
+                $Count = 0;
+                foreach ($Element1['elements'] as $Index2 => &$Element2) {
+                    if (!isset($Element2['name']) || $Element2['name'] !== 'a') {
+                        continue;
+                    }
+                    $Args[1] = ++$Count;
+                    $this->doSetAttributes($Element2, $this->footnoteBackLinkAttributes, $Args);
+                    $this->doSetContent($Element2, $this->footnoteBackLinkHtml, false, 'rawHtml');
+                }
+            }
+        }
+        return $Footnotes;
+    }
+
+    protected function doGetAttributes($Element) {
+        if (isset($Element['attributes'])) {
+            return (array) $Element['attributes'];
+        }
+        return array();
+    }
+
+    protected function doGetContent($Element) {
+        if (isset($Element['text'])) {
+            return $Element['text'];
+        }
+        if (isset($Element['rawHtml'])) {
+            return $Element['rawHtml'];
+        }
+        if (isset($Element['handler']['argument'])) {
+            return implode("\n", (array) $Element['handler']['argument']);
+        }
+        return null;
+    }
+
+    private function doSetLink($Excerpt, $Function) {
+        if (!$Inline = call_user_func('parent::' . $Function, $Excerpt)) {
+            return;
+        }
+        $this->doSetAttributes($Inline['element'], $this->linkAttributes, array($this->isLocal($Inline['element'], 'href')));
+        $this->doSetData($this->DefinitionData['Reference'], $this->referenceData);
+        return $Inline;
+    }
+
+    protected function doSetAttributes(&$Element, $From, $Args = array()) {
+        $Attributes = $this->doGetAttributes($Element);
+        $Content = $this->doGetContent($Element);
+        if (is_callable($From)) {
+            $Args = array_merge(array($Content, $Attributes, &$Element), $Args);
+            $Element['attributes'] = array_replace($Attributes, (array) call_user_func_array($From, $Args));
+        } else {
+            $Element['attributes'] = array_replace($Attributes, (array) $From);
+        }
+    }
+
+    protected function doSetContent(&$Element, $From, $Esc = false, $Mode = 'text', $Args = array()) {
+        $Attributes = $this->doGetAttributes($Element);
+        $Content = $this->doGetContent($Element);
+        if ($Esc) {
+            $Content = parent::escape($Content, true);
+        }
+        if (is_callable($From)) {
+            $Args = array_merge(array($Content, $Attributes, &$Element), $Args);
+            $Content = call_user_func_array($From, $Args);
+        } else if (!empty($From)) {
+            $Content = sprintf($From, $Content);
+        }
+        if ($Mode === 'arguments') {
+            $Element['handler']['argument'] = explode("\n", $Content);
+        } else if ($Mode === 'argument') {
+            $Element['handler']['argument'] = $Content;
+        } else {
+            $Element[$Mode] = $Content;
+        }
+    }
+
+    protected function doSetData(&$To, $From) {
+        $To = array_replace((array) $To, (array) $From);
+    }
+
+    protected function element(array $Element) {
+        if (!$Any = parent::element($Element)) {
+            return;
+        }
+        if (substr($Any, -3) === ' />') {
+            if (is_callable($this->voidElementSuffix)) {
+                $Attributes = $this->doGetAttributes($Element);
+                $Content = $this->doGetContent($Element);
+                $Suffix = call_user_func_array($this->voidElementSuffix, [$Content, $Attributes, &$Element]);
+            } else {
+                $Suffix = $this->voidElementSuffix;
+            }
+            $Any = substr_replace($Any, $Suffix, -3);
+        }
+        return $Any;
+    }
+
+    protected function inlineCode($Excerpt) {
+        if (!$Inline = parent::inlineCode($Excerpt)) {
+            return;
+        }
+        $this->doSetAttributes($Inline['element'], $this->codeAttributes);
+        $this->doSetContent($Inline['element'], $this->codeHtml, true);
+        $Inline['element']['rawHtml'] = $Inline['element']['text'];
+        $Inline['element']['allowRawHtmlInSafeMode'] = true;
+        unset($Inline['element']['text']);
+        return $Inline;
+    }
+
+    protected function inlineFootnoteMarker($Excerpt) {
+        if (!$Inline = parent::inlineFootnoteMarker($Excerpt)) {
+            return;
+        }
+        $Name = null;
+        if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches)) {
+            $Name = $matches[1];
+        }
+        $Args = array(is_numeric($Name) ? (float) $Name : $Name, $this->DefinitionData['Footnote'][$Name]['count']);
+        $this->doSetAttributes($Inline['element'], $this->footnoteReferenceAttributes, $Args);
+        $this->doSetAttributes($Inline['element']['element'], $this->footnoteLinkAttributes, $Args);
+        $this->doSetContent($Inline['element']['element'], $this->footnoteLinkHtml, false, 'text', $Args);
+        $Inline['element']['element']['rawHtml'] = $Inline['element']['element']['text'];
+        $Inline['element']['element']['allowRawHtmlInSafeMode'] = true;
+        unset($Inline['element']['element']['text']);
+        return $Inline;
+    }
+
+    protected function inlineImage($Excerpt) {
+        if (!$Inline = parent::inlineImage($Excerpt)) {
+            return;
+        }
+        $this->doSetAttributes($Inline['element'], $this->imageAttributes, array($this->isLocal($Inline['element'], 'src')));
+        return $Inline;
+    }
+
+    protected function inlineLink($Excerpt) {
+        return $this->doSetLink($Excerpt, __FUNCTION__);
+    }
+
+    protected function inlineText($Text) {
+        $this->doSetData($this->DefinitionData['Abbreviation'], $this->abbreviationData);
+        return parent::inlineText($Text);
+    }
+
+    protected function inlineUrl($Excerpt) {
+        return $this->doSetLink($Excerpt, __FUNCTION__);
+    }
+
+    protected function inlineUrlTag($Excerpt) {
+        return $this->doSetLink($Excerpt, __FUNCTION__);
+    }
+
+    protected function isLocal($Element, $Key) {
+        $Link = isset($Element['attributes'][$Key]) ? (string) $Element['attributes'][$Key] : null;
+        if (
+            // `<a href="">`
+            $Link === "" ||
+            // `<a href="../foo/bar">`
+            // `<a href="/foo/bar">`
+            // `<a href="?foo=bar">`
+            // `<a href="&foo=bar">`
+            // `<a href="#foo">`
+            strpos('./?&#', $Link[0]) !== false && strpos($Link, '//') !== 0 ||
+            // `<a href="data:text/html,asdf">`
+            strpos($Link, 'data:') === 0 ||
+            // `<a href="javascript:;">`
+            strpos($Link, 'javascript:') === 0
+        ) {
+            return true;
+        }
+        if (isset($_SERVER['HTTP_HOST'])) {
+            $Host = $_SERVER['HTTP_HOST'];
+        } else if (isset($_SERVER['SERVER_NAME'])) {
+            $Host = $_SERVER['SERVER_NAME'];
+        } else {
+            $Host = "";
+        }
+        // `<a href="//example.com">`
+        if (strpos($Link, '//') === 0 && strpos($Link, '//' . $Host) !== 0) {
+            return false;
+        }
+        if (
+            // `<a href="https://127.0.0.1">`
+            strpos($Link, 'https://' . $Host) === 0 ||
+            // `<a href="http://127.0.0.1">`
+            strpos($Link, 'http://' . $Host) === 0
+        ) {
+            return true;
+        }
+        // `<a href="foo/bar">`
+        return strpos($Link, '://') === false;
+    }
+
+    protected function parseAttributeData($attributeString) {
+        # Allow compact attributes
+        $attributeString = strtr($attributeString, array(
+            '#' => ' #',
+            '.' => ' .'
+        ));
+        if (strpos($attributeString, '="') !== false || strpos($attributeString, "='") !== false) {
+            $attributeString = preg_replace_callback('#([-\w]+=)(["\'])([^\n]*?)\2#', function($matches) {
+                $value = strtr($matches[3], array(
+                    ' #' => '#',
+                    ' .' => '.',
+                    ' ' => "\x1A"
+                ));
+                return $matches[1] . $matches[2] . $value . $matches[2];
+            }, $attributeString);
+        }
+        $Attributes = array();
+        foreach (explode(' ', $attributeString) as $v) {
+            if (!$v) {
+                continue;
+            }
+            // `{#foo}`
+            if ($v[0] === '#' && isset($v[1])) {
+                $Attributes['id'] = substr($v, 1);
+            // `{.foo}`
+            } else if ($v[0] === '.' && isset($v[1])) {
+                $Attributes['class'][] = substr($v, 1);
+            // ~
+            } else if (strpos($v, '=') !== false) {
+                $vv = explode('=', $v, 2);
+                // `{foo=}`
+                if ($vv[1] === "") {
+                    $Attributes[$vv[0]] = "";
+                // `{foo="bar baz"}`
+                // `{foo='bar baz'}`
+                } else if ($vv[1][0] === '"' && substr($vv[1], -1) === '"' || $vv[1][0] === "'" && substr($vv[1], -1) === "'") {
+                    $Attributes[$vv[0]] = stripslashes(strtr(substr(substr($vv[1], 1), 0, -1), "\x1A", ' '));
+                // `{foo=bar}`
+                } else {
+                    $Attributes[$vv[0]] = $vv[1];
+                }
+            // `{foo}`
+            } else {
+                $Attributes[$v] = $v;
+            }
+        }
+        if (isset($Attributes['class'])) {
+            $Attributes['class'] = implode(' ', array_unique($Attributes['class']));
+        }
+        return $Attributes;
+    }
+
+}
\ No newline at end of file