]>
git.bts.cx Git - cx.git/blob - cx/third_party/parsedown/Parsedown.php
  11 # For the full license information, view the LICENSE file that was distributed 
  12 # with this source code. 
  20     const version 
= '1.7.4'; 
  26         # make sure no definitions are set 
  27         $this->DefinitionData 
= array(); 
  29         # standardize line breaks 
  30         $text = str_replace(array("\r\n", "\r"), "\n", $text); 
  32         # remove surrounding line breaks 
  33         $text = trim($text, "\n"); 
  35         # split text into lines 
  36         $lines = explode("\n", $text); 
  38         # iterate through lines to identify blocks 
  39         $markup = $this->lines($lines); 
  42         $markup = trim($markup, "\n"); 
  51     function setBreaksEnabled($breaksEnabled) 
  53         $this->breaksEnabled 
= $breaksEnabled; 
  58     protected $breaksEnabled; 
  60     function setMarkupEscaped($markupEscaped) 
  62         $this->markupEscaped 
= $markupEscaped; 
  67     protected $markupEscaped; 
  69     function setUrlsLinked($urlsLinked) 
  71         $this->urlsLinked 
= $urlsLinked; 
  76     protected $urlsLinked = true; 
  78     function setSafeMode($safeMode) 
  80         $this->safeMode 
= (bool) $safeMode; 
  87     protected $safeLinksWhitelist = array( 
  93         'data:image/png;base64,', 
  94         'data:image/gif;base64,', 
  95         'data:image/jpeg;base64,', 
 108     protected $BlockTypes = array( 
 109         '#' => array('Header'), 
 110         '*' => array('Rule', 'List'), 
 111         '+' => array('List'), 
 112         '-' => array('SetextHeader', 'Table', 'Rule', 'List'), 
 113         '0' => array('List'), 
 114         '1' => array('List'), 
 115         '2' => array('List'), 
 116         '3' => array('List'), 
 117         '4' => array('List'), 
 118         '5' => array('List'), 
 119         '6' => array('List'), 
 120         '7' => array('List'), 
 121         '8' => array('List'), 
 122         '9' => array('List'), 
 123         ':' => array('Table'), 
 124         '<' => array('Comment', 'Markup'), 
 125         '=' => array('SetextHeader'), 
 126         '>' => array('Quote'), 
 127         '[' => array('Reference'), 
 128         '_' => array('Rule'), 
 129         '`' => array('FencedCode'), 
 130         '|' => array('Table'), 
 131         '~' => array('FencedCode'), 
 136     protected $unmarkedBlockTypes = array( 
 144     protected function lines(array $lines) 
 146         $CurrentBlock = null; 
 148         foreach ($lines as $line) 
 150             if (chop($line) === '') 
 152                 if (isset($CurrentBlock)) 
 154                     $CurrentBlock['interrupted'] = true; 
 160             if (strpos($line, "\t") !== false) 
 162                 $parts = explode("\t", $line); 
 168                 foreach ($parts as $part) 
 170                     $shortage = 4 - mb_strlen($line, 'utf-8') % 
4; 
 172                     $line .= str_repeat(' ', $shortage); 
 179             while (isset($line[$indent]) and $line[$indent] === ' ') 
 184             $text = $indent > 0 ? 
substr($line, $indent) : $line; 
 188             $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); 
 192             if (isset($CurrentBlock['continuable'])) 
 194                 $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); 
 198                     $CurrentBlock = $Block; 
 204                     if ($this->isBlockCompletable($CurrentBlock['type'])) 
 206                         $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); 
 217             $blockTypes = $this->unmarkedBlockTypes
; 
 219             if (isset($this->BlockTypes
[$marker])) 
 221                 foreach ($this->BlockTypes
[$marker] as $blockType) 
 223                     $blockTypes []= $blockType; 
 230             foreach ($blockTypes as $blockType) 
 232                 $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); 
 236                     $Block['type'] = $blockType; 
 238                     if ( ! isset($Block['identified'])) 
 240                         $Blocks []= $CurrentBlock; 
 242                         $Block['identified'] = true; 
 245                     if ($this->isBlockContinuable($blockType)) 
 247                         $Block['continuable'] = true; 
 250                     $CurrentBlock = $Block; 
 258             if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) 
 260                 $CurrentBlock['element']['text'] .= "\n".$text; 
 264                 $Blocks []= $CurrentBlock; 
 266                 $CurrentBlock = $this->paragraph($Line); 
 268                 $CurrentBlock['identified'] = true; 
 274         if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) 
 276             $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); 
 281         $Blocks []= $CurrentBlock; 
 289         foreach ($Blocks as $Block) 
 291             if (isset($Block['hidden'])) 
 297             $markup .= isset($Block['markup']) ? 
