1
0
mirror of https://github.com/erusev/parsedown.git synced 2023-08-10 21:13:06 +03:00

Compare commits

...

46 Commits

Author SHA1 Message Date
fd95703da5 Version bump 2018-05-07 14:26:12 +01:00
8d172a2994 Merge pull request #614 from aidantwoods/enhancement/performance-tweaks
General and performance tweaks
2018-05-07 11:18:21 +01:00
dfab7240a4 Merge pull request #621 from paukenba/master
Tilde characters may be escaped
2018-04-24 17:39:09 +01:00
113c6d2b21 Tilde characters may be escaped 2018-04-23 15:09:30 +02:00
a9764ec90f Remove complex string interpolation expressions 2018-04-14 15:27:06 +01:00
0a842fb5b1 Merge pull request #615 from aidantwoods/fix/old-handler-compat
Compatability fixes
2018-04-12 22:29:29 +01:00
7f4318dbdb PHP 5.3 == 💩 2018-04-12 22:22:53 +01:00
3e70819a20 Readability improvements, thanks @PhrozenByte 2018-04-12 22:16:25 +01:00
2bf7ca41a0 Add compat for extensions using old markup key. 2018-04-12 21:25:50 +01:00
b75fd409ff Must unset text key so that our destination is preferred as content 2018-04-12 21:10:09 +01:00
88a3f31dd7 Rewrite as one statement 2018-04-12 19:33:01 +01:00
726d4ef44a Sanity checks before starting regex engine 2018-04-09 18:09:45 +01:00
450a74fedf More expensive statement last 2018-04-09 18:09:45 +01:00
7e15d99d90 Remove regex from block rule 2018-04-09 18:09:44 +01:00
d2dd736e1b Remove regex from fenced code block
Also remove unused function
2018-04-09 18:09:44 +01:00
e74a5bd7ed In theory PHP stores the length of strings, so looking this up should be quick 2018-04-09 18:09:44 +01:00
b53aa74a72 Use standard library function 2018-04-09 18:09:44 +01:00
3ea08140b6 Remove use of array 2018-04-09 18:09:44 +01:00
c45e41950f Use standard library over while loop 2018-04-09 18:09:44 +01:00
2faba6fef5 Remove unneeded complete function 2018-04-09 18:09:44 +01:00
b42add3762 Make some regexes possesive 2018-04-09 18:09:43 +01:00
107223d3a0 Avoid recomputation 2018-04-09 18:09:43 +01:00
d4f1ac465c String interpolation is slightly faster than concat 2018-04-09 18:09:43 +01:00
d6e306d620 Optimise commonly used regexes to fail fast 2018-04-09 18:09:04 +01:00
dc5cf8770b The AST has high complexity here (and so traversal is hard anyway)
We gain quite a bit of a speed boost by working with text here
since this is a very common function
2018-04-09 18:09:04 +01:00
70f5c02d47 Use non-nestable values as keys for O(1) lookup 2018-04-09 18:09:04 +01:00
90ad738933 General readability 2018-04-09 18:09:04 +01:00
f2327023c1 No need to unset if not set 2018-04-09 18:09:04 +01:00
6f13f97674 Use mutating loop instead of array_map 2018-04-09 18:08:58 +01:00
8091e5586a Merge pull request #612 from aidantwoods/fix/table-columns
Table header should not be allowed to contain new lines
2018-04-09 16:53:07 +01:00
cb33daf0e6 Assert table header does not contain new lines 2018-04-09 16:38:03 +01:00
c440c91af5 Add failing test case 2018-04-09 16:32:36 +01:00
3514881e14 Merge pull request #611 from aidantwoods/enhancement/paragraph-block-semantics
Paragraph block semantics
2018-04-09 16:30:33 +01:00
043c55e4c6 Give paragraph block semantics for overloading 2018-04-09 15:12:17 +01:00
e4cd13350b Remove setLiteralBreaks 2018-04-09 15:11:45 +01:00
ae8067e862 Swap undefined type for type === 'Paragraph' for ease of reading
The way in which we use this assumes that it is a paragraph, for example
appending text into the handler argument — so there is no loss of
generality here, we're simply being explicit.
2018-04-09 14:48:48 +01:00
5353ebb524 Avoid needing two arrays
We only need to collect elements, we can discard finished blocks
2018-04-09 14:48:39 +01:00
39df7d4f8e Swap 'hidden' blocks for empty elements 2018-04-09 14:46:24 +01:00
50f15add44 Merge pull request #610 from aidantwoods/fix/lost-line-breaks
Fix lost line breaks
2018-04-09 14:19:38 +01:00
3f5b0ee781 Count number of interrupts 2018-04-09 14:13:10 +01:00
9a021b2130 Add failing test cases 2018-04-09 14:11:49 +01:00
43d25a74fe Fix function name 2018-04-08 18:40:50 +01:00
1d68e5506c Merge pull request #608 from aidantwoods/fix/recursion
Add seperate depth-first function instead of replacing recursive method
2018-04-08 18:02:17 +01:00
86940be224 Use mutating loop instead of creating new array 2018-04-08 17:49:36 +01:00
cdaf86b039 Add seperate depth-first function instead of replacing recursive method 2018-04-08 17:39:24 +01:00
1d65fb858a Restore file permission to that of 1.7.1 2018-04-08 14:30:23 +01:00
12 changed files with 275 additions and 250 deletions

