]>
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',