$Block['markup'] : $this->element($Block['element']); 
 307     protected function isBlockContinuable($Type) 
 309         return method_exists($this, 'block'.$Type.'Continue'); 
 312     protected function isBlockCompletable($Type) 
 314         return method_exists($this, 'block'.$Type.'Complete'); 
 320     protected function blockCode($Line, $Block = null) 
 322         if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) 
 327         if ($Line['indent'] >= 4) 
 329             $text = substr($Line['body'], 4); 
 334                     'handler' => 'element', 
 346     protected function blockCodeContinue($Line, $Block) 
 348         if ($Line['indent'] >= 4) 
 350             if (isset($Block['interrupted'])) 
 352                 $Block['element']['text']['text'] .= "\n"; 
 354                 unset($Block['interrupted']); 
 357             $Block['element']['text']['text'] .= "\n"; 
 359             $text = substr($Line['body'], 4); 
 361             $Block['element']['text']['text'] .= $text; 
 367     protected function blockCodeComplete($Block) 
 369         $text = $Block['element']['text']['text']; 
 371         $Block['element']['text']['text'] = $text; 
 379     protected function blockComment($Line) 
 381         if ($this->markupEscaped 
or $this->safeMode
) 
 386         if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') 
 389                 'markup' => $Line['body'], 
 392             if (preg_match('/-->$/', $Line['text'])) 
 394                 $Block['closed'] = true; 
 401     protected function blockCommentContinue($Line, array $Block) 
 403         if (isset($Block['closed'])) 
 408         $Block['markup'] .= "\n" . $Line['body']; 
 410         if (preg_match('/-->$/', $Line['text'])) 
 412             $Block['closed'] = true; 
 421     protected function blockFencedCode($Line) 
 423         if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) 
 430             if (isset($matches[1])) 
 433                  * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes 
 434                  * Every HTML element may have a class attribute specified. 
 435                  * The attribute, if specified, must have a value that is a set 
 436                  * of space-separated tokens representing the various classes 
 437                  * that the element belongs to. 
 439                  * The space characters, for the purposes of this specification, 
 440                  * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab), 
 441                  * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and 
 442                  * U+000D CARRIAGE RETURN (CR). 
 444                 $language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r")); 
 446                 $class = 'language-'.$language; 
 448                 $Element['attributes'] = array( 
 454                 'char' => $Line['text'][0], 
 457                     'handler' => 'element', 
 466     protected function blockFencedCodeContinue($Line, $Block) 
 468         if (isset($Block['complete'])) 
 473         if (isset($Block['interrupted'])) 
 475             $Block['element']['text']['text'] .= "\n"; 
 477             unset($Block['interrupted']); 
 480         if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) 
 482             $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); 
 484             $Block['complete'] = true; 
 489         $Block['element']['text']['text'] .= "\n".$Line['body']; 
 494     protected function blockFencedCodeComplete($Block) 
 496         $text = $Block['element']['text']['text']; 
 498         $Block['element']['text']['text'] = $text; 
 506     protected function blockHeader($Line) 
 508         if (isset($Line['text'][1])) 
 512             while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') 
 522             $text = trim($Line['text'], '# '); 
 526                     'name' => 'h' . min(6, $level), 
 539     protected function blockList($Line) 
 541         list($name, $pattern) = $Line['text'][0] <= '-' ? 
array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); 
 543         if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) 
 546                 'indent' => $Line['indent'], 
 547                 'pattern' => $pattern, 
 550                     'handler' => 'elements', 
 556                 $listStart = stristr($matches[0], '.', true); 
 558                 if($listStart !== '1') 
 560                     $Block['element']['attributes'] = array('start' => $listStart); 
 564             $Block['li'] = array( 
 572             $Block['element']['text'] []= & $Block['li']; 
 578     protected function blockListContinue($Line, array $Block) 
 580         if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) 
 582             if (isset($Block['interrupted'])) 
 584                 $Block['li']['text'] []= ''; 
 586                 $Block['loose'] = true; 
 588                 unset($Block['interrupted']); 
 593             $text = isset($matches[1]) ? 