402
Parsedown.php Executable file → Normal file
View File

@ -17,7 +17,7 @@ class Parsedown
{ {
# ~ # ~
const version = '1.8.0-beta-1'; const version = '1.8.0-beta-3';
# ~ # ~
@ -65,15 +65,6 @@ class Parsedown
protected $breaksEnabled; protected $breaksEnabled;
function setLiteralBreaks($literalBreaks)
{
$this->literalBreaks = $literalBreaks;
return $this;
}
protected $literalBreaks;
function setMarkupEscaped($markupEscaped) function setMarkupEscaped($markupEscaped)
{ {
$this->markupEscaped = $markupEscaped; $this->markupEscaped = $markupEscaped;
@ -174,43 +165,34 @@ class Parsedown
protected function linesElements(array $lines) protected function linesElements(array $lines)
{ {
$Elements = array();
$CurrentBlock = null; $CurrentBlock = null;
foreach ($lines as $line) foreach ($lines as $line)
{ {
if ( ! $this->literalBreaks and chop($line) === '') if (chop($line) === '')
{ {
if (isset($CurrentBlock)) if (isset($CurrentBlock))
{ {
$CurrentBlock['interrupted'] = true; $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
? $CurrentBlock['interrupted'] + 1 : 1
);
} }
continue; continue;
} }
if (strpos($line, "\t") !== false) while (($beforeTab = strstr($line, "\t", true)) !== false)
{ {
$parts = explode("\t", $line); $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
$line = $parts[0]; $line = $beforeTab
. str_repeat(' ', $shortage)
unset($parts[0]); . substr($line, strlen($beforeTab) + 1)
;
foreach ($parts as $part)
{
$shortage = 4 - mb_strlen($line, 'utf-8') % 4;
$line .= str_repeat(' ', $shortage);
$line .= $part;
}
} }
$indent = 0; $indent = strspn($line, ' ');
while (isset($line[$indent]) and $line[$indent] === ' ')
{
$indent ++;
}
$text = $indent > 0 ? substr($line, $indent) : $line; $text = $indent > 0 ? substr($line, $indent) : $line;
@ -222,7 +204,8 @@ class Parsedown
if (isset($CurrentBlock['continuable'])) if (isset($CurrentBlock['continuable']))
{ {
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
$Block = $this->$methodName($Line, $CurrentBlock);
if (isset($Block)) if (isset($Block))
{ {
@ -234,22 +217,15 @@ class Parsedown
{ {
if ($this->isBlockCompletable($CurrentBlock['type'])) if ($this->isBlockCompletable($CurrentBlock['type']))
{ {
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
$CurrentBlock = $this->$methodName($CurrentBlock);
} }
} }
} }
# ~ # ~
if (isset($text[0]))
{
$marker = $text[0]; $marker = $text[0];
}
elseif ($this->literalBreaks)
{
$marker = '\n';
$text = ' ';
}
# ~ # ~
@ -268,7 +244,7 @@ class Parsedown
foreach ($blockTypes as $blockType) foreach ($blockTypes as $blockType)
{ {
$Block = $this->{'block'.$blockType}($Line, $CurrentBlock); $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
if (isset($Block)) if (isset($Block))
{ {
@ -276,7 +252,10 @@ class Parsedown
if ( ! isset($Block['identified'])) if ( ! isset($Block['identified']))
{ {
$Blocks []= $CurrentBlock; if (isset($CurrentBlock))
{
$Elements[] = $this->extractElement($CurrentBlock);
}
$Block['identified'] = true; $Block['identified'] = true;
} }
@ -294,17 +273,21 @@ class Parsedown
# ~ # ~
if ( if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
isset($CurrentBlock) {
and isset($CurrentBlock['element']['name']) $Block = $this->paragraphContinue($Line, $CurrentBlock);
and $CurrentBlock['element']['name'] === 'p' }
and ! isset($CurrentBlock['interrupted'])
) { if (isset($Block))
$CurrentBlock['element']['handler']['argument'] .= "\n".$text; {
$CurrentBlock = $Block;
} }
else else
{ {
$Blocks []= $CurrentBlock; if (isset($CurrentBlock))
{
$Elements[] = $this->extractElement($CurrentBlock);
}
$CurrentBlock = $this->paragraph($Line); $CurrentBlock = $this->paragraph($Line);
@ -316,27 +299,15 @@ class Parsedown
if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
{ {
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
$CurrentBlock = $this->$methodName($CurrentBlock);
} }
# ~ # ~
$Blocks []= $CurrentBlock; if (isset($CurrentBlock))
unset($Blocks[0]);
# ~
$Elements = array();
foreach ($Blocks as $Block)
{ {
if (isset($Block['hidden'])) $Elements[] = $this->extractElement($CurrentBlock);
{
continue;
}
$Elements[] = $Block['element'];
} }
# ~ # ~
@ -344,6 +315,16 @@ class Parsedown
return $Elements; return $Elements;
} }
protected function extractElement(array $Component)
{
if ( ! isset($Component['element']) and isset($Component['markup']))
{
$Component['element'] = array('rawHtml' => $Component['markup']);
}
return $Component['element'];
}
protected function isBlockContinuable($Type) protected function isBlockContinuable($Type)
{ {
return method_exists($this, 'block' . $Type . 'Continue'); return method_exists($this, 'block' . $Type . 'Continue');
@ -359,7 +340,7 @@ class Parsedown
protected function blockCode($Line, $Block = null) protected function blockCode($Line, $Block = null)
{ {
if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
{ {
return; return;
} }
@ -388,7 +369,7 @@ class Parsedown
{ {
if (isset($Block['interrupted'])) if (isset($Block['interrupted']))
{ {
$Block['element']['element']['text'] .= "\n"; $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
unset($Block['interrupted']); unset($Block['interrupted']);
} }
@ -403,15 +384,6 @@ class Parsedown
} }
} }
protected function blockCodeComplete($Block)
{
$text = $Block['element']['element']['text'];
$Block['element']['element']['text'] = $text;
return $Block;
}
# #
# Comment # Comment
@ -462,25 +434,35 @@ class Parsedown
protected function blockFencedCode($Line) protected function blockFencedCode($Line)
{ {
if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches)) $marker = $Line['text'][0];
$openerLength = strspn($Line['text'], $marker);
if ($openerLength < 3)
{ {
return;
}
$infostring = trim(substr($Line['text'], $openerLength), "\t ");
if (strpos($infostring, '`') !== false)
{
return;
}
$Element = array( $Element = array(
'name' => 'code', 'name' => 'code',
'text' => '', 'text' => '',
); );
if (isset($matches[2])) if ($infostring !== '')
{ {
$class = 'language-'.$matches[2]; $Element['attributes'] = array('class' => "language-$infostring");
$Element['attributes'] = array(
'class' => $class,
);
} }
$Block = array( $Block = array(
'char' => $Line['text'][0], 'char' => $marker,
'openerLength' => mb_strlen($matches[1]), 'openerLength' => $openerLength,
'element' => array( 'element' => array(
'name' => 'pre', 'name' => 'pre',
'element' => $Element, 'element' => $Element,
@ -489,7 +471,6 @@ class Parsedown
return $Block; return $Block;
} }
}
protected function blockFencedCodeContinue($Line, $Block) protected function blockFencedCodeContinue($Line, $Block)
{ {
@ -500,14 +481,13 @@ class Parsedown
if (isset($Block['interrupted'])) if (isset($Block['interrupted']))
{ {
$Block['element']['element']['text'] .= "\n"; $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
unset($Block['interrupted']); unset($Block['interrupted']);
} }
if ( if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
preg_match('/^(['.preg_quote($Block['char']).']{3,})[ ]*$/', $Line['text'], $matches) and chop(substr($Line['text'], $len), ' ') === ''
and mb_strlen($matches[1]) >= $Block['openerLength']
) { ) {
$Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1); $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
@ -521,26 +501,12 @@ class Parsedown
return $Block; return $Block;
} }
protected function blockFencedCodeComplete($Block)
{
$text = $Block['element']['element']['text'];
$Block['element']['element']['text'] = $text;
return $Block;
}
# #
# Header # Header
protected function blockHeader($Line) protected function blockHeader($Line)
{ {
$level = 1; $level = strspn($Line['text'], '#');
while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
{
$level ++;
}
if ($level > 6) if ($level > 6)
{ {
@ -575,9 +541,9 @@ class Parsedown
protected function blockList($Line, array $CurrentBlock = null) protected function blockList($Line, array $CurrentBlock = null)
{ {
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}[.\)]'); list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
if (preg_match('/^('.$pattern.'([ ]+|$))(.*)/', $Line['text'], $matches)) if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
{ {
$contentIndent = strlen($matches[2]); $contentIndent = strlen($matches[2]);
@ -592,19 +558,22 @@ class Parsedown
$matches[1] .= ' '; $matches[1] .= ' ';
} }
$markerWithoutWhitespace = strstr($matches[1], ' ', true);
$Block = array( $Block = array(
'indent' => $Line['indent'], 'indent' => $Line['indent'],
'pattern' => $pattern, 'pattern' => $pattern,
'data' => array( 'data' => array(
'type' => $name, 'type' => $name,
'marker' => $matches[1], 'marker' => $matches[1],
'markerType' => ($name === 'ul' ? strstr($matches[1], ' ', true) : substr(strstr($matches[1], ' ', true), -1)), 'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
), ),
'element' => array( 'element' => array(
'name' => $name, 'name' => $name,
'elements' => array(), 'elements' => array(),
), ),
); );
$Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
if ($name === 'ol') if ($name === 'ol')
{ {
@ -614,7 +583,7 @@ class Parsedown
{ {
if ( if (
isset($CurrentBlock) isset($CurrentBlock)
and ! isset($CurrentBlock['type']) and $CurrentBlock['type'] === 'Paragraph'
and ! isset($CurrentBlock['interrupted']) and ! isset($CurrentBlock['interrupted'])
) { ) {
return; return;
@ -652,10 +621,10 @@ class Parsedown
and ( and (
( (
$Block['data']['type'] === 'ol' $Block['data']['type'] === 'ol'
and preg_match('/^[0-9]+'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
) or ( ) or (
$Block['data']['type'] === 'ul' $Block['data']['type'] === 'ul'
and preg_match('/^'.preg_quote($Block['data']['markerType']).'(?:[ ]+(.*)|$)/', $Line['text'], $matches) and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
) )
) )
) { ) {
@ -717,7 +686,7 @@ class Parsedown
if ( ! isset($Block['interrupted'])) if ( ! isset($Block['interrupted']))
{ {
$text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']); $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
$Block['li']['handler']['argument'] []= $text; $Block['li']['handler']['argument'] []= $text;
@ -746,7 +715,7 @@ class Parsedown
protected function blockQuote($Line) protected function blockQuote($Line)
{ {
if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
{ {
$Block = array( $Block = array(
'element' => array( 'element' => array(
@ -770,7 +739,7 @@ class Parsedown
return; return;
} }
if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
{ {
$Block['element']['handler']['argument'] []= $matches[1]; $Block['element']['handler']['argument'] []= $matches[1];
@ -790,7 +759,9 @@ class Parsedown
protected function blockRule($Line) protected function blockRule($Line)
{ {
if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) $marker = $Line['text'][0];
if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
{ {
$Block = array( $Block = array(
'element' => array( 'element' => array(
@ -807,15 +778,13 @@ class Parsedown
protected function blockSetextHeader($Line, array $Block = null) protected function blockSetextHeader($Line, array $Block = null)
{ {
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
{ {
return; return;
} }
if ( if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
chop(chop($Line['text'], ' '), $Line['text'][0]) === '' {
and $Line['indent'] < 4
) {
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
return $Block; return $Block;
@ -832,7 +801,7 @@ class Parsedown
return; return;
} }
if (preg_match('/^<[\/]?+(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
{ {
$element = strtolower($matches[1]); $element = strtolower($matches[1]);
@ -870,24 +839,20 @@ class Parsedown
protected function blockReference($Line) protected function blockReference($Line)
{ {
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) if (strpos($Line['text'], ']') !== false
{ and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
) {
$id = strtolower($matches[1]); $id = strtolower($matches[1]);
$Data = array( $Data = array(
'url' => $matches[2], 'url' => $matches[2],
'title' => null, 'title' => isset($matches[3]) ? $matches[3] : null,
); );
if (isset($matches[3]))
{
$Data['title'] = $matches[3];
}
$this->DefinitionData['Reference'][$id] = $Data; $this->DefinitionData['Reference'][$id] = $Data;
$Block = array( $Block = array(
'hidden' => true, 'element' => array(),
); );
return $Block; return $Block;
@ -899,7 +864,7 @@ class Parsedown
protected function blockTable($Line, array $Block = null) protected function blockTable($Line, array $Block = null)
{ {
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
{ {
return; return;
} }
@ -908,6 +873,7 @@ class Parsedown
strpos($Block['element']['handler']['argument'], '|') === false strpos($Block['element']['handler']['argument'], '|') === false
and strpos($Line['text'], '|') === false and strpos($Line['text'], '|') === false
and strpos($Line['text'], ':') === false and strpos($Line['text'], ':') === false
or strpos($Block['element']['handler']['argument'], "\n") !== false
) { ) {
return; return;
} }
@ -984,7 +950,7 @@ class Parsedown
$alignment = $alignments[$index]; $alignment = $alignments[$index];
$HeaderElement['attributes'] = array( $HeaderElement['attributes'] = array(
'style' => 'text-align: '.$alignment.';', 'style' => "text-align: $alignment;",
); );
} }
@ -1035,7 +1001,7 @@ class Parsedown
$row = trim($row); $row = trim($row);
$row = trim($row, '|'); $row = trim($row, '|');
preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
$cells = array_slice($matches[0], 0, count($Block['alignments'])); $cells = array_slice($matches[0], 0, count($Block['alignments']));
@ -1079,16 +1045,27 @@ class Parsedown
protected function paragraph($Line) protected function paragraph($Line)
{ {
$Block = array( return array(
'type' => 'Paragraph',
'element' => array( 'element' => array(
'name' => 'p', 'name' => 'p',
'handler' => array( 'handler' => array(
'function' => 'lineElements', 'function' => 'lineElements',
'argument' => $Line['text'], 'argument' => $Line['text'],
'destination' => 'elements', 'destination' => 'elements',
) ),
), ),
); );
}
protected function paragraphContinue($Line, array $Block)
{
if (isset($Block['interrupted']))
{
return;
}
$Block['element']['handler']['argument'] .= "\n".$Line['text'];
return $Block; return $Block;
} }
@ -1127,13 +1104,18 @@ class Parsedown
{ {
$Elements = array(); $Elements = array();
$nonNestables = (empty($nonNestables)
? array()
: array_combine($nonNestables, $nonNestables)
);
# $excerpt is based on the first occurrence of a marker # $excerpt is based on the first occurrence of a marker
while ($excerpt = strpbrk($text, $this->inlineMarkerList)) while ($excerpt = strpbrk($text, $this->inlineMarkerList))
{ {
$marker = $excerpt[0]; $marker = $excerpt[0];
$markerPosition = strpos($text, $marker); $markerPosition = strlen($text) - strlen($excerpt);
$Excerpt = array('text' => $excerpt, 'context' => $text); $Excerpt = array('text' => $excerpt, 'context' => $text);
@ -1141,12 +1123,12 @@ class Parsedown
{ {
# check to see if the current inline type is nestable in the current context # check to see if the current inline type is nestable in the current context
if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables)) if (isset($nonNestables[$inlineType]))
{ {
continue; continue;
} }
$Inline = $this->{'inline'.$inlineType}($Excerpt); $Inline = $this->{"inline$inlineType"}($Excerpt);
if ( ! isset($Inline)) if ( ! isset($Inline))
{ {
@ -1169,10 +1151,11 @@ class Parsedown
# cause the new element to 'inherit' our non nestables # cause the new element to 'inherit' our non nestables
foreach ($nonNestables as $non_nestable)
{ $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
$Inline['element']['nonNestables'][] = $non_nestable; ? array_merge($Inline['element']['nonNestables'], $nonNestables)
} : $nonNestables
;
# the text that comes before the inline # the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']); $unmarkedText = substr($text, 0, $Inline['position']);
@ -1182,7 +1165,7 @@ class Parsedown
$Elements[] = $InlineText['element']; $Elements[] = $InlineText['element'];
# compile the inline # compile the inline
$Elements[] = $Inline['element']; $Elements[] = $this->extractElement($Inline);
# remove the examined text # remove the examined text
$text = substr($text, $Inline['position'] + $Inline['extent']); $text = substr($text, $Inline['position'] + $Inline['extent']);
@ -1203,14 +1186,13 @@ class Parsedown
$InlineText = $this->inlineText($text); $InlineText = $this->inlineText($text);
$Elements[] = $InlineText['element']; $Elements[] = $InlineText['element'];
$Elements = array_map( foreach ($Elements as &$Element)
function ($Element) { {
$Element['autobreak'] = isset($Element['autobreak']) if ( ! isset($Element['autobreak']))
? $Element['autobreak'] : false; {
return $Element; $Element['autobreak'] = false;
}, }
$Elements }
);
return $Elements; return $Elements;
} }
@ -1223,33 +1205,17 @@ class Parsedown
{ {
$Inline = array( $Inline = array(
'extent' => strlen($text), 'extent' => strlen($text),
'element' => array( 'element' => array(),
'elements' => array(),
),
); );
if ($this->breaksEnabled) $safeText = self::escape($text, true);
{
$Inline['element']['elements'] = self::pregReplaceElements( $Inline['element']['rawHtml'] = preg_replace(
'/[ ]*\n/', $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
array( "<br />\n",
array('name' => 'br'), $safeText
array('text' => "\n"),
),
$text
); );
} $Inline['element']['allowRawHtmlInSafeMode'] = true;
else
{
$Inline['element']['elements'] = self::pregReplaceElements(
'/(?:[ ][ ]+|[ ]*\\\\)\n/',
array(
array('name' => 'br'),
array('text' => "\n"),
),
$text
);
}
return $Inline; return $Inline;
} }
@ -1258,10 +1224,10 @@ class Parsedown
{ {
$marker = $Excerpt['text'][0]; $marker = $Excerpt['text'][0];
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches)) if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
{ {
$text = $matches[2]; $text = $matches[2];
$text = preg_replace("/[ ]*\n/", ' ', $text); $text = preg_replace('/[ ]*+\n/', ' ', $text);
return array( return array(
'extent' => strlen($matches[0]), 'extent' => strlen($matches[0]),
@ -1287,7 +1253,7 @@ class Parsedown
if ( ! isset($matches[2])) if ( ! isset($matches[2]))
{ {
$url = 'mailto:' . $url; $url = "mailto:$url";
} }
return array( return array(
@ -1417,7 +1383,7 @@ class Parsedown
return; return;
} }
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
{ {
$Element['attributes']['href'] = $matches[1]; $Element['attributes']['href'] = $matches[1];
@ -1466,7 +1432,7 @@ class Parsedown
return; return;
} }
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches)) if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
{ {
return array( return array(
'element' => array('rawHtml' => $matches[0]), 'element' => array('rawHtml' => $matches[0]),
@ -1474,7 +1440,7 @@ class Parsedown
); );
} }
if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches)) if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
{ {
return array( return array(
'element' => array('rawHtml' => $matches[0]), 'element' => array('rawHtml' => $matches[0]),
@ -1482,7 +1448,7 @@ class Parsedown
); );
} }
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
{ {
return array( return array(
'element' => array('rawHtml' => $matches[0]), 'element' => array('rawHtml' => $matches[0]),
@ -1493,8 +1459,9 @@ class Parsedown
protected function inlineSpecialCharacter($Excerpt) protected function inlineSpecialCharacter($Excerpt)
{ {
if (preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)) if ($Excerpt['text'][1] !== ' ' and strpos($Excerpt['text'], ';') !== false
{ and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
) {
return array( return array(
'element' => array('rawHtml' => '&' . $matches[1] . ';'), 'element' => array('rawHtml' => '&' . $matches[1] . ';'),
'extent' => strlen($matches[0]), 'extent' => strlen($matches[0]),
@ -1534,8 +1501,9 @@ class Parsedown
return; return;
} }
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) if (strpos($Excerpt['context'], 'http') !== false
{ and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
) {
$url = $matches[0][0]; $url = $matches[0][0];
$Inline = array( $Inline = array(
@ -1556,7 +1524,7 @@ class Parsedown
protected function inlineUrlTag($Excerpt) protected function inlineUrlTag($Excerpt)
{ {
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
{ {
$url = $matches[1]; $url = $matches[1];
@ -1598,6 +1566,7 @@ class Parsedown
{ {
$function = $Element['handler']; $function = $Element['handler'];
$argument = $Element['text']; $argument = $Element['text'];
unset($Element['text']);
$destination = 'rawHtml'; $destination = 'rawHtml';
} }
else else
@ -1613,9 +1582,9 @@ class Parsedown
{ {
$Element = $this->handle($Element); $Element = $this->handle($Element);
} }
}
unset($Element['handler']); unset($Element['handler']);
}
return $Element; return $Element;
} }
@ -1632,6 +1601,8 @@ class Parsedown
protected function elementApplyRecursive($closure, array $Element) protected function elementApplyRecursive($closure, array $Element)
{ {
$Element = call_user_func($closure, $Element);
if (isset($Element['elements'])) if (isset($Element['elements']))
{ {
$Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
@ -1641,6 +1612,20 @@ class Parsedown
$Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
} }
return $Element;
}
protected function elementApplyRecursiveDepthFirst($closure, array $Element)
{
if (isset($Element['elements']))
{
$Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
}
elseif (isset($Element['element']))
{
$Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
}
$Element = call_user_func($closure, $Element); $Element = call_user_func($closure, $Element);
return $Element; return $Element;
@ -1648,14 +1633,22 @@ class Parsedown
protected function elementsApplyRecursive($closure, array $Elements) protected function elementsApplyRecursive($closure, array $Elements)
{ {
$newElements = array(); foreach ($Elements as &$Element)
foreach ($Elements as $Element)
{ {
$newElements[] = $this->elementApplyRecursive($closure, $Element); $Element = $this->elementApplyRecursive($closure, $Element);
} }
return $newElements; return $Elements;
}
protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
{
foreach ($Elements as &$Element)
{
$Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
}
return $Elements;
} }
protected function element(array $Element) protected function element(array $Element)
@ -1685,7 +1678,7 @@ class Parsedown
continue; continue;
} }
$markup .= ' '.$name.'="'.self::escape($value).'"'; $markup .= " $name=\"".self::escape($value).'"';
} }
} }
} }
@ -1750,8 +1743,13 @@ class Parsedown
foreach ($Elements as $Element) foreach ($Elements as $Element)
{ {
$autoBreakNext = (isset($Element['autobreak']) && $Element['autobreak'] if (empty($Element))
|| ! isset($Element['autobreak']) && isset($Element['name']) {
continue;
}
$autoBreakNext = (isset($Element['autobreak'])
? $Element['autobreak'] : isset($Element['name'])
); );
// (autobreak === false) covers both sides of an element // (autobreak === false) covers both sides of an element
$autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext; $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
@ -1928,12 +1926,12 @@ class Parsedown
# Read-Only # Read-Only
protected $specialCharacters = array( protected $specialCharacters = array(
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
); );
protected $StrongRegex = array( protected $StrongRegex = array(
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
'_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
); );
protected $EmRegex = array( protected $EmRegex = array(
@ -1941,7 +1939,7 @@ class Parsedown
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
); );
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
protected $voidElements = array( protected $voidElements = array(
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',

View File

@ -52,7 +52,6 @@ class ParsedownTest extends TestCase
$this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss'); $this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss');
$this->Parsedown->setStrictMode(substr($test, 0, 6) === 'strict'); $this->Parsedown->setStrictMode(substr($test, 0, 6) === 'strict');
$this->Parsedown->setLiteralBreaks(substr($test, 0, 14) === 'literal_breaks');
$actualMarkup = $this->Parsedown->text($markdown); $actualMarkup = $this->Parsedown->text($markdown);

View File

@ -6,3 +6,8 @@ echo $message;</code></pre>
<pre><code>&gt; not a quote <pre><code>&gt; not a quote
- not a list item - not a list item
[not a reference]: http://foo.com</code></pre> [not a reference]: http://foo.com</code></pre>
<hr />
<pre><code>foo
bar</code></pre>

View File

@ -8,3 +8,10 @@
> not a quote > not a quote
- not a list item - not a list item
[not a reference]: http://foo.com [not a reference]: http://foo.com
---
foo
bar

View File

@ -12,3 +12,7 @@ echo "Hello World";
<pre><code>the following isn't quite enough to close <pre><code>the following isn't quite enough to close
``` ```
still a fenced code block</code></pre> still a fenced code block</code></pre>
<pre><code>foo
bar</code></pre>

View File

@ -29,3 +29,10 @@ the following isn't quite enough to close
``` ```
still a fenced code block still a fenced code block
```` ````
```
foo
bar
```

View File

@ -1,6 +0,0 @@
<p>first line
<br />
<br />
<br />
<br />
sixth line</p>

View File

@ -1,6 +0,0 @@
first line
sixth line

View File

@ -67,3 +67,9 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<hr />
<p>Not a table, we haven't ended the paragraph:
header 1 | header 2
-------- | --------
cell 1.1 | cell 1.2
cell 2.1 | cell 2.2</p>

View File

@ -23,3 +23,11 @@ header 1
-------| -------|
cell 1.1 cell 1.1
cell 2.1 cell 2.1
---
Not a table, we haven't ended the paragraph:
header 1 | header 2
-------- | --------
cell 1.1 | cell 1.2
cell 2.1 | cell 2.2

View File

@ -1,3 +1,4 @@
<p><del>strikethrough</del></p> <p><del>strikethrough</del></p>
<p>here's <del>one</del> followed by <del>another one</del></p> <p>here's <del>one</del> followed by <del>another one</del></p>
<p>~~ this ~~ is not one neither is ~this~</p> <p>~~ this ~~ is not one neither is ~this~</p>
<p>escaped ~~this~~</p>

View File

@ -3,3 +3,5 @@
here's ~~one~~ followed by ~~another one~~ here's ~~one~~ followed by ~~another one~~
~~ this ~~ is not one neither is ~this~ ~~ this ~~ is not one neither is ~this~
escaped \~\~this\~\~