Plugin Directory

Changeset 3393327


Ignore:
Timestamp:
11/11/2025 02:44:57 AM (5 months ago)
Author:
peter202202
Message:

Releasing 3.24.0

Location:
markup-markdown
Files:
2451 added
4 edited

Legend:

Unmodified
Added
Removed
  • markup-markdown/trunk/MarkupMarkdown/Core/Parser.php

    r3392623 r3393327  
    160160            $this->allowed_html = wp_kses_allowed_html( 'post' );
    161161            $this->allowed_html[ 'iframe' ] = array( 'src' => true, 'height' => true, 'width' => true, 'id' => true, 'class' => true, 'title' => true, 'frameborder' => true, 'allow' => true, 'allowfullscreen' => true );
    162         endif;
    163         if ( ! isset( $this->allowed_html[ 'form' ] ) && ( post_password_required() || defined( 'MMD_O2_PLUG' ) ) ) :
     162            $this->allowed_html[ 'input' ] = array( 'type'  => array(), 'name' => array(), 'value' => array(), 'class' => array(), 'id' => array(), 'placeholder' => array(), 'checked' => array(), 'required' => array(), 'readonly' => array(), 'disabled' => array() );
     163        endif;
     164        if ( ! isset( $this->allowed_html[ 'form' ] ) && post_password_required() ) :
    164165            $this->allowed_html[ 'form' ] = array( 'action' => true, 'accept' => true, 'accept-charset' => true, 'enctype' => true, 'method' => true, 'name' => true, 'target' => true );
    165             $this->allowed_html[ 'input' ] = array( 'type'  => array(), 'name' => array(), 'value' => array(), 'class' => array(), 'id' => array(), 'placeholder' => array(), 'checked' => array(), 'required' => array(), 'readonly' => array(), 'disabled' => array() );
    166         elseif ( isset( $this->allowed_html[ 'form' ] ) && ! defined( 'MMD_O2_PLUG' ) ) :
     166        elseif ( isset( $this->allowed_html[ 'form' ] ) ) :
    167167            unset( $this->allowed_html[ 'form' ] );
    168             unset( $this->allowed_html[ 'input' ] );
    169168        endif;
    170169        return wp_kses( $field_content, $this->allowed_html );
  • markup-markdown/trunk/MarkupMarkdown/Parsedown/Parsedown.php

    r3381579 r3393327  
    2020class Parsedown
    2121{
    22     # ~
    23 
    24     const version = '1.7.4';
    25 
    26     # ~
    27 
    28     function text($text)
    29     {
    30         # make sure no definitions are set
    31         $this->DefinitionData = array();
    32 
    33         # standardize line breaks
    34         $text = str_replace(array("\r\n", "\r"), "\n", $text);
    35 
    36         # remove surrounding line breaks
    37         $text = trim($text, "\n");
    38 
    39         # split text into lines
    40         $lines = explode("\n", $text);
    41 
    42         # iterate through lines to identify blocks
    43         $markup = $this->lines($lines);
    44 
    45         # trim line breaks
    46         $markup = trim($markup, "\n");
    47 
    48         return $markup;
    49     }
    50 
    51     #
    52     # Setters
    53     #
    54 
    55     function setBreaksEnabled($breaksEnabled)
    56     {
    57         $this->breaksEnabled = $breaksEnabled;
    58 
    59         return $this;
    60     }
    61 
    62     protected $breaksEnabled;
    63 
    64     function setMarkupEscaped($markupEscaped)
    65     {
    66         $this->markupEscaped = $markupEscaped;
    67 
    68         return $this;
    69     }
    70 
    71     protected $markupEscaped;
    72 
    73     function setUrlsLinked($urlsLinked)
    74     {
    75         $this->urlsLinked = $urlsLinked;
    76 
    77         return $this;
    78     }
    79 
    80     protected $urlsLinked = true;
    81 
    82     function setSafeMode($safeMode)
    83     {
    84         $this->safeMode = (bool) $safeMode;
    85 
    86         return $this;
    87     }
    88 
    89     protected $safeMode;
    90 
    91     protected $safeLinksWhitelist = array(
    92         'http://',
    93         'https://',
    94         'ftp://',
    95         'ftps://',
    96         'mailto:',
    97         'data:image/png;base64,',
    98         'data:image/gif;base64,',
    99         'data:image/jpeg;base64,',
    100         'irc:',
    101         'ircs:',
    102         'git:',
    103         'ssh:',
    104         'news:',
    105         'steam:',
    106     );
     22    # ~
     23
     24    const version = '1.7.4';
     25
     26    # ~
     27
     28    function text($text)
     29    {
     30        # make sure no definitions are set
     31        $this->DefinitionData = array();
     32
     33        # standardize line breaks
     34        $text = str_replace(array("\r\n", "\r"), "\n", $text);
     35
     36        # remove surrounding line breaks
     37        $text = trim($text, "\n");
     38
     39        # split text into lines
     40        $lines = explode("\n", $text);
     41
     42        # iterate through lines to identify blocks
     43        $markup = $this->lines($lines);
     44
     45        # trim line breaks
     46        $markup = trim($markup, "\n");
     47
     48        return $markup;
     49    }
     50
     51    #
     52    # Setters
     53    #
     54
     55    function setBreaksEnabled($breaksEnabled)
     56    {
     57        $this->breaksEnabled = $breaksEnabled;
     58
     59        return $this;
     60    }
     61
     62    protected $breaksEnabled;
     63
     64    function setMarkupEscaped($markupEscaped)
     65    {
     66        $this->markupEscaped = $markupEscaped;
     67
     68        return $this;
     69    }
     70
     71    protected $markupEscaped;
     72
     73    function setUrlsLinked($urlsLinked)
     74    {
     75        $this->urlsLinked = $urlsLinked;
     76
     77        return $this;
     78    }
     79
     80    protected $urlsLinked = true;
     81
     82    function setSafeMode($safeMode)
     83    {
     84        $this->safeMode = (bool) $safeMode;
     85
     86        return $this;
     87    }
     88
     89    protected $safeMode;
     90
     91    protected $safeLinksWhitelist = array(
     92        'http://',
     93        'https://',
     94        'ftp://',
     95        'ftps://',
     96        'mailto:',
     97        'data:image/png;base64,',
     98        'data:image/gif;base64,',
     99        'data:image/jpeg;base64,',
     100        'irc:',
     101        'ircs:',
     102        'git:',
     103        'ssh:',
     104        'news:',
     105        'steam:',
     106    );
    107107
    108108    function setStrictMode($strictMode)
    109     {
    110         $this->strictMode = (bool) $strictMode;
    111 
    112         return $this;
    113     }
    114 
    115     protected $strictMode;
    116 
    117     #
    118     # Lines
    119     #
    120 
    121     protected $BlockTypes = array(
    122         '#' => array('Header'),
    123         '*' => array('Rule', 'List'),
    124         '+' => array('List'),
    125         '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
    126         '0' => array('List'),
    127         '1' => array('List'),
    128         '2' => array('List'),
    129         '3' => array('List'),
    130         '4' => array('List'),
    131         '5' => array('List'),
    132         '6' => array('List'),
    133         '7' => array('List'),
    134         '8' => array('List'),
    135         '9' => array('List'),
    136         ':' => array('Table'),
    137         '<' => array('Comment', 'Markup'),
    138         '=' => array('SetextHeader'),
    139         '>' => array('Quote'),
    140         '[' => array('Reference'),
    141         '_' => array('Rule'),
    142         '`' => array('FencedCode'),
    143         '|' => array('Table'),
    144         '~' => array('FencedCode'),
    145     );
    146 
    147     # ~
    148 
    149     protected $unmarkedBlockTypes = array(
    150         'Code',
    151     );
    152 
    153     #
    154     # Blocks
    155     #
    156 
    157     protected function lines(array $lines)
    158     {
    159         $CurrentBlock = null;
    160 
    161         foreach ($lines as $line)
    162         {
    163             if (chop($line) === '')
    164             {
    165                 if (isset($CurrentBlock))
    166                 {
    167                     $CurrentBlock['interrupted'] = true;
    168                 }
    169 
    170                 continue;
    171             }
    172 
    173             if (strpos($line, "\t") !== false)
    174             {
    175                 $parts = explode("\t", $line);
    176 
    177                 $line = $parts[0];
    178 
    179                 unset($parts[0]);
    180 
    181                 foreach ($parts as $part)
    182                 {
    183                     $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
    184 
    185                     $line .= str_repeat(' ', $shortage);
    186                     $line .= $part;
    187                 }
    188             }
    189 
    190             $indent = 0;
    191 
    192             while (isset($line[$indent]) and $line[$indent] === ' ')
    193             {
    194                 $indent ++;
    195             }
    196 
    197             $text = $indent > 0 ? substr($line, $indent) : $line;
    198 
    199             # ~
    200 
    201             $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
    202 
    203             # ~
    204 
    205             if (isset($CurrentBlock['continuable']))
    206             {
    207                 $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
    208 
    209                 if (isset($Block))
    210                 {
    211                     $CurrentBlock = $Block;
    212 
    213                     continue;
    214                 }
    215                 else
    216                 {
    217                     if ($this->isBlockCompletable($CurrentBlock['type']))
    218                     {
    219                         $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
    220                     }
    221                 }
    222             }
    223 
    224             # ~
    225 
    226             $marker = $text[0];
    227 
    228             # ~
    229 
    230             $blockTypes = $this->unmarkedBlockTypes;
    231 
    232             if (isset($this->BlockTypes[$marker]))
    233             {
    234                 foreach ($this->BlockTypes[$marker] as $blockType)
    235                 {
    236                     $blockTypes []= $blockType;
    237                 }
    238             }
    239 
    240             #
    241             # ~
    242 
    243             foreach ($blockTypes as $blockType)
    244             {
    245                 $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
    246 
    247                 if (isset($Block))
    248                 {
    249                     $Block['type'] = $blockType;
    250 
    251                     if ( ! isset($Block['identified']))
    252                     {
    253                         $Blocks []= $CurrentBlock;
    254 
    255                         $Block['identified'] = true;
    256                     }
    257 
    258                     if ($this->isBlockContinuable($blockType))
    259                     {
    260                         $Block['continuable'] = true;
    261                     }
    262 
    263                     $CurrentBlock = $Block;
    264 
    265                     continue 2;
    266                 }
    267             }
    268 
    269             # ~
    270 
    271             if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
    272             {
    273                 $CurrentBlock['element']['text'] .= "\n".$text;
    274             }
    275             else
    276             {
    277                 $Blocks []= $CurrentBlock;
    278 
    279                 $CurrentBlock = $this->paragraph($Line);
    280 
    281                 $CurrentBlock['identified'] = true;
    282             }
    283         }
    284 
    285         # ~
    286 
    287         if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
    288         {
    289             $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
    290         }
    291 
    292         # ~
    293 
    294         $Blocks []= $CurrentBlock;
    295 
    296         unset($Blocks[0]);
    297 
    298         # ~
    299 
    300         $markup = '';
    301 
    302         foreach ($Blocks as $Block)
    303         {
    304             if (isset($Block['hidden']))
    305             {
    306                 continue;
    307             }
    308 
    309             $markup .= "\n";
    310             $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
    311         }
    312 
    313         $markup .= "\n";
    314 
    315         # ~
    316 
    317         return $markup;
    318     }
    319 
    320     protected function isBlockContinuable($Type)
    321     {
    322         return method_exists($this, 'block'.$Type.'Continue');
    323     }
    324 
    325     protected function isBlockCompletable($Type)
    326     {
    327         return method_exists($this, 'block'.$Type.'Complete');
    328     }
    329 
    330     #
    331     # Code
    332 
    333     protected function blockCode($Line, $Block = null)
    334     {
    335         if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
    336         {
    337             return;
    338         }
    339 
    340         if ($Line['indent'] >= 4)
    341         {
    342             $text = substr($Line['body'], 4);
    343 
    344             $Block = array(
    345                 'element' => array(
    346                     'name' => 'pre',
    347                     'handler' => 'element',
    348                     'text' => array(
    349                         'name' => 'code',
    350                         'text' => $text,
    351                     ),
    352                 ),
    353             );
    354 
    355             return $Block;
    356         }
    357     }
    358 
    359     protected function blockCodeContinue($Line, $Block)
    360     {
    361         if ($Line['indent'] >= 4)
    362         {
    363             if (isset($Block['interrupted']))
    364             {
    365                 $Block['element']['text']['text'] .= "\n";
    366 
    367                 unset($Block['interrupted']);
    368             }
    369 
    370             $Block['element']['text']['text'] .= "\n";
    371 
    372             $text = substr($Line['body'], 4);
    373 
    374             $Block['element']['text']['text'] .= $text;
    375 
    376             return $Block;
    377         }
    378     }
    379 
    380     protected function blockCodeComplete($Block)
    381     {
    382         $text = $Block['element']['text']['text'];
    383 
    384         $Block['element']['text']['text'] = $text;
    385 
    386         return $Block;
    387     }
    388 
    389     #
    390     # Comment
    391 
    392     protected function blockComment($Line)
    393     {
    394         if ($this->markupEscaped or $this->safeMode)
    395         {
    396             return;
    397         }
    398 
    399         if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
    400         {
    401             $Block = array(
    402                 'markup' => $Line['body'],
    403             );
    404 
    405             if (preg_match('/-->$/', $Line['text']))
    406             {
    407                 $Block['closed'] = true;
    408             }
    409 
    410             return $Block;
    411         }
    412     }
    413 
    414     protected function blockCommentContinue($Line, array $Block)
    415     {
    416         if (isset($Block['closed']))
    417         {
    418             return;
    419         }
    420 
    421         $Block['markup'] .= "\n" . $Line['body'];
    422 
    423         if (preg_match('/-->$/', $Line['text']))
    424         {
    425             $Block['closed'] = true;
    426         }
    427 
    428         return $Block;
    429     }
    430 
    431     #
    432     # Fenced Code
    433 
    434     protected function blockFencedCode($Line)
    435     {
    436         if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
    437         {
    438             $Element = array(
    439                 'name' => 'code',
    440                 'text' => '',
    441             );
    442 
    443             if (isset($matches[1]))
    444             {
    445                 /**
    446                 * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
    447                 * Every HTML element may have a class attribute specified.
    448                 * The attribute, if specified, must have a value that is a set
    449                 * of space-separated tokens representing the various classes
    450                 * that the element belongs to.
    451                 * [...]
    452                 * The space characters, for the purposes of this specification,
    453                 * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
    454                 * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
    455                 * U+000D CARRIAGE RETURN (CR).
    456                 */
    457                 $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
    458 
    459                 $class = 'lang-' . $language . ' language-' . $language . ( $language !== 'mermaid' ? ' ' . $language : '' );
    460 
    461                 $Element['attributes'] = array(
    462                     'class' => $class,
    463                 );
    464             }
    465 
    466             $Block = array(
    467                 'char' => $Line['text'][0],
    468                 'element' => array(
    469                     'name' => 'pre',
    470                     'handler' => 'element',
    471                     'text' => $Element,
    472                 ),
    473             );
    474 
    475             return $Block;
    476         }
    477     }
    478 
    479     protected function blockFencedCodeContinue($Line, $Block)
    480     {
    481         if (isset($Block['complete']))
    482         {
    483             return;
    484         }
    485 
    486         if (isset($Block['interrupted']))
    487         {
    488             $Block['element']['text']['text'] .= "\n";
    489 
    490             unset($Block['interrupted']);
    491         }
    492 
    493         if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
    494         {
    495             $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
    496 
    497             $Block['complete'] = true;
    498 
    499             return $Block;
    500         }
    501 
    502         $Block['element']['text']['text'] .= "\n".$Line['body'];
    503 
    504         return $Block;
    505     }
    506 
    507     protected function blockFencedCodeComplete($Block)
    508     {
    509         $text = $Block['element']['text']['text'];
    510 
    511         $Block['element']['text']['text'] = $text;
    512 
    513         return $Block;
    514     }
    515 
    516     #
    517     # Header
    518 
    519     protected function blockHeader($Line)
    520     {
    521         if (isset($Line['text'][1]))
    522         {
    523             $level = 1;
    524 
    525             while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
    526             {
    527                 $level ++;
    528             }
    529 
    530             if ($level > 6)
    531             {
    532                 return;
    533             }
    534 
    535             $text = trim($Line['text'], '#');
     109    {
     110        $this->strictMode = (bool) $strictMode;
     111
     112        return $this;
     113    }
     114
     115    protected $strictMode;
     116
     117    #
     118    # Lines
     119    #
     120
     121    protected $BlockTypes = array(
     122        '#' => array('Header'),
     123        '*' => array('Rule', 'List'),
     124        '+' => array('List'),
     125        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
     126        '0' => array('List'),
     127        '1' => array('List'),
     128        '2' => array('List'),
     129        '3' => array('List'),
     130        '4' => array('List'),
     131        '5' => array('List'),
     132        '6' => array('List'),
     133        '7' => array('List'),
     134        '8' => array('List'),
     135        '9' => array('List'),
     136        ':' => array('Table'),
     137        '<' => array('Comment', 'Markup'),
     138        '=' => array('SetextHeader'),
     139        '>' => array('Quote'),
     140        '[' => array('Reference'),
     141        '_' => array('Rule'),
     142        '`' => array('FencedCode'),
     143        '|' => array('Table'),
     144        '~' => array('FencedCode'),
     145    );
     146
     147    # ~
     148
     149    protected $unmarkedBlockTypes = array(
     150        'Code',
     151    );
     152
     153    #
     154    # Blocks
     155    #
     156
     157    protected function lines(array $lines)
     158    {
     159        $CurrentBlock = null;
     160
     161        foreach ($lines as $line)
     162        {
     163            if (chop($line) === '')
     164            {
     165                if (isset($CurrentBlock))
     166                {
     167                    $CurrentBlock['interrupted'] = true;
     168                }
     169
     170                continue;
     171            }
     172
     173            if (strpos($line, "\t") !== false)
     174            {
     175                $parts = explode("\t", $line);
     176
     177                $line = $parts[0];
     178
     179                unset($parts[0]);
     180
     181                foreach ($parts as $part)
     182                {
     183                    $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
     184
     185                    $line .= str_repeat(' ', $shortage);
     186                    $line .= $part;
     187                }
     188            }
     189
     190            $indent = 0;
     191
     192            while (isset($line[$indent]) and $line[$indent] === ' ')
     193            {
     194                $indent ++;
     195            }
     196
     197            $text = $indent > 0 ? substr($line, $indent) : $line;
     198
     199            # ~
     200
     201            $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
     202
     203            # ~
     204
     205            if (isset($CurrentBlock['continuable']))
     206            {
     207                $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
     208
     209                if (isset($Block))
     210                {
     211                    $CurrentBlock = $Block;
     212
     213                    continue;
     214                }
     215                else
     216                {
     217                    if ($this->isBlockCompletable($CurrentBlock['type']))
     218                    {
     219                        $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
     220                    }
     221                }
     222            }
     223
     224            # ~
     225
     226            $marker = $text[0];
     227
     228            # ~
     229
     230            $blockTypes = $this->unmarkedBlockTypes;
     231
     232            if (isset($this->BlockTypes[$marker]))
     233            {
     234                foreach ($this->BlockTypes[$marker] as $blockType)
     235                {
     236                    $blockTypes []= $blockType;
     237                }
     238            }
     239
     240            #
     241            # ~
     242
     243            foreach ($blockTypes as $blockType)
     244            {
     245                $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
     246
     247                if (isset($Block))
     248                {
     249                    $Block['type'] = $blockType;
     250
     251                    if ( ! isset($Block['identified']))
     252                    {
     253                        $Blocks []= $CurrentBlock;
     254
     255                        $Block['identified'] = true;
     256                    }
     257
     258                    if ($this->isBlockContinuable($blockType))
     259                    {
     260                        $Block['continuable'] = true;
     261                    }
     262
     263                    $CurrentBlock = $Block;
     264
     265                    continue 2;
     266                }
     267            }
     268
     269            # ~
     270
     271            if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
     272            {
     273                $CurrentBlock['element']['text'] .= "\n".$text;
     274            }
     275            else
     276            {
     277                $Blocks []= $CurrentBlock;
     278
     279                $CurrentBlock = $this->paragraph($Line);
     280
     281                $CurrentBlock['identified'] = true;
     282            }
     283        }
     284
     285        # ~
     286
     287        if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
     288        {
     289            $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
     290        }
     291
     292        # ~
     293
     294        $Blocks []= $CurrentBlock;
     295
     296        unset($Blocks[0]);
     297
     298        # ~
     299
     300        $markup = '';
     301
     302        foreach ($Blocks as $Block)
     303        {
     304            if (isset($Block['hidden']))
     305            {
     306                continue;
     307            }
     308
     309            $markup .= "\n";
     310            $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
     311        }
     312
     313        $markup .= "\n";
     314
     315        # ~
     316
     317        return $markup;
     318    }
     319
     320    protected function isBlockContinuable($Type)
     321    {
     322        return method_exists($this, 'block'.$Type.'Continue');
     323    }
     324
     325    protected function isBlockCompletable($Type)
     326    {
     327        return method_exists($this, 'block'.$Type.'Complete');
     328    }
     329
     330    #
     331    # Code
     332
     333    protected function blockCode($Line, $Block = null)
     334    {
     335        if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
     336        {
     337            return;
     338        }
     339
     340        if ($Line['indent'] >= 4)
     341        {
     342            $text = substr($Line['body'], 4);
     343
     344            $Block = array(
     345                'element' => array(
     346                    'name' => 'pre',
     347                    'handler' => 'element',
     348                    'text' => array(
     349                        'name' => 'code',
     350                        'text' => $text,
     351                    ),
     352                ),
     353            );
     354
     355            return $Block;
     356        }
     357    }
     358
     359    protected function blockCodeContinue($Line, $Block)
     360    {
     361        if ($Line['indent'] >= 4)
     362        {
     363            if (isset($Block['interrupted']))
     364            {
     365                $Block['element']['text']['text'] .= "\n";
     366
     367                unset($Block['interrupted']);
     368            }
     369
     370            $Block['element']['text']['text'] .= "\n";
     371
     372            $text = substr($Line['body'], 4);
     373
     374            $Block['element']['text']['text'] .= $text;
     375
     376            return $Block;
     377        }
     378    }
     379
     380    protected function blockCodeComplete($Block)
     381    {
     382        $text = $Block['element']['text']['text'];
     383
     384        $Block['element']['text']['text'] = $text;
     385
     386        return $Block;
     387    }
     388
     389    #
     390    # Comment
     391
     392    protected function blockComment($Line)
     393    {
     394        if ($this->markupEscaped or $this->safeMode)
     395        {
     396            return;
     397        }
     398
     399        if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
     400        {
     401            $Block = array(
     402                'markup' => $Line['body'],
     403            );
     404
     405            if (preg_match('/-->$/', $Line['text']))
     406            {
     407                $Block['closed'] = true;
     408            }
     409
     410            return $Block;
     411        }
     412    }
     413
     414    protected function blockCommentContinue($Line, array $Block)
     415    {
     416        if (isset($Block['closed']))
     417        {
     418            return;
     419        }
     420
     421        $Block['markup'] .= "\n" . $Line['body'];
     422
     423        if (preg_match('/-->$/', $Line['text']))
     424        {
     425            $Block['closed'] = true;
     426        }
     427
     428        return $Block;
     429    }
     430
     431    #
     432    # Fenced Code
     433
     434    protected function blockFencedCode($Line)
     435    {
     436        if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
     437        {
     438            $Element = array(
     439                'name' => 'code',
     440                'text' => '',
     441            );
     442
     443            if (isset($matches[1]))
     444            {
     445                /**
     446                * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
     447                * Every HTML element may have a class attribute specified.
     448                * The attribute, if specified, must have a value that is a set
     449                * of space-separated tokens representing the various classes
     450                * that the element belongs to.
     451                * [...]
     452                * The space characters, for the purposes of this specification,
     453                * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
     454                * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
     455                * U+000D CARRIAGE RETURN (CR).
     456                */
     457                $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
     458
     459                $class = 'lang-' . $language . ' language-' . $language . ( $language !== 'mermaid' ? ' ' . $language : '' );
     460
     461                $Element['attributes'] = array(
     462                    'class' => $class,
     463                );
     464            }
     465
     466            $Block = array(
     467                'char' => $Line['text'][0],
     468                'element' => array(
     469                    'name' => 'pre',
     470                    'handler' => 'element',
     471                    'text' => $Element,
     472                ),
     473            );
     474
     475            return $Block;
     476        }
     477    }
     478
     479    protected function blockFencedCodeContinue($Line, $Block)
     480    {
     481        if (isset($Block['complete']))
     482        {
     483            return;
     484        }
     485
     486        if (isset($Block['interrupted']))
     487        {
     488            $Block['element']['text']['text'] .= "\n";
     489
     490            unset($Block['interrupted']);
     491        }
     492
     493        if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
     494        {
     495            $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
     496
     497            $Block['complete'] = true;
     498
     499            return $Block;
     500        }
     501
     502        $Block['element']['text']['text'] .= "\n".$Line['body'];
     503
     504        return $Block;
     505    }
     506
     507    protected function blockFencedCodeComplete($Block)
     508    {
     509        $text = $Block['element']['text']['text'];
     510
     511        $Block['element']['text']['text'] = $text;
     512
     513        return $Block;
     514    }
     515
     516    #
     517    # Header
     518
     519    protected function blockHeader($Line)
     520    {
     521        if (isset($Line['text'][1]))
     522        {
     523            $level = 1;
     524
     525            while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
     526            {
     527                $level ++;
     528            }
     529
     530            if ($level > 6)
     531            {
     532                return;
     533            }
     534
     535            $text = trim($Line['text'], '#');
    536536
    537537            if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
     
    542542            $text = trim($text, ' ');
    543543
    544             $Block = array(
    545                 'element' => array(
    546                     'name' => 'h' . min(6, $level),
    547                     'text' => $text,
    548                     'handler' => 'line',
    549                 ),
    550             );
    551 
    552             return $Block;
    553         }
    554     }
    555 
    556     #
    557     # List
    558 
    559     protected function blockList($Line)
    560     {
    561         list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
    562 
    563         if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
    564         {
    565             $Block = array(
    566                 'indent' => $Line['indent'],
    567                 'pattern' => $pattern,
    568                 'element' => array(
    569                     'name' => $name,
    570                     'handler' => 'elements',
    571                 ),
    572             );
    573 
    574             if($name === 'ol')
    575             {
    576                 $listStart = stristr($matches[0], '.', true);
    577 
    578                 if($listStart !== '1')
    579                 {
    580                     $Block['element']['attributes'] = array('start' => $listStart);
    581                 }
    582             }
    583 
    584             $Block['li'] = array(
    585                 'name' => 'li',
    586                 'handler' => 'li',
    587                 'text' => array(
    588                     $matches[2],
    589                 ),
    590             );
    591 
    592             $Block['element']['text'] []= & $Block['li'];
    593 
    594             return $Block;
    595         }
    596     }
    597 
    598     protected function blockListContinue($Line, array $Block)
    599     {
    600         if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
    601         {
    602             if (isset($Block['interrupted']))
    603             {
    604                 $Block['li']['text'] []= '';
    605 
    606                 $Block['loose'] = true;
    607 
    608                 unset($Block['interrupted']);
    609             }
    610 
    611             unset($Block['li']);
    612 
    613             $text = isset($matches[1]) ? $matches[1] : '';
    614 
    615             $Block['li'] = array(
    616                 'name' => 'li',
    617                 'handler' => 'li',
    618                 'text' => array(
    619                     $text,
    620                 ),
    621             );
    622 
    623             $Block['element']['text'] []= & $Block['li'];
    624 
    625             return $Block;
    626         }
    627 
    628         if ($Line['text'][0] === '[' and $this->blockReference($Line))
    629         {
    630             return $Block;
    631         }
    632 
    633         if ( ! isset($Block['interrupted']))
    634         {
    635             $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
    636 
    637             $Block['li']['text'] []= $text;
    638 
    639             return $Block;
    640         }
    641 
    642         if ($Line['indent'] > 0)
    643         {
    644             $Block['li']['text'] []= '';
    645 
    646             $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
    647 
    648             $Block['li']['text'] []= $text;
    649 
    650             unset($Block['interrupted']);
    651 
    652             return $Block;
    653         }
    654     }
    655 
    656     protected function blockListComplete(array $Block)
    657     {
    658         if (isset($Block['loose']))
    659         {
    660             foreach ($Block['element']['text'] as &$li)
    661             {
    662                 if (end($li['text']) !== '')
    663                 {
    664                     $li['text'] []= '';
    665                 }
    666             }
    667         }
    668 
    669         return $Block;
    670     }
    671 
    672     #
    673     # Quote
    674 
    675     protected function blockQuote($Line)
    676     {
    677         if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
    678         {
    679             $Block = array(
    680                 'element' => array(
    681                     'name' => 'blockquote',
    682                     'handler' => 'lines',
    683                     'text' => (array) $matches[1],
    684                 ),
    685             );
    686 
    687             return $Block;
    688         }
    689     }
    690 
    691     protected function blockQuoteContinue($Line, array $Block)
    692     {
    693         if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
    694         {
    695             if (isset($Block['interrupted']))
    696             {
    697                 $Block['element']['text'] []= '';
    698 
    699                 unset($Block['interrupted']);
    700             }
    701 
    702             $Block['element']['text'] []= $matches[1];
    703 
    704             return $Block;
    705         }
    706 
    707         if ( ! isset($Block['interrupted']))
    708         {
    709             $Block['element']['text'] []= $Line['text'];
    710 
    711             return $Block;
    712         }
    713     }
    714 
    715     #
    716     # Rule
    717 
    718     protected function blockRule($Line)
    719     {
    720         if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
    721         {
    722             $Block = array(
    723                 'element' => array(
    724                     'name' => 'hr'
    725                 ),
    726             );
    727 
    728             return $Block;
    729         }
    730     }
    731 
    732     #
    733     # Setext
    734 
    735     protected function blockSetextHeader($Line, ?array $Block = null)
    736     {
    737         if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
    738         {
    739             return;
    740         }
    741 
    742         if (chop($Line['text'], $Line['text'][0]) === '')
    743         {
    744             $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
    745 
    746             return $Block;
    747         }
    748     }
    749 
    750     #
    751     # Markup
    752 
    753     protected function blockMarkup($Line)
    754     {
    755         if ($this->markupEscaped or $this->safeMode)
    756         {
    757             return;
    758         }
    759 
    760         if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
    761         {
    762             $element = strtolower($matches[1]);
    763 
    764             if (in_array($element, $this->textLevelElements))
    765             {
    766                 return;
    767             }
    768 
    769             $Block = array(
    770                 'name' => $matches[1],
    771                 'depth' => 0,
    772                 'markup' => $Line['text'],
    773             );
    774 
    775             $length = strlen($matches[0]);
    776 
    777             $remainder = substr($Line['text'], $length);
    778 
    779             if (trim($remainder) === '')
    780             {
    781                 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
    782                 {
    783                     $Block['closed'] = true;
    784 
    785                     $Block['void'] = true;
    786                 }
    787             }
    788             else
    789             {
    790                 if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
    791                 {
    792                     return;
    793                 }
    794 
    795                 if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
    796                 {
    797                     $Block['closed'] = true;
    798                 }
    799             }
    800 
    801             return $Block;
    802         }
    803     }
    804 
    805     protected function blockMarkupContinue($Line, array $Block)
    806     {
    807         if (isset($Block['closed']))
    808         {
    809             return;
    810         }
    811 
    812         if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
    813         {
    814             $Block['depth'] ++;
    815         }
    816 
    817         if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
    818         {
    819             if ($Block['depth'] > 0)
    820             {
    821                 $Block['depth'] --;
    822             }
    823             else
    824             {
    825                 $Block['closed'] = true;
    826             }
    827         }
    828 
    829         if (isset($Block['interrupted']))
    830         {
    831             $Block['markup'] .= "\n";
    832 
    833             unset($Block['interrupted']);
    834         }
    835 
    836         $Block['markup'] .= "\n".$Line['body'];
    837 
    838         return $Block;
    839     }
    840 
    841     #
    842     # Reference
    843 
    844     protected function blockReference($Line)
    845     {
    846         if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
    847         {
    848             $id = strtolower($matches[1]);
    849 
    850             $Data = array(
    851                 'url' => $matches[2],
    852                 'title' => null,
    853             );
    854 
    855             if (isset($matches[3]))
    856             {
    857                 $Data['title'] = $matches[3];
    858             }
    859 
    860             $this->DefinitionData['Reference'][$id] = $Data;
    861 
    862             $Block = array(
    863                 'hidden' => true,
    864             );
    865 
    866             return $Block;
    867         }
    868     }
    869 
    870     #
    871     # Table
    872 
    873     protected function blockTable($Line, ?array $Block = null)
    874     {
    875         if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
    876         {
    877             return;
    878         }
    879 
    880         if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
    881         {
    882             $alignments = array();
    883 
    884             $divider = $Line['text'];
    885 
    886             $divider = trim($divider);
    887             $divider = trim($divider, '|');
    888 
    889             $dividerCells = explode('|', $divider);
    890 
    891             foreach ($dividerCells as $dividerCell)
    892             {
    893                 $dividerCell = trim($dividerCell);
    894 
    895                 if ($dividerCell === '')
    896                 {
    897                     continue;
    898                 }
    899 
    900                 $alignment = null;
    901 
    902                 if ($dividerCell[0] === ':')
    903                 {
    904                     $alignment = 'left';
    905                 }
    906 
    907                 if (substr($dividerCell, - 1) === ':')
    908                 {
    909                     $alignment = $alignment === 'left' ? 'center' : 'right';
    910                 }
    911 
    912                 $alignments []= $alignment;
    913             }
    914 
    915             # ~
    916 
    917             $HeaderElements = array();
    918 
    919             $header = $Block['element']['text'];
    920 
    921             $header = trim($header);
    922             $header = trim($header, '|');
    923 
    924             $headerCells = explode('|', $header);
    925 
    926             foreach ($headerCells as $index => $headerCell)
    927             {
    928                 $headerCell = trim($headerCell);
    929 
    930                 $HeaderElement = array(
    931                     'name' => 'th',
    932                     'text' => $headerCell,
    933                     'handler' => 'line',
    934                 );
    935 
    936                 if (isset($alignments[$index]))
    937                 {
    938                     $alignment = $alignments[$index];
    939 
    940                     $HeaderElement['attributes'] = array(
    941                         'style' => 'text-align: '.$alignment.';',
    942                     );
    943                 }
    944 
    945                 $HeaderElements []= $HeaderElement;
    946             }
    947 
    948             # ~
    949 
    950             $Block = array(
    951                 'alignments' => $alignments,
    952                 'identified' => true,
    953                 'element' => array(
    954                     'name' => 'table',
    955                     'handler' => 'elements',
    956                 ),
    957             );
    958 
    959             $Block['element']['text'] []= array(
    960                 'name' => 'thead',
    961                 'handler' => 'elements',
    962             );
    963 
    964             $Block['element']['text'] []= array(
    965                 'name' => 'tbody',
    966                 'handler' => 'elements',
    967                 'text' => array(),
    968             );
    969 
    970             $Block['element']['text'][0]['text'] []= array(
    971                 'name' => 'tr',
    972                 'handler' => 'elements',
    973                 'text' => $HeaderElements,
    974             );
    975 
    976             return $Block;
    977         }
    978     }
    979 
    980     protected function blockTableContinue($Line, array $Block)
    981     {
    982         if (isset($Block['interrupted']))
    983         {
    984             return;
    985         }
    986 
    987         if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
    988         {
    989             $Elements = array();
    990 
    991             $row = $Line['text'];
    992 
    993             $row = trim($row);
    994             $row = trim($row, '|');
    995 
    996             preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
    997 
    998             foreach ($matches[0] as $index => $cell)
    999             {
    1000                 $cell = trim($cell);
    1001 
    1002                 $Element = array(
    1003                     'name' => 'td',
    1004                     'handler' => 'line',
    1005                     'text' => $cell,
    1006                 );
    1007 
    1008                 if (isset($Block['alignments'][$index]))
    1009                 {
    1010                     $Element['attributes'] = array(
    1011                         'style' => 'text-align: '.$Block['alignments'][$index].';',
    1012                     );
    1013                 }
    1014 
    1015                 $Elements []= $Element;
    1016             }
    1017 
    1018             $Element = array(
    1019                 'name' => 'tr',
    1020                 'handler' => 'elements',
    1021                 'text' => $Elements,
    1022             );
    1023 
    1024             $Block['element']['text'][1]['text'] []= $Element;
    1025 
    1026             return $Block;
    1027         }
    1028     }
    1029 
    1030     #
    1031     # ~
    1032     #
    1033 
    1034     protected function paragraph($Line)
    1035     {
    1036         $Block = array(
    1037             'element' => array(
    1038                 'name' => 'p',
    1039                 'text' => $Line['text'],
    1040                 'handler' => 'line',
    1041             ),
    1042         );
    1043 
    1044         return $Block;
    1045     }
    1046 
    1047     #
    1048     # Inline Elements
    1049     #
    1050 
    1051     protected $InlineTypes = array(
    1052         '"' => array('SpecialCharacter'),
    1053         '!' => array('Image'),
    1054         '&' => array('SpecialCharacter'),
    1055         '*' => array('Emphasis'),
    1056         ':' => array('Url'),
    1057         '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
    1058         '>' => array('SpecialCharacter'),
    1059         '[' => array('Link'),
    1060         '_' => array('Emphasis'),
    1061         '`' => array('Code'),
    1062         '~' => array('Strikethrough'),
    1063         '\\' => array('EscapeSequence'),
    1064     );
    1065 
    1066     # ~
    1067 
    1068     protected $inlineMarkerList = '!"*_&[:<>`~\\';
    1069 
    1070     #
    1071     # ~
    1072     #
    1073 
    1074     public function line($text, $nonNestables=array())
    1075     {
    1076         $markup = '';
    1077 
    1078         # $excerpt is based on the first occurrence of a marker
    1079 
    1080         while ($excerpt = strpbrk($text, $this->inlineMarkerList))
    1081         {
    1082             $marker = $excerpt[0];
    1083 
    1084             $markerPosition = strpos($text, $marker);
    1085 
    1086             $Excerpt = array('text' => $excerpt, 'context' => $text);
    1087 
    1088             foreach ($this->InlineTypes[$marker] as $inlineType)
    1089             {
    1090                 # check to see if the current inline type is nestable in the current context
    1091 
    1092                 if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
    1093                 {
    1094                     continue;
    1095                 }
    1096 
    1097                 $Inline = $this->{'inline'.$inlineType}($Excerpt);
    1098 
    1099                 if ( ! isset($Inline))
    1100                 {
    1101                     continue;
    1102                 }
    1103 
    1104                 # makes sure that the inline belongs to "our" marker
    1105 
    1106                 if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
    1107                 {
    1108                     continue;
    1109                 }
    1110 
    1111                 # sets a default inline position
    1112 
    1113                 if ( ! isset($Inline['position']))
    1114                 {
    1115                     $Inline['position'] = $markerPosition;
    1116                 }
    1117 
    1118                 # cause the new element to 'inherit' our non nestables
    1119 
    1120                 foreach ($nonNestables as $non_nestable)
    1121                 {
    1122                     $Inline['element']['nonNestables'][] = $non_nestable;
    1123                 }
    1124 
    1125                 # the text that comes before the inline
    1126                 $unmarkedText = substr($text, 0, $Inline['position']);
    1127 
    1128                 # compile the unmarked text
    1129                 $markup .= $this->unmarkedText($unmarkedText);
    1130 
    1131                 # compile the inline
    1132                 $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
    1133 
    1134                 # remove the examined text
    1135                 $text = substr($text, $Inline['position'] + $Inline['extent']);
    1136 
    1137                 continue 2;
    1138             }
    1139 
    1140             # the marker does not belong to an inline
    1141 
    1142             $unmarkedText = substr($text, 0, $markerPosition + 1);
    1143 
    1144             $markup .= $this->unmarkedText($unmarkedText);
    1145 
    1146             $text = substr($text, $markerPosition + 1);
    1147         }
    1148 
    1149         $markup .= $this->unmarkedText($text);
    1150 
    1151         return $markup;
    1152     }
    1153 
    1154     #
    1155     # ~
    1156     #
    1157 
    1158     protected function inlineCode($Excerpt)
    1159     {
    1160         $marker = $Excerpt['text'][0];
    1161 
    1162         if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
    1163         {
    1164             $text = $matches[2];
    1165             $text = preg_replace("/[ ]*\n/", ' ', $text);
    1166 
    1167             return array(
    1168                 'extent' => strlen($matches[0]),
    1169                 'element' => array(
    1170                     'name' => 'code',
    1171                     'text' => $text,
    1172                 ),
    1173             );
    1174         }
    1175     }
    1176 
    1177     protected function inlineEmailTag($Excerpt)
    1178     {
    1179         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
    1180         {
    1181             $url = $matches[1];
    1182 
    1183             if ( ! isset($matches[2]))
    1184             {
    1185                 $url = 'mailto:' . $url;
    1186             }
    1187 
    1188             return array(
    1189                 'extent' => strlen($matches[0]),
    1190                 'element' => array(
    1191                     'name' => 'a',
    1192                     'text' => $matches[1],
    1193                     'attributes' => array(
    1194                         'href' => $url,
    1195                     ),
    1196                 ),
    1197             );
    1198         }
    1199     }
    1200 
    1201     protected function inlineEmphasis($Excerpt)
    1202     {
    1203         if ( ! isset($Excerpt['text'][1]))
    1204         {
    1205             return;
    1206         }
    1207 
    1208         $marker = $Excerpt['text'][0];
    1209 
    1210         if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
    1211         {
    1212             $emphasis = 'strong';
    1213         }
    1214         elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
    1215         {
    1216             $emphasis = 'em';
    1217         }
    1218         else
    1219         {
    1220             return;
    1221         }
    1222 
    1223         return array(
    1224             'extent' => strlen($matches[0]),
    1225             'element' => array(
    1226                 'name' => $emphasis,
    1227                 'handler' => 'line',
    1228                 'text' => $matches[1],
    1229             ),
    1230         );
    1231     }
    1232 
    1233     protected function inlineEscapeSequence($Excerpt)
    1234     {
    1235         if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
    1236         {
    1237             return array(
    1238                 'markup' => $Excerpt['text'][1],
    1239                 'extent' => 2,
    1240             );
    1241         }
    1242     }
    1243 
    1244     protected function inlineImage($Excerpt)
    1245     {
    1246         if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
    1247         {
    1248             return;
    1249         }
    1250 
    1251         $Excerpt['text']= substr($Excerpt['text'], 1);
    1252 
    1253         $Link = $this->inlineLink($Excerpt);
    1254 
    1255         if ($Link === null)
    1256         {
    1257             return;
    1258         }
    1259 
    1260         $Inline = array(
    1261             'extent' => $Link['extent'] + 1,
    1262             'element' => array(
    1263                 'name' => 'img',
    1264                 'attributes' => array(
    1265                     'src' => $Link['element']['attributes']['href'],
    1266                     'alt' => $Link['element']['text'],
    1267                 ),
    1268             ),
    1269         );
    1270 
    1271         $Inline['element']['attributes'] += $Link['element']['attributes'];
    1272 
    1273         unset($Inline['element']['attributes']['href']);
    1274 
    1275         return $Inline;
    1276     }
    1277 
    1278     protected function inlineLink($Excerpt)
    1279     {
    1280         $Element = array(
    1281             'name' => 'a',
    1282             'handler' => 'line',
    1283             'nonNestables' => array('Url', 'Link'),
    1284             'text' => null,
    1285             'attributes' => array(
    1286                 'href' => null,
    1287                 'title' => null,
    1288             ),
    1289         );
    1290 
    1291         $extent = 0;
    1292 
    1293         $remainder = $Excerpt['text'];
    1294 
    1295         if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
    1296         {
    1297             $Element['text'] = $matches[1];
    1298 
    1299             $extent += strlen($matches[0]);
    1300 
    1301             $remainder = substr($remainder, $extent);
    1302         }
    1303         else
    1304         {
    1305             return;
    1306         }
    1307 
    1308         if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
    1309         {
    1310             $Element['attributes']['href'] = $matches[1];
    1311 
    1312             if (isset($matches[2]))
    1313             {
    1314                 $Element['attributes']['title'] = substr($matches[2], 1, - 1);
    1315             }
    1316 
    1317             $extent += strlen($matches[0]);
    1318         }
    1319         else
    1320         {
    1321             if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
    1322             {
    1323                 $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
    1324                 $definition = strtolower($definition);
    1325 
    1326                 $extent += strlen($matches[0]);
    1327             }
    1328             else
    1329             {
    1330                 $definition = strtolower($Element['text']);
    1331             }
    1332 
    1333             if ( ! isset($this->DefinitionData['Reference'][$definition]))
    1334             {
    1335                 return;
    1336             }
    1337 
    1338             $Definition = $this->DefinitionData['Reference'][$definition];
    1339 
    1340             $Element['attributes']['href'] = $Definition['url'];
    1341             $Element['attributes']['title'] = $Definition['title'];
    1342         }
    1343 
    1344         return array(
    1345             'extent' => $extent,
    1346             'element' => $Element,
    1347         );
    1348     }
    1349 
    1350     protected function inlineMarkup($Excerpt)
    1351     {
    1352         if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
    1353         {
    1354             return;
    1355         }
    1356 
    1357         if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
    1358         {
    1359             return array(
    1360                 'markup' => $matches[0],
    1361                 'extent' => strlen($matches[0]),
    1362             );
    1363         }
    1364 
    1365         if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
    1366         {
    1367             return array(
    1368                 'markup' => $matches[0],
    1369                 'extent' => strlen($matches[0]),
    1370             );
    1371         }
    1372 
    1373         if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
    1374         {
    1375             return array(
    1376                 'markup' => $matches[0],
    1377                 'extent' => strlen($matches[0]),
    1378             );
    1379         }
    1380     }
    1381 
    1382     protected function inlineSpecialCharacter($Excerpt)
    1383     {
    1384         if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
    1385         {
    1386             return array(
    1387                 'markup' => '&amp;',
    1388                 'extent' => 1,
    1389             );
    1390         }
    1391 
    1392         $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
    1393 
    1394         if (isset($SpecialCharacter[$Excerpt['text'][0]]))
    1395         {
    1396             return array(
    1397                 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
    1398                 'extent' => 1,
    1399             );
    1400         }
    1401     }
    1402 
    1403     protected function inlineStrikethrough($Excerpt)
    1404     {
    1405         if ( ! isset($Excerpt['text'][1]))
    1406         {
    1407             return;
    1408         }
    1409 
    1410         if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
    1411         {
    1412             return array(
    1413                 'extent' => strlen($matches[0]),
    1414                 'element' => array(
    1415                     'name' => 'del',
    1416                     'text' => $matches[1],
    1417                     'handler' => 'line',
    1418                 ),
    1419             );
    1420         }
    1421     }
    1422 
    1423     protected function inlineUrl($Excerpt)
    1424     {
    1425         if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
    1426         {
    1427             return;
    1428         }
    1429 
    1430         if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
    1431         {
    1432             $url = $matches[0][0];
    1433 
    1434             $Inline = array(
    1435                 'extent' => strlen($matches[0][0]),
    1436                 'position' => $matches[0][1],
    1437                 'element' => array(
    1438                     'name' => 'a',
    1439                     'text' => $url,
    1440                     'attributes' => array(
    1441                         'href' => $url,
    1442                     ),
    1443                 ),
    1444             );
    1445 
    1446             return $Inline;
    1447         }
    1448     }
    1449 
    1450     protected function inlineUrlTag($Excerpt)
    1451     {
    1452         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
    1453         {
    1454             $url = $matches[1];
    1455 
    1456             return array(
    1457                 'extent' => strlen($matches[0]),
    1458                 'element' => array(
    1459                     'name' => 'a',
    1460                     'text' => $url,
    1461                     'attributes' => array(
    1462                         'href' => $url,
    1463                     ),
    1464                 ),
    1465             );
    1466         }
    1467     }
    1468 
    1469     # ~
    1470 
    1471     protected function unmarkedText($text)
    1472     {
    1473         if ($this->breaksEnabled)
    1474         {
    1475             $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
    1476         }
    1477         else
    1478         {
    1479             $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
    1480             $text = str_replace(" \n", "\n", $text);
    1481         }
    1482 
    1483         return $text;
    1484     }
    1485 
    1486     #
    1487     # Handlers
    1488     #
    1489 
    1490     protected function element(array $Element)
    1491     {
    1492         if ($this->safeMode)
    1493         {
    1494             $Element = $this->sanitiseElement($Element);
    1495         }
    1496 
    1497         $markup = '<'.$Element['name'];
    1498 
    1499         if (isset($Element['attributes']))
    1500         {
    1501             foreach ($Element['attributes'] as $name => $value)
    1502             {
    1503                 if ($value === null)
    1504                 {
    1505                     continue;
    1506                 }
    1507 
    1508                 $markup .= ' '.$name.'="'.self::escape($value).'"';
    1509             }
    1510         }
    1511 
    1512         $permitRawHtml = false;
    1513 
    1514         if (isset($Element['text']))
    1515         {
    1516             $text = $Element['text'];
    1517         }
    1518         // very strongly consider an alternative if you're writing an
    1519         // extension
    1520         elseif (isset($Element['rawHtml']))
    1521         {
    1522             $text = $Element['rawHtml'];
    1523             $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
    1524             $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
    1525         }
    1526 
    1527         if (isset($text))
    1528         {
    1529             $markup .= '>';
    1530 
    1531             if (!isset($Element['nonNestables']))
    1532             {
    1533                 $Element['nonNestables'] = array();
    1534             }
    1535 
    1536             if (isset($Element['handler']))
    1537             {
    1538                 $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']);
    1539             }
    1540             elseif (!$permitRawHtml)
    1541             {
    1542                 $markup .= self::escape($text, true);
    1543             }
    1544             else
    1545             {
    1546                 $markup .= $text;
    1547             }
    1548 
    1549             $markup .= '</'.$Element['name'].'>';
    1550         }
    1551         else
    1552         {
    1553             $markup .= ' />';
    1554         }
    1555 
    1556         return $markup;
    1557     }
    1558 
    1559     protected function elements(array $Elements)
    1560     {
    1561         $markup = '';
    1562 
    1563         foreach ($Elements as $Element)
    1564         {
    1565             $markup .= "\n" . $this->element($Element);
    1566         }
    1567 
    1568         $markup .= "\n";
    1569 
    1570         return $markup;
    1571     }
    1572 
    1573     # ~
    1574 
    1575     protected function li($lines)
    1576     {
    1577         $markup = $this->lines($lines);
    1578 
    1579         $trimmedMarkup = trim($markup);
    1580 
    1581         if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
    1582         {
    1583             $markup = $trimmedMarkup;
    1584             $markup = substr($markup, 3);
    1585 
    1586             $position = strpos($markup, "</p>");
    1587 
    1588             $markup = substr_replace($markup, '', $position, 4);
    1589         }
    1590 
    1591         return $markup;
    1592     }
    1593 
    1594     #
    1595     # Deprecated Methods
    1596     #
    1597 
    1598     function parse($text)
    1599     {
    1600         $markup = $this->text($text);
    1601 
    1602         return $markup;
    1603     }
    1604 
    1605     protected function sanitiseElement(array $Element)
    1606     {
    1607         static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
    1608         static $safeUrlNameToAtt  = array(
    1609             'a'   => 'href',
    1610             'img' => 'src',
    1611         );
    1612 
    1613         if (isset($safeUrlNameToAtt[$Element['name']]))
    1614         {
    1615             $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
    1616         }
    1617 
    1618         if ( ! empty($Element['attributes']))
    1619         {
    1620             foreach ($Element['attributes'] as $att => $val)
    1621             {
    1622                 # filter out badly parsed attribute
    1623                 if ( ! preg_match($goodAttribute, $att))
    1624                 {
    1625                     unset($Element['attributes'][$att]);
    1626                 }
    1627                 # dump onevent attribute
    1628                 elseif (self::striAtStart($att, 'on'))
    1629                 {
    1630                     unset($Element['attributes'][$att]);
    1631                 }
    1632             }
    1633         }
    1634 
    1635         return $Element;
    1636     }
    1637 
    1638     protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
    1639     {
    1640         foreach ($this->safeLinksWhitelist as $scheme)
    1641         {
    1642             if (self::striAtStart($Element['attributes'][$attribute], $scheme))
    1643             {
    1644                 return $Element;
    1645             }
    1646         }
    1647 
    1648         $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
    1649 
    1650         return $Element;
    1651     }
    1652 
    1653     #
    1654     # Static Methods
    1655     #
    1656 
    1657     protected static function escape($text, $allowQuotes = false)
    1658     {
    1659         return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
    1660     }
    1661 
    1662     protected static function striAtStart($string, $needle)
    1663     {
    1664         $len = strlen($needle);
    1665 
    1666         if ($len > strlen($string))
    1667         {
    1668             return false;
    1669         }
    1670         else
    1671         {
    1672             return strtolower(substr($string, 0, $len)) === strtolower($needle);
    1673         }
    1674     }
    1675 
    1676     static function instance($name = 'default')
    1677     {
    1678         if (isset(self::$instances[$name]))
    1679         {
    1680             return self::$instances[$name];
    1681         }
    1682 
    1683         $instance = new static();
    1684 
    1685         self::$instances[$name] = $instance;
    1686 
    1687         return $instance;
    1688     }
    1689 
    1690     private static $instances = array();
    1691 
    1692     #
    1693     # Fields
    1694     #
    1695 
    1696     protected $DefinitionData;
    1697 
    1698     #
    1699     # Read-Only
    1700 
    1701     protected $specialCharacters = array(
    1702         '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
    1703     );
    1704 
    1705     protected $StrongRegex = array(
    1706         '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
    1707         '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
    1708     );
    1709 
    1710     protected $EmRegex = array(
    1711         '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
    1712         '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
    1713     );
    1714 
    1715     protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
    1716 
    1717     protected $voidElements = array(
    1718         'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
    1719     );
    1720 
    1721     protected $textLevelElements = array(
    1722         'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
    1723         'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
    1724         'i', 'rp', 'del', 'code',          'strike', 'marquee',
    1725         'q', 'rt', 'ins', 'font',          'strong',
    1726         's', 'tt', 'kbd', 'mark',
    1727         'u', 'xm', 'sub', 'nobr',
    1728                    'sup', 'ruby',
    1729                    'var', 'span',
    1730                    'wbr', 'time',
    1731     );
     544            $Block = array(
     545                'element' => array(
     546                    'name' => 'h' . min(6, $level),
     547                    'text' => $text,
     548                    'handler' => 'line',
     549                ),
     550            );
     551
     552            return $Block;
     553        }
     554    }
     555
     556    #
     557    # List
     558
     559    protected function blockList($Line)
     560    {
     561        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
     562
     563        if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
     564        {
     565            $Block = array(
     566                'indent' => $Line['indent'],
     567                'pattern' => $pattern,
     568                'element' => array(
     569                    'name' => $name,
     570                    'handler' => 'elements',
     571                ),
     572            );
     573
     574            if($name === 'ol')
     575            {
     576                $listStart = stristr($matches[0], '.', true);
     577
     578                if($listStart !== '1')
     579                {
     580                    $Block['element']['attributes'] = array('start' => $listStart);
     581                }
     582            }
     583
     584            $Block['li'] = array(
     585                'name' => 'li',
     586                'handler' => 'li',
     587                'text' => array(
     588                    $matches[2],
     589                ),
     590            );
     591
     592            $Block['element']['text'] []= & $Block['li'];
     593
     594            return $Block;
     595        }
     596    }
     597
     598    protected function blockListContinue($Line, array $Block)
     599    {
     600        if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
     601        {
     602            if (isset($Block['interrupted']))
     603            {
     604                $Block['li']['text'] []= '';
     605
     606                $Block['loose'] = true;
     607
     608                unset($Block['interrupted']);
     609            }
     610
     611            unset($Block['li']);
     612
     613            $text = isset($matches[1]) ? $matches[1] : '';
     614
     615            $Block['li'] = array(
     616                'name' => 'li',
     617                'handler' => 'li',
     618                'text' => array(
     619                    $text,
     620                ),
     621            );
     622
     623            $Block['element']['text'] []= & $Block['li'];
     624
     625            return $Block;
     626        }
     627
     628        if ($Line['text'][0] === '[' and $this->blockReference($Line))
     629        {
     630            return $Block;
     631        }
     632
     633        if ( ! isset($Block['interrupted']))
     634        {
     635            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
     636
     637            $Block['li']['text'] []= $text;
     638
     639            return $Block;
     640        }
     641
     642        if ($Line['indent'] > 0)
     643        {
     644            $Block['li']['text'] []= '';
     645
     646            $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
     647
     648            $Block['li']['text'] []= $text;
     649
     650            unset($Block['interrupted']);
     651
     652            return $Block;
     653        }
     654    }
     655
     656    protected function blockListComplete(array $Block)
     657    {
     658        if (isset($Block['loose']))
     659        {
     660            foreach ($Block['element']['text'] as &$li)
     661            {
     662                if (end($li['text']) !== '')
     663                {
     664                    $li['text'] []= '';
     665                }
     666            }
     667        }
     668
     669        return $Block;
     670    }
     671
     672    #
     673    # Quote
     674
     675    protected function blockQuote($Line)
     676    {
     677        if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
     678        {
     679            $Block = array(
     680                'element' => array(
     681                    'name' => 'blockquote',
     682                    'handler' => 'lines',
     683                    'text' => (array) $matches[1],
     684                ),
     685            );
     686
     687            return $Block;
     688        }
     689    }
     690
     691    protected function blockQuoteContinue($Line, array $Block)
     692    {
     693        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
     694        {
     695            if (isset($Block['interrupted']))
     696            {
     697                $Block['element']['text'] []= '';
     698
     699                unset($Block['interrupted']);
     700            }
     701
     702            $Block['element']['text'] []= $matches[1];
     703
     704            return $Block;
     705        }
     706
     707        if ( ! isset($Block['interrupted']))
     708        {
     709            $Block['element']['text'] []= $Line['text'];
     710
     711            return $Block;
     712        }
     713    }
     714
     715    #
     716    # Rule
     717
     718    protected function blockRule($Line)
     719    {
     720        if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
     721        {
     722            $Block = array(
     723                'element' => array(
     724                    'name' => 'hr'
     725                ),
     726            );
     727
     728            return $Block;
     729        }
     730    }
     731
     732    #
     733    # Setext
     734
     735    protected function blockSetextHeader($Line, ?array $Block = null)
     736    {
     737        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
     738        {
     739            return;
     740        }
     741
     742        if (chop($Line['text'], $Line['text'][0]) === '')
     743        {
     744            $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
     745
     746            return $Block;
     747        }
     748    }
     749
     750    #
     751    # Markup
     752
     753    protected function blockMarkup($Line)
     754    {
     755        if ($this->markupEscaped or $this->safeMode)
     756        {
     757            return;
     758        }
     759
     760        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
     761        {
     762            $element = strtolower($matches[1]);
     763
     764            if (in_array($element, $this->textLevelElements))
     765            {
     766                return;
     767            }
     768
     769            $Block = array(
     770                'name' => $matches[1],
     771                'depth' => 0,
     772                'markup' => $Line['text'],
     773            );
     774
     775            $length = strlen($matches[0]);
     776
     777            $remainder = substr($Line['text'], $length);
     778
     779            if (trim($remainder) === '')
     780            {
     781                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
     782                {
     783                    $Block['closed'] = true;
     784
     785                    $Block['void'] = true;
     786                }
     787            }
     788            else
     789            {
     790                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
     791                {
     792                    return;
     793                }
     794
     795                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
     796                {
     797                    $Block['closed'] = true;
     798                }
     799            }
     800
     801            return $Block;
     802        }
     803    }
     804
     805    protected function blockMarkupContinue($Line, array $Block)
     806    {
     807        if (isset($Block['closed']))
     808        {
     809            return;
     810        }
     811
     812        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
     813        {
     814            $Block['depth'] ++;
     815        }
     816
     817        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
     818        {
     819            if ($Block['depth'] > 0)
     820            {
     821                $Block['depth'] --;
     822            }
     823            else
     824            {
     825                $Block['closed'] = true;
     826            }
     827        }
     828
     829        if (isset($Block['interrupted']))
     830        {
     831            $Block['markup'] .= "\n";
     832
     833            unset($Block['interrupted']);
     834        }
     835
     836        $Block['markup'] .= "\n".$Line['body'];
     837
     838        return $Block;
     839    }
     840
     841    #
     842    # Reference
     843
     844    protected function blockReference($Line)
     845    {
     846        if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
     847        {
     848            $id = strtolower($matches[1]);
     849
     850            $Data = array(
     851                'url' => $matches[2],
     852                'title' => null,
     853            );
     854
     855            if (isset($matches[3]))
     856            {
     857                $Data['title'] = $matches[3];
     858            }
     859
     860            $this->DefinitionData['Reference'][$id] = $Data;
     861
     862            $Block = array(
     863                'hidden' => true,
     864            );
     865
     866            return $Block;
     867        }
     868    }
     869
     870    #
     871    # Table
     872
     873    protected function blockTable($Line, ?array $Block = null)
     874    {
     875        if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
     876        {
     877            return;
     878        }
     879
     880        if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
     881        {
     882            $alignments = array();
     883
     884            $divider = $Line['text'];
     885
     886            $divider = trim($divider);
     887            $divider = trim($divider, '|');
     888
     889            $dividerCells = explode('|', $divider);
     890
     891            foreach ($dividerCells as $dividerCell)
     892            {
     893                $dividerCell = trim($dividerCell);
     894
     895                if ($dividerCell === '')
     896                {
     897                    continue;
     898                }
     899
     900                $alignment = null;
     901
     902                if ($dividerCell[0] === ':')
     903                {
     904                    $alignment = 'left';
     905                }
     906
     907                if (substr($dividerCell, - 1) === ':')
     908                {
     909                    $alignment = $alignment === 'left' ? 'center' : 'right';
     910                }
     911
     912                $alignments []= $alignment;
     913            }
     914
     915            # ~
     916
     917            $HeaderElements = array();
     918
     919            $header = $Block['element']['text'];
     920
     921            $header = trim($header);
     922            $header = trim($header, '|');
     923
     924            $headerCells = explode('|', $header);
     925
     926            foreach ($headerCells as $index => $headerCell)
     927            {
     928                $headerCell = trim($headerCell);
     929
     930                $HeaderElement = array(
     931                    'name' => 'th',
     932                    'text' => $headerCell,
     933                    'handler' => 'line',
     934                );
     935
     936                if (isset($alignments[$index]))
     937                {
     938                    $alignment = $alignments[$index];
     939
     940                    $HeaderElement['attributes'] = array(
     941                        'style' => 'text-align: '.$alignment.';',
     942                    );
     943                }
     944
     945                $HeaderElements []= $HeaderElement;
     946            }
     947
     948            # ~
     949
     950            $Block = array(
     951                'alignments' => $alignments,
     952                'identified' => true,
     953                'element' => array(
     954                    'name' => 'table',
     955                    'handler' => 'elements',
     956                ),
     957            );
     958
     959            $Block['element']['text'] []= array(
     960                'name' => 'thead',
     961                'handler' => 'elements',
     962            );
     963
     964            $Block['element']['text'] []= array(
     965                'name' => 'tbody',
     966                'handler' => 'elements',
     967                'text' => array(),
     968            );
     969
     970            $Block['element']['text'][0]['text'] []= array(
     971                'name' => 'tr',
     972                'handler' => 'elements',
     973                'text' => $HeaderElements,
     974            );
     975
     976            return $Block;
     977        }
     978    }
     979
     980    protected function blockTableContinue($Line, array $Block)
     981    {
     982        if (isset($Block['interrupted']))
     983        {
     984            return;
     985        }
     986
     987        if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
     988        {
     989            $Elements = array();
     990
     991            $row = $Line['text'];
     992
     993            $row = trim($row);
     994            $row = trim($row, '|');
     995
     996            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
     997
     998            foreach ($matches[0] as $index => $cell)
     999            {
     1000                $cell = trim($cell);
     1001
     1002                $Element = array(
     1003                    'name' => 'td',
     1004                    'handler' => 'line',
     1005                    'text' => $cell,
     1006                );
     1007
     1008                if (isset($Block['alignments'][$index]))
     1009                {
     1010                    $Element['attributes'] = array(
     1011                        'style' => 'text-align: '.$Block['alignments'][$index].';',
     1012                    );
     1013                }
     1014
     1015                $Elements []= $Element;
     1016            }
     1017
     1018            $Element = array(
     1019                'name' => 'tr',
     1020                'handler' => 'elements',
     1021                'text' => $Elements,
     1022            );
     1023
     1024            $Block['element']['text'][1]['text'] []= $Element;
     1025
     1026            return $Block;
     1027        }
     1028    }
     1029
     1030    #
     1031    # ~
     1032    #
     1033
     1034    protected function paragraph($Line)
     1035    {
     1036        $Block = array(
     1037            'element' => array(
     1038                'name' => 'p',
     1039                'text' => $Line['text'],
     1040                'handler' => 'line',
     1041            ),
     1042        );
     1043
     1044        return $Block;
     1045    }
     1046
     1047    #
     1048    # Inline Elements
     1049    #
     1050
     1051    protected $InlineTypes = array(
     1052        '"' => array('SpecialCharacter'),
     1053        '!' => array('Image'),
     1054        '&' => array('SpecialCharacter'),
     1055        '*' => array('Emphasis'),
     1056        ':' => array('Url'),
     1057        '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
     1058        '>' => array('SpecialCharacter'),
     1059        '[' => array('Link'),
     1060        '_' => array('Emphasis'),
     1061        '`' => array('Code'),
     1062        '~' => array('Strikethrough'),
     1063        '\\' => array('EscapeSequence'),
     1064    );
     1065
     1066    # ~
     1067
     1068    protected $inlineMarkerList = '!"*_&[:<>`~\\';
     1069
     1070    #
     1071    # ~
     1072    #
     1073
     1074    public function line($text, $nonNestables=array())
     1075    {
     1076        $markup = '';
     1077
     1078        # $excerpt is based on the first occurrence of a marker
     1079
     1080        while ($excerpt = strpbrk($text, $this->inlineMarkerList))
     1081        {
     1082            $marker = $excerpt[0];
     1083
     1084            $markerPosition = strpos($text, $marker);
     1085
     1086            $Excerpt = array('text' => $excerpt, 'context' => $text);
     1087
     1088            foreach ($this->InlineTypes[$marker] as $inlineType)
     1089            {
     1090                # check to see if the current inline type is nestable in the current context
     1091
     1092                if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
     1093                {
     1094                    continue;
     1095                }
     1096
     1097                $Inline = $this->{'inline'.$inlineType}($Excerpt);
     1098
     1099                if ( ! isset($Inline))
     1100                {
     1101                    continue;
     1102                }
     1103
     1104                # makes sure that the inline belongs to "our" marker
     1105
     1106                if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
     1107                {
     1108                    continue;
     1109                }
     1110
     1111                # sets a default inline position
     1112
     1113                if ( ! isset($Inline['position']))
     1114                {
     1115                    $Inline['position'] = $markerPosition;
     1116                }
     1117
     1118                # cause the new element to 'inherit' our non nestables
     1119
     1120                foreach ($nonNestables as $non_nestable)
     1121                {
     1122                    $Inline['element']['nonNestables'][] = $non_nestable;
     1123                }
     1124
     1125                # the text that comes before the inline
     1126                $unmarkedText = substr($text, 0, $Inline['position']);
     1127
     1128                # compile the unmarked text
     1129                $markup .= $this->unmarkedText($unmarkedText);
     1130
     1131                # compile the inline
     1132                $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
     1133
     1134                # remove the examined text
     1135                $text = substr($text, $Inline['position'] + $Inline['extent']);
     1136
     1137                continue 2;
     1138            }
     1139
     1140            # the marker does not belong to an inline
     1141
     1142            $unmarkedText = substr($text, 0, $markerPosition + 1);
     1143
     1144            $markup .= $this->unmarkedText($unmarkedText);
     1145
     1146            $text = substr($text, $markerPosition + 1);
     1147        }
     1148
     1149        $markup .= $this->unmarkedText($text);
     1150
     1151        return $markup;
     1152    }
     1153
     1154    #
     1155    # ~
     1156    #
     1157
     1158    protected function inlineCode($Excerpt)
     1159    {
     1160        $marker = $Excerpt['text'][0];
     1161
     1162        if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
     1163        {
     1164            $text = $matches[2];
     1165            $text = preg_replace("/[ ]*\n/", ' ', $text);
     1166
     1167            return array(
     1168                'extent' => strlen($matches[0]),
     1169                'element' => array(
     1170                    'name' => 'code',
     1171                    'text' => $text,
     1172                ),
     1173            );
     1174        }
     1175    }
     1176
     1177    protected function inlineEmailTag($Excerpt)
     1178    {
     1179        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
     1180        {
     1181            $url = $matches[1];
     1182
     1183            if ( ! isset($matches[2]))
     1184            {
     1185                $url = 'mailto:' . $url;
     1186            }
     1187
     1188            return array(
     1189                'extent' => strlen($matches[0]),
     1190                'element' => array(
     1191                    'name' => 'a',
     1192                    'text' => $matches[1],
     1193                    'attributes' => array(
     1194                        'href' => $url,
     1195                    ),
     1196                ),
     1197            );
     1198        }
     1199    }
     1200
     1201    protected function inlineEmphasis($Excerpt)
     1202    {
     1203        if ( ! isset($Excerpt['text'][1]))
     1204        {
     1205            return;
     1206        }
     1207
     1208        $marker = $Excerpt['text'][0];
     1209
     1210        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
     1211        {
     1212            $emphasis = 'strong';
     1213        }
     1214        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
     1215        {
     1216            $emphasis = 'em';
     1217        }
     1218        else
     1219        {
     1220            return;
     1221        }
     1222
     1223        return array(
     1224            'extent' => strlen($matches[0]),
     1225            'element' => array(
     1226                'name' => $emphasis,
     1227                'handler' => 'line',
     1228                'text' => $matches[1],
     1229            ),
     1230        );
     1231    }
     1232
     1233    protected function inlineEscapeSequence($Excerpt)
     1234    {
     1235        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
     1236        {
     1237            return array(
     1238                'markup' => $Excerpt['text'][1],
     1239                'extent' => 2,
     1240            );
     1241        }
     1242    }
     1243
     1244    protected function inlineImage($Excerpt)
     1245    {
     1246        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
     1247        {
     1248            return;
     1249        }
     1250
     1251        $Excerpt['text']= substr($Excerpt['text'], 1);
     1252
     1253        $Link = $this->inlineLink($Excerpt);
     1254
     1255        if ($Link === null)
     1256        {
     1257            return;
     1258        }
     1259
     1260        $Inline = array(
     1261            'extent' => $Link['extent'] + 1,
     1262            'element' => array(
     1263                'name' => 'img',
     1264                'attributes' => array(
     1265                    'src' => $Link['element']['attributes']['href'],
     1266                    'alt' => $Link['element']['text'],
     1267                ),
     1268            ),
     1269        );
     1270
     1271        $Inline['element']['attributes'] += $Link['element']['attributes'];
     1272
     1273        unset($Inline['element']['attributes']['href']);
     1274
     1275        return $Inline;
     1276    }
     1277
     1278    protected function inlineLink($Excerpt)
     1279    {
     1280        $Element = array(
     1281            'name' => 'a',
     1282            'handler' => 'line',
     1283            'nonNestables' => array('Url', 'Link'),
     1284            'text' => null,
     1285            'attributes' => array(
     1286                'href' => null,
     1287                'title' => null,
     1288            ),
     1289        );
     1290
     1291        $extent = 0;
     1292
     1293        $remainder = $Excerpt['text'];
     1294
     1295        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
     1296        {
     1297            $Element['text'] = $matches[1];
     1298
     1299            $extent += strlen($matches[0]);
     1300
     1301            $remainder = substr($remainder, $extent);
     1302        }
     1303        else
     1304        {
     1305            return;
     1306        }
     1307
     1308        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
     1309        {
     1310            $Element['attributes']['href'] = $matches[1];
     1311
     1312            if (isset($matches[2]))
     1313            {
     1314                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
     1315            }
     1316
     1317            $extent += strlen($matches[0]);
     1318        }
     1319        else
     1320        {
     1321            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
     1322            {
     1323                $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
     1324                $definition = strtolower($definition);
     1325
     1326                $extent += strlen($matches[0]);
     1327            }
     1328            else
     1329            {
     1330                $definition = strtolower($Element['text']);
     1331            }
     1332
     1333            if ( ! isset($this->DefinitionData['Reference'][$definition]))
     1334            {
     1335                return;
     1336            }
     1337
     1338            $Definition = $this->DefinitionData['Reference'][$definition];
     1339
     1340            $Element['attributes']['href'] = $Definition['url'];
     1341            $Element['attributes']['title'] = $Definition['title'];
     1342        }
     1343
     1344        return array(
     1345            'extent' => $extent,
     1346            'element' => $Element,
     1347        );
     1348    }
     1349
     1350    protected function inlineMarkup($Excerpt)
     1351    {
     1352        if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
     1353        {
     1354            return;
     1355        }
     1356
     1357        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
     1358        {
     1359            return array(
     1360                'markup' => $matches[0],
     1361                'extent' => strlen($matches[0]),
     1362            );
     1363        }
     1364
     1365        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
     1366        {
     1367            return array(
     1368                'markup' => $matches[0],
     1369                'extent' => strlen($matches[0]),
     1370            );
     1371        }
     1372
     1373        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
     1374        {
     1375            return array(
     1376                'markup' => $matches[0],
     1377                'extent' => strlen($matches[0]),
     1378            );
     1379        }
     1380    }
     1381
     1382    protected function inlineSpecialCharacter($Excerpt)
     1383    {
     1384        if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
     1385        {
     1386            return array(
     1387                'markup' => '&amp;',
     1388                'extent' => 1,
     1389            );
     1390        }
     1391
     1392        $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
     1393
     1394        if (isset($SpecialCharacter[$Excerpt['text'][0]]))
     1395        {
     1396            return array(
     1397                'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
     1398                'extent' => 1,
     1399            );
     1400        }
     1401    }
     1402
     1403    protected function inlineStrikethrough($Excerpt)
     1404    {
     1405        if ( ! isset($Excerpt['text'][1]))
     1406        {
     1407            return;
     1408        }
     1409
     1410        if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
     1411        {
     1412            return array(
     1413                'extent' => strlen($matches[0]),
     1414                'element' => array(
     1415                    'name' => 'del',
     1416                    'text' => $matches[1],
     1417                    'handler' => 'line',
     1418                ),
     1419            );
     1420        }
     1421    }
     1422
     1423    protected function inlineUrl($Excerpt)
     1424    {
     1425        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
     1426        {
     1427            return;
     1428        }
     1429
     1430        if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
     1431        {
     1432            $url = $matches[0][0];
     1433
     1434            $Inline = array(
     1435                'extent' => strlen($matches[0][0]),
     1436                'position' => $matches[0][1],
     1437                'element' => array(
     1438                    'name' => 'a',
     1439                    'text' => $url,
     1440                    'attributes' => array(
     1441                        'href' => $url,
     1442                    ),
     1443                ),
     1444            );
     1445
     1446            return $Inline;
     1447        }
     1448    }
     1449
     1450    protected function inlineUrlTag($Excerpt)
     1451    {
     1452        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
     1453        {
     1454            $url = $matches[1];
     1455
     1456            return array(
     1457                'extent' => strlen($matches[0]),
     1458                'element' => array(
     1459                    'name' => 'a',
     1460                    'text' => $url,
     1461                    'attributes' => array(
     1462                        'href' => $url,
     1463                    ),
     1464                ),
     1465            );
     1466        }
     1467    }
     1468
     1469    # ~
     1470
     1471    protected function unmarkedText($text)
     1472    {
     1473        if ($this->breaksEnabled)
     1474        {
     1475            $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
     1476        }
     1477        else
     1478        {
     1479            $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
     1480            $text = str_replace(" \n", "\n", $text);
     1481        }
     1482
     1483        return $text;
     1484    }
     1485
     1486    #
     1487    # Handlers
     1488    #
     1489
     1490    protected function element(array $Element)
     1491    {
     1492        if ($this->safeMode)
     1493        {
     1494            $Element = $this->sanitiseElement($Element);
     1495        }
     1496
     1497        $markup = '<'.$Element['name'];
     1498
     1499        if (isset($Element['attributes']))
     1500        {
     1501            foreach ($Element['attributes'] as $name => $value)
     1502            {
     1503                if ($value === null)
     1504                {
     1505                    continue;
     1506                }
     1507
     1508                $markup .= ' '.$name.'="'.self::escape($value).'"';
     1509            }
     1510        }
     1511
     1512        $permitRawHtml = false;
     1513
     1514        if (isset($Element['text']))
     1515        {
     1516            $text = $Element['text'];
     1517        }
     1518        // very strongly consider an alternative if you're writing an
     1519        // extension
     1520        elseif (isset($Element['rawHtml']))
     1521        {
     1522            $text = $Element['rawHtml'];
     1523            $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
     1524            $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
     1525        }
     1526
     1527        if (isset($text))
     1528        {
     1529
     1530            if (!isset($Element['nonNestables']))
     1531            {
     1532                $Element['nonNestables'] = array();
     1533            }
     1534
     1535            if (isset($Element['handler']))
     1536            {
     1537                $tmp_markup = $this->{$Element['handler']}($text, $Element['nonNestables']);
     1538                if ( $Element['name'] !== 'li' || ! preg_match( '#^\s*\[[x\s]{1}\]#', $tmp_markup ) )
     1539                {
     1540                    $markup .= '>';
     1541                    $markup .= $tmp_markup;
     1542                }
     1543                else
     1544                {
     1545                    $markup .= ' style="margin-left:-1.25em;list-style-type:none">';
     1546                    $tmp_markup = preg_replace( '#^\[\s\]#', '<input type="checkbox" disabled="disabled" />', $tmp_markup );
     1547                    $markup .= preg_replace( '#^\[x\]#', '<input type="checkbox" disabled="disabled" checked="checked" />', $tmp_markup );
     1548                }
     1549            }
     1550            elseif (!$permitRawHtml)
     1551            {
     1552                $markup .= '>';
     1553                $markup .= self::escape($text, true);
     1554            }
     1555            else
     1556            {
     1557                $markup .= '>';
     1558                $markup .= $text;
     1559            }
     1560
     1561            $markup .= '</'.$Element['name'].'>';
     1562        }
     1563        else
     1564        {
     1565            $markup .= ' />';
     1566        }
     1567
     1568        return $markup;
     1569    }
     1570
     1571    protected function elements(array $Elements)
     1572    {
     1573        $markup = '';
     1574
     1575        foreach ($Elements as $Element)
     1576        {
     1577            $markup .= "\n" . $this->element($Element);
     1578        }
     1579
     1580        $markup .= "\n";
     1581
     1582        return $markup;
     1583    }
     1584
     1585    # ~
     1586
     1587    protected function li($lines)
     1588    {
     1589        $markup = $this->lines($lines);
     1590
     1591        $trimmedMarkup = trim($markup);
     1592
     1593        if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
     1594        {
     1595            $markup = $trimmedMarkup;
     1596            $markup = substr($markup, 3);
     1597
     1598            $position = strpos($markup, "</p>");
     1599
     1600            $markup = substr_replace($markup, '', $position, 4);
     1601        }
     1602
     1603        return $markup;
     1604    }
     1605
     1606    #
     1607    # Deprecated Methods
     1608    #
     1609
     1610    function parse($text)
     1611    {
     1612        $markup = $this->text($text);
     1613
     1614        return $markup;
     1615    }
     1616
     1617    protected function sanitiseElement(array $Element)
     1618    {
     1619        static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
     1620        static $safeUrlNameToAtt  = array(
     1621            'a'   => 'href',
     1622            'img' => 'src',
     1623        );
     1624
     1625        if (isset($safeUrlNameToAtt[$Element['name']]))
     1626        {
     1627            $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
     1628        }
     1629
     1630        if ( ! empty($Element['attributes']))
     1631        {
     1632            foreach ($Element['attributes'] as $att => $val)
     1633            {
     1634                # filter out badly parsed attribute
     1635                if ( ! preg_match($goodAttribute, $att))
     1636                {
     1637                    unset($Element['attributes'][$att]);
     1638                }
     1639                # dump onevent attribute
     1640                elseif (self::striAtStart($att, 'on'))
     1641                {
     1642                    unset($Element['attributes'][$att]);
     1643                }
     1644            }
     1645        }
     1646
     1647        return $Element;
     1648    }
     1649
     1650    protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
     1651    {
     1652        foreach ($this->safeLinksWhitelist as $scheme)
     1653        {
     1654            if (self::striAtStart($Element['attributes'][$attribute], $scheme))
     1655            {
     1656                return $Element;
     1657            }
     1658        }
     1659
     1660        $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
     1661
     1662        return $Element;
     1663    }
     1664
     1665    #
     1666    # Static Methods
     1667    #
     1668
     1669    protected static function escape($text, $allowQuotes = false)
     1670    {
     1671        return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
     1672    }
     1673
     1674    protected static function striAtStart($string, $needle)
     1675    {
     1676        $len = strlen($needle);
     1677
     1678        if ($len > strlen($string))
     1679        {
     1680            return false;
     1681        }
     1682        else
     1683        {
     1684            return strtolower(substr($string, 0, $len)) === strtolower($needle);
     1685        }
     1686    }
     1687
     1688    static function instance($name = 'default')
     1689    {
     1690        if (isset(self::$instances[$name]))
     1691        {
     1692            return self::$instances[$name];
     1693        }
     1694
     1695        $instance = new static();
     1696
     1697        self::$instances[$name] = $instance;
     1698
     1699        return $instance;
     1700    }
     1701
     1702    private static $instances = array();
     1703
     1704    #
     1705    # Fields
     1706    #
     1707
     1708    protected $DefinitionData;
     1709
     1710    #
     1711    # Read-Only
     1712
     1713    protected $specialCharacters = array(
     1714        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
     1715    );
     1716
     1717    protected $StrongRegex = array(
     1718        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
     1719        '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
     1720    );
     1721
     1722    protected $EmRegex = array(
     1723        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
     1724        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
     1725    );
     1726
     1727    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
     1728
     1729    protected $voidElements = array(
     1730        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
     1731    );
     1732
     1733    protected $textLevelElements = array(
     1734        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
     1735        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
     1736        'i', 'rp', 'del', 'code',         'strike', 'marquee',
     1737        'q', 'rt', 'ins', 'font',         'strong',
     1738        's', 'tt', 'kbd', 'mark',
     1739        'u', 'xm', 'sub', 'nobr',
     1740                   'sup', 'ruby',
     1741                   'var', 'span',
     1742                   'wbr', 'time',
     1743    );
    17321744}
  • markup-markdown/trunk/markup-markdown.php

    r3392623 r3393327  
    66 * Plugin URI:  https://www.markup-markdown.com
    77 * Description: Replaces the Gutenberg Block Editor in favor of pure markdown based markups
    8  * Version:     3.23.0
     8 * Version:     3.24.0
    99 * Author:      Pierre-Henri Lavigne
    1010 * Author URI:  https://www.markup-markdown.com
     
    3434
    3535        protected $settings = array(
    36             'version' => '3.23.0',
     36            'version' => '3.24.0',
    3737            'plugin_uri' => '',
    3838            'plugin_dir' => '',
  • markup-markdown/trunk/readme.txt

    r3392623 r3393327  
    11=== Markup Markdown ===
    22Tags: Editor, Markdown
    3 Stable Tag: 3.23.0
    4 Version: 3.23.0
     3Stable Tag: 3.24.0
     4Version: 3.24.0
    55Requires at least: 6.6
    66Tested up to: 6.8
     
    6969
    7070== Changelog ==
     71
     72= 3.24.0 =
     73
     74Improvement:
     75- Adding native basic support for TODO list items
    7176
    7277= 3.23.0 =
Note: See TracChangeset for help on using the changeset viewer.