$matches[1] : ''; 
 595             $Block['li'] = array( 
 603             $Block['element']['text'] []= & $Block['li']; 
 608         if ($Line['text'][0] === '[' and $this->blockReference($Line)) 
 613         if ( ! isset($Block['interrupted'])) 
 615             $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); 
 617             $Block['li']['text'] []= $text; 
 622         if ($Line['indent'] > 0) 
 624             $Block['li']['text'] []= ''; 
 626             $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); 
 628             $Block['li']['text'] []= $text; 
 630             unset($Block['interrupted']); 
 636     protected function blockListComplete(array $Block) 
 638         if (isset($Block['loose'])) 
 640             foreach ($Block['element']['text'] as &$li) 
 642                 if (end($li['text']) !== '') 
 655     protected function blockQuote($Line) 
 657         if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) 
 661                     'name' => 'blockquote', 
 662                     'handler' => 'lines', 
 663                     'text' => (array) $matches[1], 
 671     protected function blockQuoteContinue($Line, array $Block) 
 673         if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) 
 675             if (isset($Block['interrupted'])) 
 677                 $Block['element']['text'] []= ''; 
 679                 unset($Block['interrupted']); 
 682             $Block['element']['text'] []= $matches[1]; 
 687         if ( ! isset($Block['interrupted'])) 
 689             $Block['element']['text'] []= $Line['text']; 
 698     protected function blockRule($Line) 
 700         if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) 
 715     protected function blockSetextHeader($Line, array $Block = null) 
 717         if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) 
 722         if (chop($Line['text'], $Line['text'][0]) === '') 
 724             $Block['element']['name'] = $Line['text'][0] === '=' ? 
'h1' : 'h2'; 
 733     protected function blockMarkup($Line) 
 735         if ($this->markupEscaped 
or $this->safeMode
) 
 740         if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute
.')*[ ]*(\/)?>/', $Line['text'], $matches)) 
 742             $element = strtolower($matches[1]); 
 744             if (in_array($element, $this->textLevelElements
)) 
 750                 'name' => $matches[1], 
 752                 'markup' => $Line['text'], 
 755             $length = strlen($matches[0]); 
 757             $remainder = substr($Line['text'], $length); 
 759             if (trim($remainder) === '') 
 761                 if (isset($matches[2]) or in_array($matches[1], $this->voidElements
)) 
 763                     $Block['closed'] = true; 
 765                     $Block['void'] = true; 
 770                 if (isset($matches[2]) or in_array($matches[1], $this->voidElements
)) 
 775                 if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) 
 777                     $Block['closed'] = true; 
 785     protected function blockMarkupContinue($Line, array $Block) 
 787         if (isset($Block['closed'])) 
 792         if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute
.')*[ ]*>/i', $Line['text'])) # open 
 797         if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close 
 799             if ($Block['depth'] > 0) 
 805                 $Block['closed'] = true; 
 809         if (isset($Block['interrupted'])) 
 811             $Block['markup'] .= "\n"; 
 813             unset($Block['interrupted']); 
 816         $Block['markup'] .= "\n".$Line['body']; 
 824     protected function blockReference($Line) 
 826         if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) 
 828             $id = strtolower($matches[1]); 
 831                 'url' => $matches[2], 
 835             if (isset($matches[3])) 
 837                 $Data['title'] = $matches[3]; 
 840             $this->DefinitionData
['Reference'][$id] = $Data; 
 853     protected function blockTable($Line, array $Block = null) 
 855         if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) 
 860         if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') 
 862             $alignments = array(); 
 864             $divider = $Line['text']; 
 866             $divider = trim($divider); 
 867             $divider = trim($divider, '|'); 
 869             $dividerCells = explode('|', $divider); 
 871             foreach ($dividerCells as $dividerCell) 
 873                 $dividerCell = trim($dividerCell); 
 875                 if ($dividerCell === '') 
 882                 if ($dividerCell[0] === ':') 
 887                 if (substr($dividerCell, - 1) === ':') 
 889                     $alignment = $alignment === 'left' ? 
