Changeset 82489 in spip-zone


Ignore:
Timestamp:
May 17, 2014, 10:24:41 AM (5 years ago)
Author:
cedric@…
Message:

upgrade Parsedown

Location:
_plugins_/markdown/branches/v0.12.0
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • _plugins_/markdown/branches/v0.12.0/lib/parsedown/Parsedown.php

    r82481 r82489  
    99# http://erusev.com
    1010#
    11 # For the full license information, please view the LICENSE file that was
    12 # distributed with this source code.
     11# For the full license information, view the LICENSE file that was distributed
     12# with this source code.
    1313#
    1414#
     
    1616class Parsedown
    1717{
    18         #
    19         # Multiton (http://en.wikipedia.org/wiki/Multiton_pattern)
    20         #
    21 
    22         static function instance($name = 'default')
    23         {
    24                 if (isset(self::$instances[$name]))
    25                         return self::$instances[$name];
    26 
    27                 $instance = new Parsedown();
    28 
    29                 self::$instances[$name] = $instance;
    30 
    31                 return $instance;
    32         }
    33 
    34         private static $instances = array();
    35 
    36         #
    37         # Setters
    38         #
    39 
    40         private $break_marker = "  \n";
    41 
    42         function set_breaks_enabled($breaks_enabled)
    43         {
    44                 $this->break_marker = $breaks_enabled ? "\n" : "  \n";
    45 
    46                 return $this;
    47         }
    48 
    49         #
    50         # Fields
    51         #
    52 
    53         private $reference_map = array();
    54         private $escape_sequence_map = array();
    55 
    56         #
    57         # Public Methods
    58         #
    59 
    60         function parse($text)
    61         {
    62                 # removes UTF-8 BOM and marker characters
    63                 $text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
    64 
    65                 # removes \r characters
    66                 $text = str_replace("\r\n", "\n", $text);
    67                 $text = str_replace("\r", "\n", $text);
    68 
    69                 # replaces tabs with spaces
    70                 $text = str_replace("\t", '    ', $text);
    71 
    72                 # encodes escape sequences
    73 
    74                 if (strpos($text, '\\') !== FALSE)
    75                 {
    76                         $escape_sequences = array('\\\\', '\`', '\*', '\_', '\{', '\}', '\[', '\]', '\(', '\)', '\>', '\#', '\+', '\-', '\.', '\!');
    77 
    78                         foreach ($escape_sequences as $index => $escape_sequence)
    79                         {
    80                                 if (strpos($text, $escape_sequence) !== FALSE)
    81                                 {
    82                                         $code = "\x1A".'\\'.$index.';';
    83 
    84                                         $text = str_replace($escape_sequence, $code, $text);
    85 
    86                                         $this->escape_sequence_map[$code] = $escape_sequence;
    87                                 }
    88                         }
    89                 }
    90 
    91                 # ~
    92 
    93                 $text = preg_replace('/\n\s*\n/', "\n\n", $text);
    94                 $text = trim($text, "\n");
    95 
    96                 $lines = explode("\n", $text);
    97 
    98                 $text = $this->parse_block_elements($lines);
    99 
    100                 # decodes escape sequences
    101 
    102                 foreach ($this->escape_sequence_map as $code => $escape_sequence)
    103                 {
    104                         $text = str_replace($code, $escape_sequence[1], $text);
    105                 }
    106 
    107                 # ~
    108 
    109                 $text = rtrim($text, "\n");
    110 
    111                 return $text;
    112         }
    113 
    114         #
    115         # Private Methods
    116         #
    117 
    118         private function parse_block_elements(array $lines, $context = '')
    119         {
    120                 $elements = array();
    121 
    122                 $element = array(
    123                         'type' => '',
    124                 );
    125 
    126                 foreach ($lines as $line)
    127                 {
    128                         # fenced elements
    129 
    130                         switch ($element['type'])
    131                         {
    132                                 case 'fenced_code_block':
    133 
    134                                         if ( ! isset($element['closed']))
    135                                         {
    136                                                 if (preg_match('/^[ ]*'.$element['fence'][0].'{3,}[ ]*$/', $line))
    137                                                 {
    138                                                         $element['closed'] = true;
    139                                                 }
    140                                                 else
    141                                                 {
    142                                                         $element['text'] !== '' and $element['text'] .= "\n";
    143 
    144                                                         $element['text'] .= $line;
    145                                                 }
    146 
    147                                                 continue 2;
    148                                         }
    149 
    150                                         break;
    151 
    152                                 case 'markup':
    153 
    154                                         if ( ! isset($element['closed']))
    155                                         {
    156                                                 if (preg_match('{<'.$element['subtype'].'>$}', $line)) # opening tag
    157                                                 {
    158                                                         $element['depth']++;
    159                                                 }
    160 
    161                                                 if (preg_match('{</'.$element['subtype'].'>$}', $line)) # closing tag
    162                                                 {
    163                                                         $element['depth'] > 0
    164                                                                 ? $element['depth']--
    165                                                                 : $element['closed'] = true;
    166                                                 }
    167 
    168                                                 $element['text'] .= "\n".$line;
    169 
    170                                                 continue 2;
    171                                         }
    172 
    173                                         break;
    174                         }
    175 
    176                         # *
    177 
    178                         if ($line === '')
    179                         {
    180                                 $element['interrupted'] = true;
    181 
    182                                 continue;
    183                         }
    184 
    185                         # composite elements
    186 
    187                         switch ($element['type'])
    188                         {
    189                                 case 'blockquote':
    190 
    191                                         if ( ! isset($element['interrupted']))
    192                                         {
    193                                                 $line = preg_replace('/^[ ]*>[ ]?/', '', $line);
    194 
    195                                                 $element['lines'] []= $line;
    196 
    197                                                 continue 2;
    198                                         }
    199 
    200                                         break;
    201 
    202                                 case 'li':
    203 
    204                                         if (preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches))
    205                                         {
    206                                                 if ($element['indentation'] !== $matches[1])
    207                                                 {
    208                                                         $element['lines'] []= $line;
    209                                                 }
    210                                                 else
    211                                                 {
    212                                                         unset($element['last']);
    213 
    214                                                         $elements []= $element;
    215 
    216                                                         $element = array(
    217                                                                 'type' => 'li',
    218                                                                 'indentation' => $matches[1],
    219                                                                 'last' => true,
    220                                                                 'lines' => array(
    221                                                                         preg_replace('/^[ ]{0,4}/', '', $matches[3]),
    222                                                                 ),
    223                                                         );
    224                                                 }
    225 
    226                                                 continue 2;
    227                                         }
    228 
    229                                         if (isset($element['interrupted']))
    230                                         {
    231                                                 if ($line[0] === ' ')
    232                                                 {
    233                                                         $element['lines'] []= '';
    234 
    235                                                         $line = preg_replace('/^[ ]{0,4}/', '', $line);
    236 
    237                                                         $element['lines'] []= $line;
    238 
    239                                                         unset($element['interrupted']);
    240 
    241                                                         continue 2;
    242                                                 }
    243                                         }
    244                                         else
    245                                         {
    246                                                 $line = preg_replace('/^[ ]{0,4}/', '', $line);
    247 
    248                                                 $element['lines'] []= $line;
    249 
    250                                                 continue 2;
    251                                         }
    252 
    253                                         break;
    254                         }
    255 
    256                         # indentation sensitive types
    257 
    258                         $deindented_line = $line;
    259 
    260                         switch ($line[0])
    261                         {
    262                                 case ' ':
    263 
    264                                         # ~
    265 
    266                                         $deindented_line = ltrim($line);
    267 
    268                                         if ($deindented_line === '')
    269                                         {
    270                                                 continue 2;
    271                                         }
    272 
    273                                         # code block
    274 
    275                                         if (preg_match('/^[ ]{4}(.*)/', $line, $matches))
    276                                         {
    277                                                 if ($element['type'] === 'code_block')
    278                                                 {
    279                                                         if (isset($element['interrupted']))
    280                                                         {
    281                                                                 $element['text'] .= "\n";
    282 
    283                                                                 unset ($element['interrupted']);
    284                                                         }
    285 
    286                                                         $element['text'] .= "\n".$matches[1];
    287                                                 }
    288                                                 else
    289                                                 {
    290                                                         $elements []= $element;
    291 
    292                                                         $element = array(
    293                                                                 'type' => 'code_block',
    294                                                                 'text' => $matches[1],
    295                                                         );
    296                                                 }
    297 
    298                                                 continue 2;
    299                                         }
    300 
    301                                         break;
    302 
    303                                 case '#':
    304 
    305                                         # atx heading (#)
    306 
    307                                         if (preg_match('/^(#{1,6})[ ]*(.+?)[ ]*#*$/', $line, $matches))
    308                                         {
    309                                                 $elements []= $element;
    310 
    311                                                 $level = strlen($matches[1]);
    312 
    313                                                 $element = array(
    314                                                         'type' => 'h.',
    315                                                         'text' => $matches[2],
    316                                                         'level' => $level,
    317                                                 );
    318 
    319                                                 continue 2;
    320                                         }
    321 
    322                                         break;
    323 
    324                                 case '-':
    325 
    326                                         # setext heading (---)
    327 
    328                                         if ($line[0] === '-' and $element['type'] === 'p' and ! isset($element['interrupted']) and preg_match('/^[-]+[ ]*$/', $line))
    329                                         {
    330                                                 $element['type'] = 'h.';
    331                                                 $element['level'] = 2;
    332 
    333                                                 continue 2;
    334                                         }
    335 
    336                                         break;
    337 
    338                                 case '=':
    339 
    340                                         # setext heading (===)
    341 
    342                                         if ($line[0] === '=' and $element['type'] === 'p' and ! isset($element['interrupted']) and preg_match('/^[=]+[ ]*$/', $line))
    343                                         {
    344                                                 $element['type'] = 'h.';
    345                                                 $element['level'] = 1;
    346 
    347                                                 continue 2;
    348                                         }
    349 
    350                                         break;
    351                         }
    352 
    353                         # indentation insensitive types
    354 
    355                         switch ($deindented_line[0])
    356                         {
    357                                 case '<':
    358 
    359                                         # self-closing tag
    360 
    361                                         if (preg_match('{^<.+?/>$}', $deindented_line))
    362                                         {
    363                                                 $elements []= $element;
    364 
    365                                                 $element = array(
    366                                                         'type' => '',
    367                                                         'text' => $deindented_line,
    368                                                 );
    369 
    370                                                 continue 2;
    371                                         }
    372 
    373                                         # opening tag
    374 
    375                                         if (preg_match('{^<(\w+)(?:[ ].*?)?>}', $deindented_line, $matches))
    376                                         {
    377                                                 $elements []= $element;
    378 
    379                                                 $element = array(
    380                                                         'type' => 'markup',
    381                                                         'subtype' => strtolower($matches[1]),
    382                                                         'text' => $deindented_line,
    383                                                         'depth' => 0,
    384                                                 );
    385 
    386                                                 preg_match('{</'.$matches[1].'>\s*$}', $deindented_line) and $element['closed'] = true;
    387 
    388                                                 continue 2;
    389                                         }
    390 
    391                                         break;
    392 
    393                                 case '>':
    394 
    395                                         # quote
    396 
    397                                         if (preg_match('/^>[ ]?(.*)/', $deindented_line, $matches))
    398                                         {
    399                                                 $elements []= $element;
    400 
    401                                                 $element = array(
    402                                                         'type' => 'blockquote',
    403                                                         'lines' => array(
    404                                                                 $matches[1],
    405                                                         ),
    406                                                 );
    407 
    408                                                 continue 2;
    409                                         }
    410 
    411                                         break;
    412 
    413                                 case '[':
    414 
    415                                         # reference
    416 
    417                                         if (preg_match('/^\[(.+?)\]:[ ]*(.+?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*$/', $deindented_line, $matches))
    418                                         {
    419                                                 $label = strtolower($matches[1]);
    420 
    421                                                 $this->reference_map[$label] = array(
    422                                                         '»' => trim($matches[2], '<>'),
    423                                                 );
    424 
    425                                                 if (isset($matches[3]))
    426                                                 {
    427                                                         $this->reference_map[$label]['#'] = $matches[3];
    428                                                 }
    429 
    430                                                 continue 2;
    431                                         }
    432 
    433                                         break;
    434 
    435                                 case '`':
    436                                 case '~':
    437 
    438                                         # fenced code block
    439 
    440                                         if (preg_match('/^([`]{3,}|[~]{3,})[ ]*(\S+)?[ ]*$/', $deindented_line, $matches))
    441                                         {
    442                                                 $elements []= $element;
    443 
    444                                                 $element = array(
    445                                                         'type' => 'fenced_code_block',
    446                                                         'text' => '',
    447                                                         'fence' => $matches[1],
    448                                                 );
    449 
    450                                                 isset($matches[2]) and $element['language'] = $matches[2];
    451 
    452                                                 continue 2;
    453                                         }
    454 
    455                                         break;
    456 
    457                                 case '*':
    458                                 case '+':
    459                                 case '-':
    460                                 case '_':
    461 
    462                                         # hr
    463 
    464                                         if (preg_match('/^([-*_])([ ]{0,2}\1){2,}[ ]*$/', $deindented_line))
    465                                         {
    466                                                 $elements []= $element;
    467 
    468                                                 $element = array(
    469                                                         'type' => 'hr',
    470                                                 );
    471 
    472                                                 continue 2;
    473                                         }
    474 
    475                                         # li
    476 
    477                                         if (preg_match('/^([ ]*)[*+-][ ](.*)/', $line, $matches))
    478                                         {
    479                                                 $elements []= $element;
    480 
    481                                                 $element = array(
    482                                                         'type' => 'li',
    483                                                         'ordered' => false,
    484                                                         'indentation' => $matches[1],
    485                                                         'last' => true,
    486                                                         'lines' => array(
    487                                                                 preg_replace('/^[ ]{0,4}/', '', $matches[2]),
    488                                                         ),
    489                                                 );
    490 
    491                                                 continue 2;
    492                                         }
    493                         }
    494 
    495                         # li
    496 
    497                         if ($deindented_line[0] <= '9' and $deindented_line >= '0' and preg_match('/^([ ]*)\d+[.][ ](.*)/', $line, $matches))
    498                         {
    499                                 $elements []= $element;
    500 
    501                                 $element = array(
    502                                         'type' => 'li',
    503                                         'ordered' => true,
    504                                         'indentation' => $matches[1],
    505                                         'last' => true,
    506                                         'lines' => array(
    507                                                 preg_replace('/^[ ]{0,4}/', '', $matches[2]),
    508                                         ),
    509                                 );
    510 
    511                                 continue;
    512                         }
    513 
    514                         # paragraph
    515 
    516                         if ($element['type'] === 'p')
    517                         {
    518                                 if (isset($element['interrupted']))
    519                                 {
    520                                         $elements []= $element;
    521 
    522                                         $element['text'] = $line;
    523 
    524                                         unset($element['interrupted']);
    525                                 }
    526                                 else
    527                                 {
    528                                         $element['text'] .= "\n".$line;
    529                                 }
    530                         }
    531                         else
    532                         {
    533                                 $elements []= $element;
    534 
    535                                 $element = array(
    536                                         'type' => 'p',
    537                                         'text' => $line,
    538                                 );
    539                         }
    540                 }
    541 
    542                 $elements []= $element;
    543 
    544                 unset($elements[0]);
    545 
    546                 #
    547                 # ~
    548                 #
    549 
    550                 $markup = '';
    551 
    552                 foreach ($elements as $element)
    553                 {
    554                         switch ($element['type'])
    555                         {
    556                                 case 'p':
    557 
    558                                         $text = $this->parse_span_elements($element['text']);
    559 
    560                                         if ($context === 'li' and $markup === '')
    561                                         {
    562                                                 if (isset($element['interrupted']))
    563                                                 {
    564                                                         $markup .= "\n".'<p>'.$text.'</p>'."\n";
    565                                                 }
    566                                                 else
    567                                                 {
    568                                                         $markup .= $text;
    569                                                 }
    570                                         }
    571                                         else
    572                                         {
    573                                                 $markup .= '<p>'.$text.'</p>'."\n";
    574                                         }
    575 
    576                                         break;
    577 
    578                                 case 'blockquote':
    579 
    580                                         $text = $this->parse_block_elements($element['lines']);
    581 
    582                                         $markup .= '<blockquote>'."\n".$text.'</blockquote>'."\n";
    583 
    584                                         break;
    585 
    586                                 case 'code_block':
    587                                 case 'fenced_code_block':
    588 
    589                                         $text = htmlspecialchars($element['text'], ENT_NOQUOTES, 'UTF-8');
    590 
    591                                         strpos($text, "\x1A\\") !== FALSE and $text = strtr($text, $this->escape_sequence_map);
    592 
    593                                         $markup .= isset($element['language'])
    594                                                 ? '<pre><code class="language-'.$element['language'].'">'.$text.'</code></pre>'
    595                                                 : '<pre><code>'.$text.'</code></pre>';
    596 
    597                                         $markup .= "\n";
    598 
    599                                         break;
    600 
    601                                 case 'h.':
    602 
    603                                         $text = $this->parse_span_elements($element['text']);
    604 
    605                                         $markup .= '<h'.$element['level'].'>'.$text.'</h'.$element['level'].'>'."\n";
    606 
    607                                         break;
    608 
    609                                 case 'hr':
    610 
    611                                         $markup .= '<hr />'."\n";
    612 
    613                                         break;
    614 
    615                                 case 'li':
    616 
    617                                         if (isset($element['ordered'])) # first
    618                                         {
    619                                                 $list_type = $element['ordered'] ? 'ol' : 'ul';
    620 
    621                                                 $markup .= '<'.$list_type.'>'."\n";
    622                                         }
    623 
    624                                         if (isset($element['interrupted']) and ! isset($element['last']))
    625                                         {
    626                                                 $element['lines'] []= '';
    627                                         }
    628 
    629                                         $text = $this->parse_block_elements($element['lines'], 'li');
    630 
    631                                         $markup .= '<li>'.$text.'</li>'."\n";
    632 
    633                                         isset($element['last']) and $markup .= '</'.$list_type.'>'."\n";
    634 
    635                                         break;
    636 
    637                                 case 'markup':
    638 
    639                                         $markup .= $this->parse_span_elements($element['text'])."\n";
    640 
    641                                         break;
    642 
    643                                 default:
    644 
    645                                         $markup .= $element['text']."\n";
    646                         }
    647                 }
    648 
    649                 return $markup;
    650         }
    651 
    652         # ~
    653 
    654         private $strong_regex = array(
    655                 '*' => '/^[*]{2}([^*]+?)[*]{2}(?![*])/s',
    656                 '_' => '/^__([^_]+?)__(?!_)/s',
    657         );
    658 
    659         private $em_regex = array(
    660                 '*' => '/^[*]([^*]+?)[*](?![*])/s',
    661                 '_' => '/^_([^_]+?)[_](?![_])\b/s',
    662         );
    663 
    664         private $strong_em_regex = array(
    665                 '*' => '/^[*]{2}(.*?)[*](.+?)[*](.*?)[*]{2}/s',
    666                 '_' => '/^__(.*?)_(.+?)_(.*?)__/s',
    667         );
    668 
    669         private $em_strong_regex = array(
    670                 '*' => '/^[*](.*?)[*]{2}(.+?)[*]{2}(.*?)[*]/s',
    671                 '_' => '/^_(.*?)__(.+?)__(.*?)_/s',
    672         );
    673 
    674         private function parse_span_elements($text, $markers = array('![', '&', '*', '<', '[', '_', '`', 'http', '~~'))
    675         {
    676                 if (isset($text[2]) === false or $markers === array())
    677                 {
    678                         return $text;
    679                 }
    680 
    681                 # ~
    682 
    683                 $markup = '';
    684 
    685                 while ($markers)
    686                 {
    687                         $closest_marker = null;
    688                         $closest_marker_index = 0;
    689                         $closest_marker_position = null;
    690 
    691                         foreach ($markers as $index => $marker)
    692                         {
    693                                 $marker_position = strpos($text, $marker);
    694 
    695                                 if ($marker_position === false)
    696                                 {
    697                                         unset($markers[$index]);
    698 
    699                                         continue;
    700                                 }
    701 
    702                                 if ($closest_marker === null or $marker_position < $closest_marker_position)
    703                                 {
    704                                         $closest_marker = $marker;
    705                                         $closest_marker_index = $index;
    706                                         $closest_marker_position = $marker_position;
    707                                 }
    708                         }
    709 
    710                         # ~
    711 
    712                         if ($closest_marker === null or isset($text[$closest_marker_position + 2]) === false)
    713                         {
    714                                 $markup .= $text;
    715 
    716                                 break;
    717                         }
    718                         else
    719                         {
    720                                 $markup .= substr($text, 0, $closest_marker_position);
    721                         }
    722 
    723                         $text = substr($text, $closest_marker_position);
    724 
    725                         # ~
    726 
    727                         unset($markers[$closest_marker_index]);
    728 
    729                         # ~
    730 
    731                         switch ($closest_marker)
    732                         {
    733                                 case '![':
    734                                 case '[':
    735 
    736                                         if (strpos($text, ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $text, $matches))
    737                                         {
    738                                                 $element = array(
    739                                                         '!' => $text[0] === '!',
    740                                                         'a' => $matches[1],
    741                                                 );
    742 
    743                                                 $offset = strlen($matches[0]);
    744 
    745                                                 $element['!'] and $offset++;
    746 
    747                                                 $remaining_text = substr($text, $offset);
    748 
    749                                                 if ($remaining_text[0] === '(' and preg_match('/\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $remaining_text, $matches))
    750                                                 {
    751                                                         $element['»'] = $matches[1];
    752 
    753                                                         if (isset($matches[2]))
    754                                                         {
    755                                                                 $element['#'] = $matches[2];
    756                                                         }
    757 
    758                                                         $offset += strlen($matches[0]);
    759                                                 }
    760                                                 elseif ($this->reference_map)
    761                                                 {
    762                                                         $reference = $element['a'];
    763 
    764                                                         if (preg_match('/^\s*\[(.*?)\]/', $remaining_text, $matches))
    765                                                         {
    766                                                                 $reference = $matches[1] ? $matches[1] : $element['a'];
    767 
    768                                                                 $offset += strlen($matches[0]);
    769                                                         }
    770 
    771                                                         $reference = strtolower($reference);
    772 
    773                                                         if (isset($this->reference_map[$reference]))
    774                                                         {
    775                                                                 $element['»'] = $this->reference_map[$reference]['»'];
    776 
    777                                                                 if (isset($this->reference_map[$reference]['#']))
    778                                                                 {
    779                                                                         $element['#'] = $this->reference_map[$reference]['#'];
    780                                                                 }
    781                                                         }
    782                                                         else
    783                                                         {
    784                                                                 unset($element);
    785                                                         }
    786                                                 }
    787                                                 else
    788                                                 {
    789                                                         unset($element);
    790                                                 }
    791                                         }
    792 
    793                                         if (isset($element))
    794                                         {
    795                                                 $element['»'] = str_replace('&', '&amp;', $element['»']);
    796                                                 $element['»'] = str_replace('<', '&lt;', $element['»']);
    797 
    798                                                 if ($element['!'])
    799                                                 {
    800                                                         $markup .= '<img alt="'.$element['a'].'" src="'.$element['»'].'" />';
    801                                                 }
    802                                                 else
    803                                                 {
    804                                                         $element['a'] = $this->parse_span_elements($element['a'], $markers);
    805 
    806                                                         $markup .= isset($element['#'])
    807                                                                 ? '<a href="'.$element['»'].'" title="'.$element['#'].'">'.$element['a'].'</a>'
    808                                                                 : '<a href="'.$element['»'].'">'.$element['a'].'</a>';
    809                                                 }
    810 
    811                                                 unset($element);
    812                                         }
    813                                         else
    814                                         {
    815                                                 $markup .= $closest_marker;
    816 
    817                                                 $offset = $closest_marker === '![' ? 2 : 1;
    818                                         }
    819 
    820                                         break;
    821 
    822                                 case '&':
    823 
    824                                         $markup .= '&amp;';
    825 
    826                                         $offset = substr($text, 0, 5) === '&amp;' ? 5 : 1;
    827 
    828                                         break;
    829 
    830                                 case '*':
    831                                 case '_':
    832 
    833                                         if ($text[1] === $closest_marker and preg_match($this->strong_regex[$closest_marker], $text, $matches))
    834                                         {
    835                                                 $matches[1] = $this->parse_span_elements($matches[1], $markers);
    836 
    837                                                 $markup .= '<strong>'.$matches[1].'</strong>';
    838                                         }
    839                                         elseif (preg_match($this->em_regex[$closest_marker], $text, $matches))
    840                                         {
    841                                                 $matches[1] = $this->parse_span_elements($matches[1], $markers);
    842 
    843                                                 $markup .= '<em>'.$matches[1].'</em>';
    844                                         }
    845                                         elseif ($text[1] === $closest_marker and preg_match($this->strong_em_regex[$closest_marker], $text, $matches))
    846                                         {
    847                                                 $matches[2] = $this->parse_span_elements($matches[2], $markers);
    848 
    849                                                 $matches[1] and $matches[1] = $this->parse_span_elements($matches[1], $markers);
    850                                                 $matches[3] and $matches[3] = $this->parse_span_elements($matches[3], $markers);
    851 
    852                                                 $markup .= '<strong>'.$matches[1].'<em>'.$matches[2].'</em>'.$matches[3].'</strong>';
    853                                         }
    854                                         elseif (preg_match($this->em_strong_regex[$closest_marker], $text, $matches))
    855                                         {
    856                                                 $matches[2] = $this->parse_span_elements($matches[2], $markers);
    857 
    858                                                 $matches[1] and $matches[1] = $this->parse_span_elements($matches[1], $markers);
    859                                                 $matches[3] and $matches[3] = $this->parse_span_elements($matches[3], $markers);
    860 
    861                                                 $markup .= '<em>'.$matches[1].'<strong>'.$matches[2].'</strong>'.$matches[3].'</em>';
    862                                         }
    863 
    864                                         if (isset($matches) and $matches)
    865                                         {
    866                                                 $offset = strlen($matches[0]);
    867                                         }
    868                                         else
    869                                         {
    870                                                 $markup .= $closest_marker;
    871 
    872                                                 $offset = 1;
    873                                         }
    874 
    875                                         break;
    876 
    877                                 case '<':
    878 
    879                                         if (strpos($text, '>') !== false)
    880                                         {
    881                                                 if ($text[1] === 'h' and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $text, $matches))
    882                                                 {
    883                                                         $element_url = $matches[1];
    884                                                         $element_url = str_replace('&', '&amp;', $element_url);
    885                                                         $element_url = str_replace('<', '&lt;', $element_url);
    886 
    887                                                         $markup .= '<a href="'.$element_url.'">'.$element_url.'</a>';
    888 
    889                                                         $offset = strlen($matches[0]);
    890                                                 }
    891                                                 elseif (preg_match('/^<\/?\w.*?>/', $text, $matches))
    892                                                 {
    893                                                         $markup .= $matches[0];
    894 
    895                                                         $offset = strlen($matches[0]);
    896                                                 }
    897                                                 else
    898                                                 {
    899                                                         $markup .= '&lt;';
    900 
    901                                                         $offset = 1;
    902                                                 }
    903                                         }
    904                                         else
    905                                         {
    906                                                 $markup .= '&lt;';
    907 
    908                                                 $offset = 1;
    909                                         }
    910 
    911                                         break;
    912 
    913                                 case '`':
    914 
    915                                         if (preg_match('/^`(.+?)`/', $text, $matches))
    916                                         {
    917                                                 $element_text = $matches[1];
    918                                                 $element_text = htmlspecialchars($element_text, ENT_NOQUOTES, 'UTF-8');
    919 
    920                                                 if ($this->escape_sequence_map and strpos($element_text, "\x1A") !== false)
    921                                                 {
    922                                                         $element_text = strtr($element_text, $this->escape_sequence_map);
    923                                                 }
    924 
    925                                                 $markup .= '<code>'.$element_text.'</code>';
    926 
    927                                                 $offset = strlen($matches[0]);
    928                                         }
    929                                         else
    930                                         {
    931                                                 $markup .= '`';
    932 
    933                                                 $offset = 1;
    934                                         }
    935 
    936                                         break;
    937 
    938                                 case 'http':
    939 
    940                                         if (preg_match('/^https?:[\/]{2}[^\s]+\b/i', $text, $matches))
    941                                         {
    942                                                 $element_url = $matches[0];
    943                                                 $element_url = str_replace('&', '&amp;', $element_url);
    944                                                 $element_url = str_replace('<', '&lt;', $element_url);
    945 
    946                                                 $markup .= '<a href="'.$element_url.'">'.$element_url.'</a>';
    947 
    948                                                 $offset = strlen($matches[0]);
    949                                         }
    950                                         else
    951                                         {
    952                                                 $markup .= 'http';
    953 
    954                                                 $offset = 4;
    955                                         }
    956 
    957                                         break;
    958 
    959                                 case '~~':
    960 
    961                                         if (preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $text, $matches))
    962                                         {
    963                                                 $matches[1] = $this->parse_span_elements($matches[1], $markers);
    964 
    965                                                 $markup .= '<del>'.$matches[1].'</del>';
    966 
    967                                                 $offset = strlen($matches[0]);
    968                                         }
    969                                         else
    970                                         {
    971                                                 $markup .= '~~';
    972 
    973                                                 $offset = 2;
    974                                         }
    975 
    976                                         break;
    977                         }
    978 
    979                         if (isset($offset))
    980                         {
    981                                 $text = substr($text, $offset);
    982                         }
    983 
    984                         $markers[$closest_marker_index] = $closest_marker;
    985                 }
    986 
    987                 $markup = str_replace($this->break_marker, '<br />'."\n", $markup);
    988 
    989                 return $markup;
    990         }
     18    # Multiton
     19
     20    static function instance($name = 'default')
     21    {
     22        if (isset(self::$instances[$name]))
     23        {
     24            return self::$instances[$name];
     25        }
     26
     27        $instance = new Parsedown();
     28
     29        self::$instances[$name] = $instance;
     30
     31        return $instance;
     32    }
     33
     34    private static $instances = array();
     35
     36    #
     37    # Setters
     38    #
     39
     40    # Enables GFM line breaks.
     41
     42    function set_breaks_enabled($breaks_enabled)
     43    {
     44        $this->breaks_enabled = $breaks_enabled;
     45
     46        return $this;
     47    }
     48
     49    private $breaks_enabled = false;
     50
     51    #
     52    # Synopsis
     53    #
     54
     55    # Markdown is intended to be easy-to-read by humans - those of us who read
     56    # line by line, left to right, top to bottom. In order to take advantage of
     57    # this, Parsedown tries to read in a similar way. It breaks texts into
     58    # lines, it iterates through them and it looks at how they start and relate
     59    # to each other.
     60
     61    #
     62    # Methods
     63    #
     64
     65    function parse($text)
     66    {
     67        # standardize line breaks
     68        $text = str_replace("\r\n", "\n", $text);
     69        $text = str_replace("\r", "\n", $text);
     70
     71        # replace tabs with spaces
     72        $text = str_replace("\t", '    ', $text);
     73
     74        # remove surrounding line breaks
     75        $text = trim($text, "\n");
     76
     77        # split text into lines
     78        $lines = explode("\n", $text);
     79
     80        # convert lines into html
     81        $text = $this->parse_block_elements($lines);
     82
     83        # remove trailing line breaks
     84        $text = chop($text, "\n");
     85
     86        return $text;
     87    }
     88
     89    #
     90    # Private
     91
     92    private function parse_block_elements(array $lines, $context = '')
     93    {
     94        $blocks = array();
     95
     96        $block = array(
     97            'type' => '',
     98        );
     99
     100        foreach ($lines as $line)
     101        {
     102            # context
     103
     104            switch ($block['type'])
     105            {
     106                case 'fenced':
     107
     108                    if ( ! isset($block['closed']))
     109                    {
     110                        if (preg_match('/^[ ]*'.$block['fence'][0].'{3,}[ ]*$/', $line))
     111                        {
     112                            $block['closed'] = true;
     113                        }
     114                        else
     115                        {
     116                            if ($block['text'] !== '')
     117                            {
     118                                $block['text'] .= "\n";
     119                            }
     120
     121                            $block['text'] .= $line;
     122                        }
     123
     124                        continue 2;
     125                    }
     126
     127                    break;
     128
     129                case 'markup':
     130
     131                    if ( ! isset($block['closed']))
     132                    {
     133                        if (strpos($line, $block['start']) !== false) # opening tag
     134                        {
     135                            $block['depth']++;
     136                        }
     137
     138                        if (strpos($line, $block['end']) !== false) # closing tag
     139                        {
     140                            if ($block['depth'] > 0)
     141                            {
     142                                $block['depth']--;
     143                            }
     144                            else
     145                            {
     146                                $block['closed'] = true;
     147                            }
     148                        }
     149
     150                        $block['text'] .= "\n".$line;
     151
     152                        continue 2;
     153                    }
     154
     155                    break;
     156            }
     157
     158            # ~
     159
     160            $indentation = 0;
     161
     162            while(isset($line[$indentation]) and $line[$indentation] === ' ')
     163            {
     164                $indentation++;
     165            }
     166
     167            $outdented_line = $indentation > 0 ? ltrim($line) : $line;
     168
     169            # blank
     170
     171            if ($outdented_line === '')
     172            {
     173                $block['interrupted'] = true;
     174
     175                continue;
     176            }
     177
     178            # context
     179
     180            switch ($block['type'])
     181            {
     182                case 'quote':
     183
     184                    if ( ! isset($block['interrupted']))
     185                    {
     186                        $line = preg_replace('/^[ ]*>[ ]?/', '', $line);
     187
     188                        $block['lines'] []= $line;
     189
     190                        continue 2;
     191                    }
     192
     193                    break;
     194
     195                case 'li':
     196
     197                    if ($block['indentation'] === $indentation and preg_match('/^'.$block['marker'].'[ ]+(.*)/', $outdented_line, $matches))
     198                    {
     199                        unset($block['last']);
     200
     201                        $blocks []= $block;
     202
     203                        $block['last'] = true;
     204                        $block['lines'] = array($matches[1]);
     205
     206                        unset($block['first']);
     207                        unset($block['interrupted']);
     208
     209                        continue 2;
     210                    }
     211
     212                    if ( ! isset($block['interrupted']))
     213                    {
     214                        $line = preg_replace('/^[ ]{0,'.$block['baseline'].'}/', '', $line);
     215
     216                        $block['lines'] []= $line;
     217
     218                        continue 2;
     219                    }
     220                    elseif ($line[0] === ' ')
     221                    {
     222                        $block['lines'] []= '';
     223
     224                        $line = preg_replace('/^[ ]{0,'.$block['baseline'].'}/', '', $line);
     225
     226                        $block['lines'] []= $line;
     227
     228                        unset($block['interrupted']);
     229
     230                        continue 2;
     231                    }
     232
     233                    break;
     234            }
     235
     236            # indentation sensitive types
     237
     238            switch ($line[0])
     239            {
     240                case ' ':
     241
     242                    # code
     243
     244                    if ($indentation >= 4)
     245                    {
     246                        $code_line = substr($line, 4);
     247
     248                        if ($block['type'] === 'code')
     249                        {
     250                            if (isset($block['interrupted']))
     251                            {
     252                                $block['text'] .= "\n";
     253
     254                                unset($block['interrupted']);
     255                            }
     256
     257                            $block['text'] .= "\n".$code_line;
     258                        }
     259                        else
     260                        {
     261                            $blocks []= $block;
     262
     263                            $block = array(
     264                                'type' => 'code',
     265                                'text' => $code_line,
     266                            );
     267                        }
     268
     269                        continue 2;
     270                    }
     271
     272                    break;
     273
     274                case '#':
     275
     276                    # atx heading (#)
     277
     278                    if (isset($line[1]))
     279                    {
     280                        $blocks []= $block;
     281
     282                        $level = 1;
     283
     284                        while (isset($line[$level]) and $line[$level] === '#')
     285                        {
     286                            $level++;
     287                        }
     288
     289                        $block = array(
     290                            'type' => 'heading',
     291                            'text' => trim($line, '# '),
     292                            'level' => $level,
     293                        );
     294
     295                        continue 2;
     296                    }
     297
     298                    break;
     299
     300                case '-':
     301                case '=':
     302
     303                    # setext heading (===)
     304
     305                    if ($block['type'] === 'paragraph' and isset($block['interrupted']) === false)
     306                    {
     307                        $chopped_line = chop($line);
     308
     309                        $i = 1;
     310
     311                        while (isset($chopped_line[$i]))
     312                        {
     313                            if ($chopped_line[$i] !== $line[0])
     314                            {
     315                                break 2;
     316                            }
     317
     318                            $i++;
     319                        }
     320
     321                        $block['type'] = 'heading';
     322
     323                        $block['level'] = $line[0] === '-' ? 2 : 1;
     324
     325                        continue 2;
     326                    }
     327
     328                    break;
     329            }
     330
     331            # indentation insensitive types
     332
     333            switch ($outdented_line[0])
     334            {
     335                case '<':
     336
     337                    $position = strpos($outdented_line, '>');
     338
     339                    if ($position > 1)
     340                    {
     341                        $substring = substr($outdented_line, 1, $position - 1);
     342
     343                        $substring = chop($substring);
     344
     345                        if (substr($substring, -1) === '/')
     346                        {
     347                            $is_self_closing = true;
     348
     349                            $substring = substr($substring, 0, -1);
     350                        }
     351
     352                        $position = strpos($substring, ' ');
     353
     354                        if ($position)
     355                        {
     356                            $name = substr($substring, 0, $position);
     357                        }
     358                        else
     359                        {
     360                            $name = $substring;
     361                        }
     362
     363                        if ( ! ctype_alpha($name))
     364                        {
     365                            break;
     366                        }
     367
     368                        if (in_array($name, self::$text_level_elements))
     369                        {
     370                            break;
     371                        }
     372
     373                        $blocks []= $block;
     374
     375                        if (isset($is_self_closing))
     376                        {
     377                            $block = array(
     378                                'type' => 'self-closing tag',
     379                                'text' => $outdented_line,
     380                            );
     381
     382                            unset($is_self_closing);
     383
     384                            continue 2;
     385                        }
     386
     387                        $block = array(
     388                            'type' => 'markup',
     389                            'text' => $outdented_line,
     390                            'start' => '<'.$name.'>',
     391                            'end' => '</'.$name.'>',
     392                            'depth' => 0,
     393                        );
     394
     395                        if (strpos($outdented_line, $block['end']))
     396                        {
     397                            $block['closed'] = true;
     398                        }
     399
     400                        continue 2;
     401                    }
     402
     403                    break;
     404
     405                case '>':
     406
     407                    # quote
     408
     409                    if (preg_match('/^>[ ]?(.*)/', $outdented_line, $matches))
     410                    {
     411                        $blocks []= $block;
     412
     413                        $block = array(
     414                            'type' => 'quote',
     415                            'lines' => array(
     416                                $matches[1],
     417                            ),
     418                        );
     419
     420                        continue 2;
     421                    }
     422
     423                    break;
     424
     425                case '[':
     426
     427                    # reference
     428
     429                    $position = strpos($outdented_line, ']:');
     430
     431                    if ($position)
     432                    {
     433                        $reference = array();
     434
     435                        $label = substr($outdented_line, 1, $position - 1);
     436                        $label = strtolower($label);
     437
     438                        $substring = substr($outdented_line, $position + 2);
     439                        $substring = trim($substring);
     440
     441                        if ($substring === '')
     442                        {
     443                            break;
     444                        }
     445
     446                        if ($substring[0] === '<')
     447                        {
     448                            $position = strpos($substring, '>');
     449
     450                            if ($position === false)
     451                            {
     452                                break;
     453                            }
     454
     455                            $reference['»'] = substr($substring, 1, $position - 1);
     456
     457                            $substring = substr($substring, $position + 1);
     458                        }
     459                        else
     460                        {
     461                            $position = strpos($substring, ' ');
     462
     463                            if ($position === false)
     464                            {
     465                                $reference['»'] = $substring;
     466
     467                                $substring = false;
     468                            }
     469                            else
     470                            {
     471                                $reference['»'] = substr($substring, 0, $position);
     472
     473                                $substring = substr($substring, $position + 1);
     474                            }
     475                        }
     476
     477                        if ($substring !== false)
     478                        {
     479                            if ($substring[0] !== '"' and $substring[0] !== "'" and $substring[0] !== '(')
     480                            {
     481                                break;
     482                            }
     483
     484                            $last_char = substr($substring, -1);
     485
     486                            if ($last_char !== '"' and $last_char !== "'" and $last_char !== ')')
     487                            {
     488                                break;
     489                            }
     490
     491                            $reference['#'] = substr($substring, 1, -1);
     492                        }
     493
     494                        $this->reference_map[$label] = $reference;
     495
     496                        continue 2;
     497                    }
     498
     499                    break;
     500
     501                case '`':
     502                case '~':
     503
     504                    # fenced code block
     505
     506                    if (preg_match('/^([`]{3,}|[~]{3,})[ ]*(\S+)?[ ]*$/', $outdented_line, $matches))
     507                    {
     508                        $blocks []= $block;
     509
     510                        $block = array(
     511                            'type' => 'fenced',
     512                            'text' => '',
     513                            'fence' => $matches[1],
     514                        );
     515
     516                        if (isset($matches[2]))
     517                        {
     518                            $block['language'] = $matches[2];
     519                        }
     520
     521                        continue 2;
     522                    }
     523
     524                    break;
     525
     526                case '*':
     527                case '+':
     528                case '-':
     529                case '_':
     530
     531                    # hr
     532
     533                    if (preg_match('/^([-*_])([ ]{0,2}\1){2,}[ ]*$/', $outdented_line))
     534                    {
     535                        $blocks []= $block;
     536
     537                        $block = array(
     538                            'type' => 'rule',
     539                        );
     540
     541                        continue 2;
     542                    }
     543
     544                    # li
     545
     546                    if (preg_match('/^([*+-][ ]+)(.*)/', $outdented_line, $matches))
     547                    {
     548                        $blocks []= $block;
     549
     550                        $baseline = $indentation + strlen($matches[1]);
     551
     552                        $block = array(
     553                            'type' => 'li',
     554                            'indentation' => $indentation,
     555                            'baseline' => $baseline,
     556                            'marker' => '[*+-]',
     557                            'first' => true,
     558                            'last' => true,
     559                            'lines' => array(),
     560                        );
     561
     562                        $block['lines'] []= preg_replace('/^[ ]{0,4}/', '', $matches[2]);
     563
     564                        continue 2;
     565                    }
     566            }
     567
     568            # li
     569
     570            if ($outdented_line[0] <= '9' and preg_match('/^(\d+[.][ ]+)(.*)/', $outdented_line, $matches))
     571            {
     572                $blocks []= $block;
     573
     574                $baseline = $indentation + strlen($matches[1]);
     575
     576                $block = array(
     577                    'type' => 'li',
     578                    'indentation' => $indentation,
     579                    'baseline' => $baseline,
     580                    'marker' => '\d+[.]',
     581                    'first' => true,
     582                    'last' => true,
     583                    'ordered' => true,
     584                    'lines' => array(),
     585                );
     586
     587                $block['lines'] []= preg_replace('/^[ ]{0,4}/', '', $matches[2]);
     588
     589                continue;
     590            }
     591
     592            # paragraph
     593
     594            if ($block['type'] === 'paragraph')
     595            {
     596                if (isset($block['interrupted']))
     597                {
     598                    $blocks []= $block;
     599
     600                    $block['text'] = $line;
     601
     602                    unset($block['interrupted']);
     603                }
     604                else
     605                {
     606                    if ($this->breaks_enabled)
     607                    {
     608                        $block['text'] .= '  ';
     609                    }
     610
     611                    $block['text'] .= "\n".$line;
     612                }
     613            }
     614            else
     615            {
     616                $blocks []= $block;
     617
     618                $block = array(
     619                    'type' => 'paragraph',
     620                    'text' => $line,
     621                );
     622            }
     623        }
     624
     625        $blocks []= $block;
     626
     627        unset($blocks[0]);
     628
     629        # $blocks » HTML
     630
     631        $markup = '';
     632
     633        foreach ($blocks as $block)
     634        {
     635            switch ($block['type'])
     636            {
     637                case 'paragraph':
     638
     639                    $text = $this->parse_span_elements($block['text']);
     640
     641                    if ($context === 'li' and $markup === '')
     642                    {
     643                        if (isset($block['interrupted']))
     644                        {
     645                            $markup .= "\n".'<p>'.$text.'</p>'."\n";
     646                        }
     647                        else
     648                        {
     649                            $markup .= $text;
     650
     651                            if (isset($blocks[2]))
     652                            {
     653                                $markup .= "\n";
     654                            }
     655                        }
     656                    }
     657                    else
     658                    {
     659                        $markup .= '<p>'.$text.'</p>'."\n";
     660                    }
     661
     662                    break;
     663
     664                case 'quote':
     665
     666                    $text = $this->parse_block_elements($block['lines']);
     667
     668                    $markup .= '<blockquote>'."\n".$text.'</blockquote>'."\n";
     669
     670                    break;
     671
     672                case 'code':
     673
     674                    $text = htmlspecialchars($block['text'], ENT_NOQUOTES, 'UTF-8');
     675
     676                    $markup .= '<pre><code>'.$text.'</code></pre>'."\n";
     677
     678                    break;
     679
     680                case 'fenced':
     681
     682                    $text = htmlspecialchars($block['text'], ENT_NOQUOTES, 'UTF-8');
     683
     684                    $markup .= '<pre><code';
     685
     686                    if (isset($block['language']))
     687                    {
     688                        $markup .= ' class="language-'.$block['language'].'"';
     689                    }
     690
     691                    $markup .= '>'.$text.'</code></pre>'."\n";
     692
     693                    break;
     694
     695                case 'heading':
     696
     697                    $text = $this->parse_span_elements($block['text']);
     698
     699                    $markup .= '<h'.$block['level'].'>'.$text.'</h'.$block['level'].'>'."\n";
     700
     701                    break;
     702
     703                case 'rule':
     704
     705                    $markup .= '<hr />'."\n";
     706
     707                    break;
     708
     709                case 'li':
     710
     711                    if (isset($block['first']))
     712                    {
     713                        $type = isset($block['ordered']) ? 'ol' : 'ul';
     714
     715                        $markup .= '<'.$type.'>'."\n";
     716                    }
     717
     718                    if (isset($block['interrupted']) and ! isset($block['last']))
     719                    {
     720                        $block['lines'] []= '';
     721                    }
     722
     723                    $text = $this->parse_block_elements($block['lines'], 'li');
     724
     725                    $markup .= '<li>'.$text.'</li>'."\n";
     726
     727                    if (isset($block['last']))
     728                    {
     729                        $type = isset($block['ordered']) ? 'ol' : 'ul';
     730
     731                        $markup .= '</'.$type.'>'."\n";
     732                    }
     733
     734                    break;
     735
     736                case 'markup':
     737
     738                    $markup .= $block['text']."\n";
     739
     740                    break;
     741
     742                default:
     743
     744                    $markup .= $block['text']."\n";
     745            }
     746        }
     747
     748        return $markup;
     749    }
     750
     751    private function parse_span_elements($text, $markers = array("  \n", '![', '&', '*', '<', '[', '\\', '_', '`', 'http', '~~'))
     752    {
     753        if (isset($text[1]) === false or $markers === array())
     754        {
     755            return $text;
     756        }
     757
     758        # ~
     759
     760        $markup = '';
     761
     762        while ($markers)
     763        {
     764            $closest_marker = null;
     765            $closest_marker_index = 0;
     766            $closest_marker_position = null;
     767
     768            foreach ($markers as $index => $marker)
     769            {
     770                $marker_position = strpos($text, $marker);
     771
     772                if ($marker_position === false)
     773                {
     774                    unset($markers[$index]);
     775
     776                    continue;
     777                }
     778
     779                if ($closest_marker === null or $marker_position < $closest_marker_position)
     780                {
     781                    $closest_marker = $marker;
     782                    $closest_marker_index = $index;
     783                    $closest_marker_position = $marker_position;
     784                }
     785            }
     786
     787            # ~
     788
     789            if ($closest_marker === null or isset($text[$closest_marker_position + 1]) === false)
     790            {
     791                $markup .= $text;
     792
     793                break;
     794            }
     795            else
     796            {
     797                $markup .= substr($text, 0, $closest_marker_position);
     798            }
     799
     800            $text = substr($text, $closest_marker_position);
     801
     802            # ~
     803
     804            unset($markers[$closest_marker_index]);
     805
     806            # ~
     807
     808            switch ($closest_marker)
     809            {
     810                case "  \n":
     811
     812                    $markup .= '<br />'."\n";
     813
     814                    $offset = 3;
     815
     816                    break;
     817
     818                case '![':
     819                case '[':
     820
     821                    if (strpos($text, ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $text, $matches))
     822                    {
     823                        $element = array(
     824                            '!' => $text[0] === '!',
     825                            'a' => $matches[1],
     826                        );
     827
     828                        $offset = strlen($matches[0]);
     829
     830                        if ($element['!'])
     831                        {
     832                            $offset++;
     833                        }
     834
     835                        $remaining_text = substr($text, $offset);
     836
     837                        if ($remaining_text[0] === '(' and preg_match('/\([ ]*(.*?)(?:[ ]+[\'"](.+?)[\'"])?[ ]*\)/', $remaining_text, $matches))
     838                        {
     839                            $element['»'] = $matches[1];
     840
     841                            if (isset($matches[2]))
     842                            {
     843                                $element['#'] = $matches[2];
     844                            }
     845
     846                            $offset += strlen($matches[0]);
     847                        }
     848                        elseif ($this->reference_map)
     849                        {
     850                            $reference = $element['a'];
     851
     852                            if (preg_match('/^\s*\[(.*?)\]/', $remaining_text, $matches))
     853                            {
     854                                $reference = $matches[1] ? $matches[1] : $element['a'];
     855
     856                                $offset += strlen($matches[0]);
     857                            }
     858
     859                            $reference = strtolower($reference);
     860
     861                            if (isset($this->reference_map[$reference]))
     862                            {
     863                                $element['»'] = $this->reference_map[$reference]['»'];
     864
     865                                if (isset($this->reference_map[$reference]['#']))
     866                                {
     867                                    $element['#'] = $this->reference_map[$reference]['#'];
     868                                }
     869                            }
     870                            else
     871                            {
     872                                unset($element);
     873                            }
     874                        }
     875                        else
     876                        {
     877                            unset($element);
     878                        }
     879                    }
     880
     881                    if (isset($element))
     882                    {
     883                        $element['»'] = str_replace('&', '&amp;', $element['»']);
     884                        $element['»'] = str_replace('<', '&lt;', $element['»']);
     885
     886                        if ($element['!'])
     887                        {
     888                            $markup .= '<img alt="'.$element['a'].'" src="'.$element['»'].'"';
     889
     890                            if (isset($element['#']))
     891                            {
     892                                $markup .= ' title="'.$element['#'].'"';
     893                            }
     894
     895                            $markup .= ' />';
     896                        }
     897                        else
     898                        {
     899                            $element['a'] = $this->parse_span_elements($element['a'], $markers);
     900
     901                            $markup .= '<a href="'.$element['»'].'"';
     902
     903                            if (isset($element['#']))
     904                            {
     905                                $markup .= ' title="'.$element['#'].'"';
     906                            }
     907
     908                            $markup .= '>'.$element['a'].'</a>';
     909                        }
     910
     911                        unset($element);
     912                    }
     913                    else
     914                    {
     915                        $markup .= $closest_marker;
     916
     917                        $offset = $closest_marker === '![' ? 2 : 1;
     918                    }
     919
     920                    break;
     921
     922                case '&':
     923
     924                    if (preg_match('/^&#?\w+;/', $text, $matches))
     925                    {
     926                        $markup .= $matches[0];
     927
     928                        $offset = strlen($matches[0]);
     929                    }
     930                    else
     931                    {
     932                        $markup .= '&amp;';
     933
     934                        $offset = 1;
     935                    }
     936
     937                    break;
     938
     939                case '*':
     940                case '_':
     941
     942                    if ($text[1] === $closest_marker and preg_match(self::$strong_regex[$closest_marker], $text, $matches))
     943                    {
     944                        $markers[] = $closest_marker;
     945                        $matches[1] = $this->parse_span_elements($matches[1], $markers);
     946
     947                        $markup .= '<strong>'.$matches[1].'</strong>';
     948                    }
     949                    elseif (preg_match(self::$em_regex[$closest_marker], $text, $matches))
     950                    {
     951                        $markers[] = $closest_marker;
     952                        $matches[1] = $this->parse_span_elements($matches[1], $markers);
     953
     954                        $markup .= '<em>'.$matches[1].'</em>';
     955                    }
     956
     957                    if (isset($matches) and $matches)
     958                    {
     959                        $offset = strlen($matches[0]);
     960                    }
     961                    else
     962                    {
     963                        $markup .= $closest_marker;
     964
     965                        $offset = 1;
     966                    }
     967
     968                    break;
     969
     970                case '<':
     971
     972                    if (strpos($text, '>') !== false)
     973                    {
     974                        if ($text[1] === 'h' and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $text, $matches))
     975                        {
     976                            $element_url = $matches[1];
     977                            $element_url = str_replace('&', '&amp;', $element_url);
     978                            $element_url = str_replace('<', '&lt;', $element_url);
     979
     980                            $markup .= '<a href="'.$element_url.'">'.$element_url.'</a>';
     981
     982                            $offset = strlen($matches[0]);
     983                        }
     984                        elseif (strpos($text, '@') > 1 and preg_match('/<(\S+?@\S+?)>/', $text, $matches))
     985                        {
     986                            $markup .= '<a href="mailto:'.$matches[1].'">'.$matches[1].'</a>';
     987
     988                            $offset = strlen($matches[0]);
     989                        }
     990                        elseif (preg_match('/^<\/?\w.*?>/', $text, $matches))
     991                        {
     992                            $markup .= $matches[0];
     993
     994                            $offset = strlen($matches[0]);
     995                        }
     996                        else
     997                        {
     998                            $markup .= '&lt;';
     999
     1000                            $offset = 1;
     1001                        }
     1002                    }
     1003                    else
     1004                    {
     1005                        $markup .= '&lt;';
     1006
     1007                        $offset = 1;
     1008                    }
     1009
     1010                    break;
     1011
     1012                case '\\':
     1013
     1014                    if (in_array($text[1], self::$special_characters))
     1015                    {
     1016                        $markup .= $text[1];
     1017
     1018                        $offset = 2;
     1019                    }
     1020                    else
     1021                    {
     1022                        $markup .= '\\';
     1023
     1024                        $offset = 1;
     1025                    }
     1026
     1027                    break;
     1028
     1029                case '`':
     1030
     1031                    if (preg_match('/^(`+)[ ]*(.+?)[ ]*(?<!`)\1(?!`)/', $text, $matches))
     1032                    {
     1033                        $element_text = $matches[2];
     1034                        $element_text = htmlspecialchars($element_text, ENT_NOQUOTES, 'UTF-8');
     1035
     1036                        $markup .= '<code>'.$element_text.'</code>';
     1037
     1038                        $offset = strlen($matches[0]);
     1039                    }
     1040                    else
     1041                    {
     1042                        $markup .= '`';
     1043
     1044                        $offset = 1;
     1045                    }
     1046
     1047                    break;
     1048
     1049                case 'http':
     1050
     1051                    if (preg_match('/^https?:[\/]{2}[^\s]+\b\/*/ui', $text, $matches))
     1052                    {
     1053                        $element_url = $matches[0];
     1054                        $element_url = str_replace('&', '&amp;', $element_url);
     1055                        $element_url = str_replace('<', '&lt;', $element_url);
     1056
     1057                        $markup .= '<a href="'.$element_url.'">'.$element_url.'</a>';
     1058
     1059                        $offset = strlen($matches[0]);
     1060                    }
     1061                    else
     1062                    {
     1063                        $markup .= 'http';
     1064
     1065                        $offset = 4;
     1066                    }
     1067
     1068                    break;
     1069
     1070                case '~~':
     1071
     1072                    if (preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $text, $matches))
     1073                    {
     1074                        $matches[1] = $this->parse_span_elements($matches[1], $markers);
     1075
     1076                        $markup .= '<del>'.$matches[1].'</del>';
     1077
     1078                        $offset = strlen($matches[0]);
     1079                    }
     1080                    else
     1081                    {
     1082                        $markup .= '~~';
     1083
     1084                        $offset = 2;
     1085                    }
     1086
     1087                    break;
     1088            }
     1089
     1090            if (isset($offset))
     1091            {
     1092                $text = substr($text, $offset);
     1093            }
     1094
     1095            $markers[$closest_marker_index] = $closest_marker;
     1096        }
     1097
     1098        return $markup;
     1099    }
     1100
     1101    #
     1102    # Fields
     1103    #
     1104
     1105    private $reference_map = array();
     1106
     1107    #
     1108    # Read-only
     1109
     1110    private static $strong_regex = array(
     1111        '*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
     1112        '_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us',
     1113    );
     1114
     1115    private static $em_regex = array(
     1116        '*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
     1117        '_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us',
     1118    );
     1119
     1120    private static $special_characters = array(
     1121        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!',
     1122    );
     1123
     1124    private static $text_level_elements = array(
     1125        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
     1126        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
     1127        'i', 'rp', 'sub', 'code',          'strike', 'marquee',
     1128        'q', 'rt', 'sup', 'font',          'strong',
     1129        's', 'tt', 'var', 'mark',
     1130        'u', 'xm', 'wbr', 'nobr',
     1131                          'ruby',
     1132                          'span',
     1133                          'time',
     1134    );
    9911135}
  • _plugins_/markdown/branches/v0.12.0/paquet.xml

    r82488 r82489  
    22        prefix="markdown"
    33        categorie="edition"
    4         version="0.4.1"
     4        version="0.4.2"
    55        etat="experimental"
    66        compatibilite="[3.0.0;3.0.*]"
Note: See TracChangeset for help on using the changeset viewer.