'center' : 'right'; 
 892                 $alignments []= $alignment; 
 897             $HeaderElements = array(); 
 899             $header = $Block['element']['text']; 
 901             $header = trim($header); 
 902             $header = trim($header, '|'); 
 904             $headerCells = explode('|', $header); 
 906             foreach ($headerCells as $index => $headerCell) 
 908                 $headerCell = trim($headerCell); 
 910                 $HeaderElement = array( 
 912                     'text' => $headerCell, 
 916                 if (isset($alignments[$index])) 
 918                     $alignment = $alignments[$index]; 
 920                     $HeaderElement['attributes'] = array( 
 921                         'style' => 'text-align: '.$alignment.';', 
 925                 $HeaderElements []= $HeaderElement; 
 931                 'alignments' => $alignments, 
 932                 'identified' => true, 
 935                     'handler' => 'elements', 
 939             $Block['element']['text'] []= array( 
 941                 'handler' => 'elements', 
 944             $Block['element']['text'] []= array( 
 946                 'handler' => 'elements', 
 950             $Block['element']['text'][0]['text'] []= array( 
 952                 'handler' => 'elements', 
 953                 'text' => $HeaderElements, 
 960     protected function blockTableContinue($Line, array $Block) 
 962         if (isset($Block['interrupted'])) 
 967         if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) 
 971             $row = $Line['text']; 
 974             $row = trim($row, '|'); 
 976             preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); 
 978             foreach ($matches[0] as $index => $cell) 
 988                 if (isset($Block['alignments'][$index])) 
 990                     $Element['attributes'] = array( 
 991                         'style' => 'text-align: '.$Block['alignments'][$index].';', 
 995                 $Elements []= $Element; 
1000                 'handler' => 'elements', 
1001                 'text' => $Elements, 
1004             $Block['element']['text'][1]['text'] []= $Element; 
1014     protected function paragraph($Line) 
1019                 'text' => $Line['text'], 
1020                 'handler' => 'line', 
1031     protected $InlineTypes = array( 
1032         '"' => array('SpecialCharacter'), 
1033         '!' => array('Image'), 
1034         '&' => array('SpecialCharacter'), 
1035         '*' => array('Emphasis'), 
1036         ':' => array('Url'), 
1037         '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), 
1038         '>' => array('SpecialCharacter'), 
1039         '[' => array('Link'), 
1040         '_' => array('Emphasis'), 
1041         '`' => array('Code'), 
1042         '~' => array('Strikethrough'), 
1043         '\\' => array('EscapeSequence'), 
1048     protected $inlineMarkerList = '!"*_&[:<>`~\\'; 
1054     public function line($text, $nonNestables=array()) 
1058         # $excerpt is based on the first occurrence of a marker 
1060         while ($excerpt = strpbrk($text, $this->inlineMarkerList
)) 
1062             $marker = $excerpt[0]; 
1064             $markerPosition = strpos($text, $marker); 
1066             $Excerpt = array('text' => $excerpt, 'context' => $text); 
1068             foreach ($this->InlineTypes
[$marker] as $inlineType) 
1070                 # check to see if the current inline type is nestable in the current context 
1072                 if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) 
1077                 $Inline = $this->{'inline'.$inlineType}($Excerpt); 
1079                 if ( ! isset($Inline)) 
1084                 # makes sure that the inline belongs to "our" marker 
1086                 if (isset($Inline['position']) and $Inline['position'] > $markerPosition) 
1091                 # sets a default inline position 
1093                 if ( ! isset($Inline['position'])) 
1095                     $Inline['position'] = $markerPosition; 
1098                 # cause the new element to 'inherit' our non nestables 
1100                 foreach ($nonNestables as $non_nestable) 
1102                     $Inline['element']['nonNestables'][] = $non_nestable; 
1105                 # the text that comes before the inline 
1106                 $unmarkedText = substr($text, 0, $Inline['position']); 
1108                 # compile the unmarked text 
1109                 $markup .= $this->unmarkedText($unmarkedText); 
1111                 # compile the inline 
1112                 $markup .= isset($Inline['markup']) ? 
$Inline['markup'] : $this->element($Inline['element']); 
1114                 # remove the examined text 
1115                 $text = substr($text, $Inline['position'] + 
$Inline['extent']); 
1120             # the marker does not belong to an inline 
1122             $unmarkedText = substr($text, 0, $markerPosition + 
1); 
1124             $markup .= $this->unmarkedText($unmarkedText); 
1126             $text = substr($text, $markerPosition + 
1); 
1129         $markup .= $this->unmarkedText($text); 
1138     protected function inlineCode($Excerpt) 
1140         $marker = $Excerpt['text'][0]; 
1142         if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches)) 
1144             $text = $matches[2]; 
1145             $text = preg_replace("/[ ]*\n/", ' ', $text); 
1148                 'extent' => strlen($matches[0]), 
1157     protected function inlineEmailTag($Excerpt) 
1159         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) 
1163             if ( ! isset($matches[2])) 
1165                 $url = 'mailto:' . $url; 
1169                 'extent' => strlen($matches[0]), 
1172                     'text' => $matches[1], 
1173                     'attributes' => array( 
1181     protected function inlineEmphasis($Excerpt) 
1183         if ( ! isset($Excerpt['text'][1])) 
1188         $marker = $Excerpt['text'][0]; 
1190         if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex
[$marker], $Excerpt['text'], $matches)) 
1192             $emphasis = 'strong'; 
1194         elseif (preg_match($this->EmRegex
[$marker], $Excerpt['text'], $matches)) 
1204             'extent' => strlen($matches[0]), 
1206                 'name' => $emphasis, 
1207                 'handler' => 'line', 
1208                 'text' => $matches[1], 
1213     protected function inlineEscapeSequence($Excerpt) 
1215         if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters
)) 
1218                 'markup' => $Excerpt['text'][1], 
1224     protected function inlineImage($Excerpt) 
1226         if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') 
1231         $Excerpt['text']= substr($Excerpt['text'], 1); 
1233         $Link = $this->inlineLink($Excerpt); 
1241             'extent' => $Link['extent'] + 
1, 
1244                 'attributes' => array( 
1245                     'src' => $Link['element']['attributes']['href'], 
1246                     'alt' => $Link['element']['text'], 
1251         $Inline['element']['attributes'] +
= $Link['element']['attributes']; 
1253         unset($Inline['element']['attributes']['href']); 
1258     protected function inlineLink($Excerpt) 
1262             'handler' => 'line', 
1263             'nonNestables' => array('Url', 'Link'), 
1265             'attributes' => array( 
1273         $remainder = $Excerpt['text']; 
1275         if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) 
1277             $Element['text'] = $matches[1]; 
1279             $extent +
= strlen($matches[0]); 
1281             $remainder = substr($remainder, $extent); 
1288         if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) 
1290             $Element['attributes']['href'] = $matches[1]; 
1292             if (isset($matches[2])) 
1294                 $Element['attributes']['title'] = substr($matches[2], 1, - 1); 
1297             $extent +
= strlen($matches[0]); 
1301             if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) 
1303                 $definition = strlen($matches[1]) ? 
$matches[1] : $Element['text']; 
1304                 $definition = strtolower($definition); 
1306                 $extent +
= strlen($matches[0]); 
1310                 $definition = strtolower($Element['text']); 
1313             if ( ! isset($this->DefinitionData
['Reference'][$definition])) 
1318             $Definition = $this->DefinitionData
['Reference'][$definition]; 
1320             $Element['attributes']['href'] = $Definition['url']; 
1321             $Element['attributes']['title'] = $Definition['title']; 
1325             'extent' => $extent, 
1326             'element' => $Element, 
1330     protected function inlineMarkup($Excerpt) 
1332         if ($this->markupEscaped 
or $this->safeMode 
or strpos($Excerpt['text'], '>') === false) 
1337         if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) 
1340                 'markup' => $matches[0], 
1341                 'extent' => strlen($matches[0]), 
1345         if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches)) 
1348                 'markup' => $matches[0], 
1349                 'extent' => strlen($matches[0]), 
1353         if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute
.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) 
1356                 'markup' => $matches[0], 
1357                 'extent' => strlen($matches[0]), 
1362     protected function inlineSpecialCharacter($Excerpt) 
1364         if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) 
1367                 'markup' => '&', 
1372         $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); 
1374         if (isset($SpecialCharacter[$Excerpt['text'][0]])) 
1377                 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', 
1383     protected function inlineStrikethrough($Excerpt) 
1385         if ( ! isset($Excerpt['text'][1])) 
1390         if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) 
1393                 'extent' => strlen($matches[0]), 
1396                     'text' => $matches[1], 
1397                     'handler' => 'line', 
1403     protected function inlineUrl($Excerpt) 
1405         if ($this->urlsLinked 
!== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') 
1410         if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE
)) 
1412             $url = $matches[0][0]; 
1415                 'extent' => strlen($matches[0][0]), 
1416                 'position' => $matches[0][1], 
1420                     'attributes' => array( 
1430     protected function inlineUrlTag($Excerpt) 
1432         if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) 
1437                 'extent' => strlen($matches[0]), 
1441                     'attributes' => array( 
1451     protected function unmarkedText($text) 
1453         if ($this->breaksEnabled
) 
1455             $text = preg_replace('/[ ]*\n/', "<br />\n", $text); 
1459             $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text); 
1460             $text = str_replace(" \n", "\n", $text); 
1470     protected function element(array $Element) 
1472         if ($this->safeMode
) 
1474             $Element = $this->sanitiseElement($Element); 
1477         $markup = '<'.$Element['name']; 
1479         if (isset($Element['attributes'])) 
1481             foreach ($Element['attributes'] as $name => $value) 
1483                 if ($value === null) 
1488                 $markup .= ' '.$name.'="'.self
::escape($value).'"'; 
1492         $permitRawHtml = false; 
1494         if (isset($Element['text'])) 
1496             $text = $Element['text']; 
1498         // very strongly consider an alternative if you're writing an 
1500         elseif (isset($Element['rawHtml'])) 
1502             $text = $Element['rawHtml']; 
1503             $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; 
1504             $permitRawHtml = !$this->safeMode || 
$allowRawHtmlInSafeMode; 
1511             if (!isset($Element['nonNestables'])) 
1513                 $Element['nonNestables'] = array(); 
1516             if (isset($Element['handler'])) 
1518                 $markup .= $this->{$Element['handler']}($text, $Element['nonNestables']); 
1520             elseif (!$permitRawHtml) 
1522                 $markup .= self
::escape($text, true); 
1529             $markup .= '</'.$Element['name'].'>'; 
1539     protected function elements(array $Elements) 
1543         foreach ($Elements as $Element) 
1545             $markup .= "\n" . $this->element($Element); 
1555     protected function li($lines) 
1557         $markup = $this->lines($lines); 
1559         $trimmedMarkup = trim($markup); 
1561         if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>') 
1563             $markup = $trimmedMarkup; 
1564             $markup = substr($markup, 3); 
1566             $position = strpos($markup, "</p>"); 
1568             $markup = substr_replace($markup, '', $position, 4); 
1575     # Deprecated Methods 
1578     function parse($text) 
1580         $markup = $this->text($text); 
1585     protected function sanitiseElement(array $Element) 
1587         static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/'; 
1588         static $safeUrlNameToAtt  = array( 
1593         if (isset($safeUrlNameToAtt[$Element['name']])) 
1595             $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); 
1598         if ( ! empty($Element['attributes'])) 
1600             foreach ($Element['attributes'] as $att => $val) 
1602                 # filter out badly parsed attribute 
1603                 if ( ! preg_match($goodAttribute, $att)) 
1605                     unset($Element['attributes'][$att]); 
1607                 # dump onevent attribute 
1608                 elseif (self
::striAtStart($att, 'on')) 
1610                     unset($Element['attributes'][$att]); 
1618     protected function filterUnsafeUrlInAttribute(array $Element, $attribute) 
1620         foreach ($this->safeLinksWhitelist 
as $scheme) 
1622             if (self
::striAtStart($Element['attributes'][$attribute], $scheme)) 
1628         $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]); 
1637     protected static function escape($text, $allowQuotes = false) 
1639         return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES 
: ENT_QUOTES
, 'UTF-8'); 
1642     protected static function striAtStart($string, $needle) 
1644         $len = strlen($needle); 
1646         if ($len > strlen($string)) 
1652             return strtolower(substr($string, 0, $len)) === strtolower($needle); 
1656     static function instance($name = 'default') 
1658         if (isset(self
::$instances[$name])) 
1660             return self
::$instances[$name]; 
1663         $instance = new static(); 
1665         self
::$instances[$name] = $instance; 
1670     private static $instances = array(); 
1676     protected $DefinitionData; 
1681     protected $specialCharacters = array( 
1682         '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', 
1685     protected $StrongRegex = array( 
1686         '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', 
1687         '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', 
1690     protected $EmRegex = array( 
1691         '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', 
1692         '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', 
1695     protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; 
1697     protected $voidElements = array( 
1698         'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 
1701     protected $textLevelElements = array( 
1702         'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', 
1703         'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', 
1704         'i', 'rp', 'del', 'code',          'strike', 'marquee', 
1705         'q', 'rt', 'ins', 'font',          'strong', 
1706         's', 'tt', 'kbd', 'mark', 
1707         'u', 'xm', 'sub', 'nobr',