mirror of
https://github.com/erusev/parsedown.git
synced 2023-08-10 21:13:06 +03:00
Compare commits
102 Commits
1.8.0-beta
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6598f3860c | ||
![]() |
1e5080190c | ||
![]() |
1610e4747c | ||
![]() |
3159a9d3cd | ||
![]() |
dbee8ab4f2 | ||
![]() |
dba4125b59 | ||
![]() |
bfaa76d370 | ||
![]() |
3825db53a2 | ||
![]() |
fe7a50eceb | ||
![]() |
bce642f2d7 | ||
![]() |
7d4c06cb52 | ||
![]() |
f7b66e6b20 | ||
![]() |
811bc32726 | ||
![]() |
8fd5464c46 | ||
![]() |
21c8c792de | ||
![]() |
6ca29539e1 | ||
![]() |
a503c1a69b | ||
![]() |
819c68899d | ||
![]() |
4c2d79fc6a | ||
![]() |
48a2fb26fe | ||
![]() |
33b79d2446 | ||
![]() |
d6d2d96459 | ||
![]() |
d5b6ab5198 | ||
![]() |
15e8439c7f | ||
![]() |
ff6148f9b9 | ||
![]() |
dfd8657bc5 | ||
![]() |
ee64646765 | ||
![]() |
c956090b55 | ||
![]() |
0be26550f3 | ||
![]() |
8e26a65a6f | ||
![]() |
2a24a8583b | ||
![]() |
1d55344e92 | ||
![]() |
5dc8d1cc39 | ||
![]() |
33cf0f0b99 | ||
![]() |
dc1ff7d6c2 | ||
![]() |
0f0987571d | ||
![]() |
18eaa649b5 | ||
![]() |
e124572b60 | ||
![]() |
1686a34469 | ||
![]() |
2bd7113c55 | ||
![]() |
29fce0ec37 | ||
![]() |
b0bbc275d4 | ||
![]() |
72f9ca92ae | ||
![]() |
89c3fa05d9 | ||
![]() |
69163d6e88 | ||
![]() |
3a0c964291 | ||
![]() |
1829106e60 | ||
![]() |
464f5f9329 | ||
![]() |
c26a2ee4bf | ||
![]() |
ba3b60d6e4 | ||
![]() |
0b1e6b8c86 | ||
![]() |
1f69f7e697 | ||
![]() |
c83af0a7d5 | ||
![]() |
4686daf8c2 | ||
![]() |
c9e7183cfa | ||
![]() |
9eed1104e7 | ||
![]() |
fd95703da5 | ||
![]() |
8d172a2994 | ||
![]() |
dfab7240a4 | ||
![]() |
113c6d2b21 | ||
![]() |
a9764ec90f | ||
![]() |
0a842fb5b1 | ||
![]() |
7f4318dbdb | ||
![]() |
3e70819a20 | ||
![]() |
2bf7ca41a0 | ||
![]() |
b75fd409ff | ||
![]() |
88a3f31dd7 | ||
![]() |
726d4ef44a | ||
![]() |
450a74fedf | ||
![]() |
7e15d99d90 | ||
![]() |
d2dd736e1b | ||
![]() |
e74a5bd7ed | ||
![]() |
b53aa74a72 | ||
![]() |
3ea08140b6 | ||
![]() |
c45e41950f | ||
![]() |
2faba6fef5 | ||
![]() |
b42add3762 | ||
![]() |
107223d3a0 | ||
![]() |
d4f1ac465c | ||
![]() |
d6e306d620 | ||
![]() |
dc5cf8770b | ||
![]() |
70f5c02d47 | ||
![]() |
90ad738933 | ||
![]() |
f2327023c1 | ||
![]() |
6f13f97674 | ||
![]() |
8091e5586a | ||
![]() |
cb33daf0e6 | ||
![]() |
c440c91af5 | ||
![]() |
3514881e14 | ||
![]() |
043c55e4c6 | ||
![]() |
e4cd13350b | ||
![]() |
ae8067e862 | ||
![]() |
5353ebb524 | ||
![]() |
39df7d4f8e | ||
![]() |
50f15add44 | ||
![]() |
3f5b0ee781 | ||
![]() |
9a021b2130 | ||
![]() |
43d25a74fe | ||
![]() |
1d68e5506c | ||
![]() |
86940be224 | ||
![]() |
cdaf86b039 | ||
![]() |
1d65fb858a |
20
.travis.yml
20
.travis.yml
|
@ -1,25 +1,25 @@
|
|||
language: php
|
||||
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 5.3
|
||||
dist: precise
|
||||
- php: 5.4
|
||||
dist: trusty
|
||||
- php: 5.5
|
||||
dist: trusty
|
||||
- php: 5.6
|
||||
dist: xenial
|
||||
- php: 7.0
|
||||
dist: xenial
|
||||
- php: 7.1
|
||||
dist: bionic
|
||||
- php: 7.2
|
||||
- php: nightly
|
||||
- php: hhvm
|
||||
- php: hhvm-nightly
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- php: hhvm-nightly
|
||||
dist: bionic
|
||||
- php: 7.3
|
||||
dist: bionic
|
||||
- php: 7.4
|
||||
dist: bionic
|
||||
|
||||
install:
|
||||
- composer install --prefer-dist --no-interaction --no-progress
|
||||
|
|
473
Parsedown.php
Executable file → Normal file
473
Parsedown.php
Executable file → Normal file
|
@ -17,7 +17,7 @@ class Parsedown
|
|||
{
|
||||
# ~
|
||||
|
||||
const version = '1.8.0-beta-1';
|
||||
const version = '1.8.0-beta-7';
|
||||
|
||||
# ~
|
||||
|
||||
|
@ -65,15 +65,6 @@ class Parsedown
|
|||
|
||||
protected $breaksEnabled;
|
||||
|
||||
function setLiteralBreaks($literalBreaks)
|
||||
{
|
||||
$this->literalBreaks = $literalBreaks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected $literalBreaks;
|
||||
|
||||
function setMarkupEscaped($markupEscaped)
|
||||
{
|
||||
$this->markupEscaped = $markupEscaped;
|
||||
|
@ -116,6 +107,7 @@ class Parsedown
|
|||
'ftp://',
|
||||
'ftps://',
|
||||
'mailto:',
|
||||
'tel:',
|
||||
'data:image/png;base64,',
|
||||
'data:image/gif;base64,',
|
||||
'data:image/jpeg;base64,',
|
||||
|
@ -174,43 +166,34 @@ class Parsedown
|
|||
|
||||
protected function linesElements(array $lines)
|
||||
{
|
||||
$Elements = array();
|
||||
$CurrentBlock = null;
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
if ( ! $this->literalBreaks and chop($line) === '')
|
||||
if (chop($line) === '')
|
||||
{
|
||||
if (isset($CurrentBlock))
|
||||
{
|
||||
$CurrentBlock['interrupted'] = true;
|
||||
$CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
|
||||
? $CurrentBlock['interrupted'] + 1 : 1
|
||||
);
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
unset($parts[0]);
|
||||
|
||||
foreach ($parts as $part)
|
||||
{
|
||||
$shortage = 4 - mb_strlen($line, 'utf-8') % 4;
|
||||
|
||||
$line .= str_repeat(' ', $shortage);
|
||||
$line .= $part;
|
||||
}
|
||||
$line = $beforeTab
|
||||
. str_repeat(' ', $shortage)
|
||||
. substr($line, strlen($beforeTab) + 1)
|
||||
;
|
||||
}
|
||||
|
||||
$indent = 0;
|
||||
|
||||
while (isset($line[$indent]) and $line[$indent] === ' ')
|
||||
{
|
||||
$indent ++;
|
||||
}
|
||||
$indent = strspn($line, ' ');
|
||||
|
||||
$text = $indent > 0 ? substr($line, $indent) : $line;
|
||||
|
||||
|
@ -222,7 +205,8 @@ class Parsedown
|
|||
|
||||
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))
|
||||
{
|
||||
|
@ -234,22 +218,15 @@ class Parsedown
|
|||
{
|
||||
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];
|
||||
}
|
||||
elseif ($this->literalBreaks)
|
||||
{
|
||||
$marker = '\n';
|
||||
$text = ' ';
|
||||
}
|
||||
$marker = $text[0];
|
||||
|
||||
# ~
|
||||
|
||||
|
@ -268,7 +245,7 @@ class Parsedown
|
|||
|
||||
foreach ($blockTypes as $blockType)
|
||||
{
|
||||
$Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
|
||||
$Block = $this->{"block$blockType"}($Line, $CurrentBlock);
|
||||
|
||||
if (isset($Block))
|
||||
{
|
||||
|
@ -276,7 +253,10 @@ class Parsedown
|
|||
|
||||
if ( ! isset($Block['identified']))
|
||||
{
|
||||
$Blocks []= $CurrentBlock;
|
||||
if (isset($CurrentBlock))
|
||||
{
|
||||
$Elements[] = $this->extractElement($CurrentBlock);
|
||||
}
|
||||
|
||||
$Block['identified'] = true;
|
||||
}
|
||||
|
@ -294,17 +274,21 @@ class Parsedown
|
|||
|
||||
# ~
|
||||
|
||||
if (
|
||||
isset($CurrentBlock)
|
||||
and isset($CurrentBlock['element']['name'])
|
||||
and $CurrentBlock['element']['name'] === 'p'
|
||||
and ! isset($CurrentBlock['interrupted'])
|
||||
) {
|
||||
$CurrentBlock['element']['handler']['argument'] .= "\n".$text;
|
||||
if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
|
||||
{
|
||||
$Block = $this->paragraphContinue($Line, $CurrentBlock);
|
||||
}
|
||||
|
||||
if (isset($Block))
|
||||
{
|
||||
$CurrentBlock = $Block;
|
||||
}
|
||||
else
|
||||
{
|
||||
$Blocks []= $CurrentBlock;
|
||||
if (isset($CurrentBlock))
|
||||
{
|
||||
$Elements[] = $this->extractElement($CurrentBlock);
|
||||
}
|
||||
|
||||
$CurrentBlock = $this->paragraph($Line);
|
||||
|
||||
|
@ -316,27 +300,15 @@ class Parsedown
|
|||
|
||||
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;
|
||||
|
||||
unset($Blocks[0]);
|
||||
|
||||
# ~
|
||||
|
||||
$Elements = array();
|
||||
|
||||
foreach ($Blocks as $Block)
|
||||
if (isset($CurrentBlock))
|
||||
{
|
||||
if (isset($Block['hidden']))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$Elements[] = $Block['element'];
|
||||
$Elements[] = $this->extractElement($CurrentBlock);
|
||||
}
|
||||
|
||||
# ~
|
||||
|
@ -344,14 +316,31 @@ class Parsedown
|
|||
return $Elements;
|
||||
}
|
||||
|
||||
protected function extractElement(array $Component)
|
||||
{
|
||||
if ( ! isset($Component['element']))
|
||||
{
|
||||
if (isset($Component['markup']))
|
||||
{
|
||||
$Component['element'] = array('rawHtml' => $Component['markup']);
|
||||
}
|
||||
elseif (isset($Component['hidden']))
|
||||
{
|
||||
$Component['element'] = array();
|
||||
}
|
||||
}
|
||||
|
||||
return $Component['element'];
|
||||
}
|
||||
|
||||
protected function isBlockContinuable($Type)
|
||||
{
|
||||
return method_exists($this, 'block'.$Type.'Continue');
|
||||
return method_exists($this, 'block' . $Type . 'Continue');
|
||||
}
|
||||
|
||||
protected function isBlockCompletable($Type)
|
||||
{
|
||||
return method_exists($this, 'block'.$Type.'Complete');
|
||||
return method_exists($this, 'block' . $Type . 'Complete');
|
||||
}
|
||||
|
||||
#
|
||||
|
@ -359,7 +348,7 @@ class Parsedown
|
|||
|
||||
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;
|
||||
}
|
||||
|
@ -388,7 +377,7 @@ class Parsedown
|
|||
{
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
$Block['element']['element']['text'] .= "\n";
|
||||
$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
|
||||
|
||||
unset($Block['interrupted']);
|
||||
}
|
||||
|
@ -405,10 +394,6 @@ class Parsedown
|
|||
|
||||
protected function blockCodeComplete($Block)
|
||||
{
|
||||
$text = $Block['element']['element']['text'];
|
||||
|
||||
$Block['element']['element']['text'] = $text;
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
|
@ -462,33 +447,56 @@ class Parsedown
|
|||
|
||||
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)
|
||||
{
|
||||
$Element = array(
|
||||
'name' => 'code',
|
||||
'text' => '',
|
||||
);
|
||||
|
||||
if (isset($matches[2]))
|
||||
{
|
||||
$class = 'language-'.$matches[2];
|
||||
|
||||
$Element['attributes'] = array(
|
||||
'class' => $class,
|
||||
);
|
||||
}
|
||||
|
||||
$Block = array(
|
||||
'char' => $Line['text'][0],
|
||||
'openerLength' => mb_strlen($matches[1]),
|
||||
'element' => array(
|
||||
'name' => 'pre',
|
||||
'element' => $Element,
|
||||
),
|
||||
);
|
||||
|
||||
return $Block;
|
||||
return;
|
||||
}
|
||||
|
||||
$infostring = trim(substr($Line['text'], $openerLength), "\t ");
|
||||
|
||||
if (strpos($infostring, '`') !== false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$Element = array(
|
||||
'name' => 'code',
|
||||
'text' => '',
|
||||
);
|
||||
|
||||
if ($infostring !== '')
|
||||
{
|
||||
/**
|
||||
* https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
|
||||
* Every HTML element may have a class attribute specified.
|
||||
* The attribute, if specified, must have a value that is a set
|
||||
* of space-separated tokens representing the various classes
|
||||
* that the element belongs to.
|
||||
* [...]
|
||||
* The space characters, for the purposes of this specification,
|
||||
* are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
|
||||
* U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
|
||||
* U+000D CARRIAGE RETURN (CR).
|
||||
*/
|
||||
$language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
|
||||
|
||||
$Element['attributes'] = array('class' => "language-$language");
|
||||
}
|
||||
|
||||
$Block = array(
|
||||
'char' => $marker,
|
||||
'openerLength' => $openerLength,
|
||||
'element' => array(
|
||||
'name' => 'pre',
|
||||
'element' => $Element,
|
||||
),
|
||||
);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
protected function blockFencedCodeContinue($Line, $Block)
|
||||
|
@ -500,14 +508,13 @@ class Parsedown
|
|||
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
$Block['element']['element']['text'] .= "\n";
|
||||
$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
|
||||
|
||||
unset($Block['interrupted']);
|
||||
}
|
||||
|
||||
if (
|
||||
preg_match('/^(['.preg_quote($Block['char']).']{3,})[ ]*$/', $Line['text'], $matches)
|
||||
and mb_strlen($matches[1]) >= $Block['openerLength']
|
||||
if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
|
||||
and chop(substr($Line['text'], $len), ' ') === ''
|
||||
) {
|
||||
$Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
|
||||
|
||||
|
@ -516,17 +523,13 @@ class Parsedown
|
|||
return $Block;
|
||||
}
|
||||
|
||||
$Block['element']['element']['text'] .= "\n".$Line['body'];
|
||||
$Block['element']['element']['text'] .= "\n" . $Line['body'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
protected function blockFencedCodeComplete($Block)
|
||||
{
|
||||
$text = $Block['element']['element']['text'];
|
||||
|
||||
$Block['element']['element']['text'] = $text;
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
|
@ -535,12 +538,7 @@ class Parsedown
|
|||
|
||||
protected function blockHeader($Line)
|
||||
{
|
||||
$level = 1;
|
||||
|
||||
while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
|
||||
{
|
||||
$level ++;
|
||||
}
|
||||
$level = strspn($Line['text'], '#');
|
||||
|
||||
if ($level > 6)
|
||||
{
|
||||
|
@ -558,7 +556,7 @@ class Parsedown
|
|||
|
||||
$Block = array(
|
||||
'element' => array(
|
||||
'name' => 'h' . min(6, $level),
|
||||
'name' => 'h' . $level,
|
||||
'handler' => array(
|
||||
'function' => 'lineElements',
|
||||
'argument' => $text,
|
||||
|
@ -575,9 +573,9 @@ class Parsedown
|
|||
|
||||
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]);
|
||||
|
||||
|
@ -592,19 +590,22 @@ class Parsedown
|
|||
$matches[1] .= ' ';
|
||||
}
|
||||
|
||||
$markerWithoutWhitespace = strstr($matches[1], ' ', true);
|
||||
|
||||
$Block = array(
|
||||
'indent' => $Line['indent'],
|
||||
'pattern' => $pattern,
|
||||
'data' => array(
|
||||
'type' => $name,
|
||||
'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(
|
||||
'name' => $name,
|
||||
'elements' => array(),
|
||||
),
|
||||
);
|
||||
$Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
|
||||
|
||||
if ($name === 'ol')
|
||||
{
|
||||
|
@ -614,7 +615,7 @@ class Parsedown
|
|||
{
|
||||
if (
|
||||
isset($CurrentBlock)
|
||||
and ! isset($CurrentBlock['type'])
|
||||
and $CurrentBlock['type'] === 'Paragraph'
|
||||
and ! isset($CurrentBlock['interrupted'])
|
||||
) {
|
||||
return;
|
||||
|
@ -652,10 +653,10 @@ class Parsedown
|
|||
and (
|
||||
(
|
||||
$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 (
|
||||
$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 +718,7 @@ class Parsedown
|
|||
|
||||
if ( ! isset($Block['interrupted']))
|
||||
{
|
||||
$text = preg_replace('/^[ ]{0,'.$requiredIndent.'}/', '', $Line['body']);
|
||||
$text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
|
||||
|
||||
$Block['li']['handler']['argument'] []= $text;
|
||||
|
||||
|
@ -746,7 +747,7 @@ class Parsedown
|
|||
|
||||
protected function blockQuote($Line)
|
||||
{
|
||||
if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
|
||||
if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
|
||||
{
|
||||
$Block = array(
|
||||
'element' => array(
|
||||
|
@ -770,7 +771,7 @@ class Parsedown
|
|||
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];
|
||||
|
||||
|
@ -790,7 +791,9 @@ class Parsedown
|
|||
|
||||
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(
|
||||
'element' => array(
|
||||
|
@ -807,15 +810,13 @@ class Parsedown
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
if (
|
||||
chop(chop($Line['text'], ' '), $Line['text'][0]) === ''
|
||||
and $Line['indent'] < 4
|
||||
) {
|
||||
if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
|
||||
{
|
||||
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
|
||||
|
||||
return $Block;
|
||||
|
@ -832,7 +833,7 @@ class Parsedown
|
|||
return;
|
||||
}
|
||||
|
||||
if (preg_match('/^<[\/]?+(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
|
||||
if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
|
||||
{
|
||||
$element = strtolower($matches[1]);
|
||||
|
||||
|
@ -860,7 +861,7 @@ class Parsedown
|
|||
return;
|
||||
}
|
||||
|
||||
$Block['element']['rawHtml'] .= "\n".$Line['body'];
|
||||
$Block['element']['rawHtml'] .= "\n" . $Line['body'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
@ -870,24 +871,20 @@ class Parsedown
|
|||
|
||||
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]);
|
||||
|
||||
$Data = array(
|
||||
'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;
|
||||
|
||||
$Block = array(
|
||||
'hidden' => true,
|
||||
'element' => array(),
|
||||
);
|
||||
|
||||
return $Block;
|
||||
|
@ -899,7 +896,7 @@ class Parsedown
|
|||
|
||||
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;
|
||||
}
|
||||
|
@ -908,6 +905,7 @@ class Parsedown
|
|||
strpos($Block['element']['handler']['argument'], '|') === false
|
||||
and strpos($Line['text'], '|') === false
|
||||
and strpos($Line['text'], ':') === false
|
||||
or strpos($Block['element']['handler']['argument'], "\n") !== false
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
@ -984,7 +982,7 @@ class Parsedown
|
|||
$alignment = $alignments[$index];
|
||||
|
||||
$HeaderElement['attributes'] = array(
|
||||
'style' => 'text-align: '.$alignment.';',
|
||||
'style' => "text-align: $alignment;",
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1035,7 +1033,7 @@ class Parsedown
|
|||
$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']));
|
||||
|
||||
|
@ -1055,7 +1053,7 @@ class Parsedown
|
|||
if (isset($Block['alignments'][$index]))
|
||||
{
|
||||
$Element['attributes'] = array(
|
||||
'style' => 'text-align: '.$Block['alignments'][$index].';',
|
||||
'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1079,16 +1077,27 @@ class Parsedown
|
|||
|
||||
protected function paragraph($Line)
|
||||
{
|
||||
$Block = array(
|
||||
return array(
|
||||
'type' => 'Paragraph',
|
||||
'element' => array(
|
||||
'name' => 'p',
|
||||
'handler' => array(
|
||||
'function' => 'lineElements',
|
||||
'argument' => $Line['text'],
|
||||
'destination' => 'elements',
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function paragraphContinue($Line, array $Block)
|
||||
{
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$Block['element']['handler']['argument'] .= "\n".$Line['text'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
@ -1118,22 +1127,30 @@ class Parsedown
|
|||
# ~
|
||||
#
|
||||
|
||||
public function line($text, $nonNestables=array())
|
||||
public function line($text, $nonNestables = array())
|
||||
{
|
||||
return $this->elements($this->lineElements($text, $nonNestables));
|
||||
}
|
||||
|
||||
protected function lineElements($text, $nonNestables = array())
|
||||
{
|
||||
# standardize line breaks
|
||||
$text = str_replace(array("\r\n", "\r"), "\n", $text);
|
||||
|
||||
$Elements = array();
|
||||
|
||||
$nonNestables = (empty($nonNestables)
|
||||
? array()
|
||||
: array_combine($nonNestables, $nonNestables)
|
||||
);
|
||||
|
||||
# $excerpt is based on the first occurrence of a marker
|
||||
|
||||
while ($excerpt = strpbrk($text, $this->inlineMarkerList))
|
||||
{
|
||||
$marker = $excerpt[0];
|
||||
|
||||
$markerPosition = strpos($text, $marker);
|
||||
$markerPosition = strlen($text) - strlen($excerpt);
|
||||
|
||||
$Excerpt = array('text' => $excerpt, 'context' => $text);
|
||||
|
||||
|
@ -1141,12 +1158,12 @@ class Parsedown
|
|||
{
|
||||
# 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;
|
||||
}
|
||||
|
||||
$Inline = $this->{'inline'.$inlineType}($Excerpt);
|
||||
$Inline = $this->{"inline$inlineType"}($Excerpt);
|
||||
|
||||
if ( ! isset($Inline))
|
||||
{
|
||||
|
@ -1169,10 +1186,11 @@ class Parsedown
|
|||
|
||||
# cause the new element to 'inherit' our non nestables
|
||||
|
||||
foreach ($nonNestables as $non_nestable)
|
||||
{
|
||||
$Inline['element']['nonNestables'][] = $non_nestable;
|
||||
}
|
||||
|
||||
$Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
|
||||
? array_merge($Inline['element']['nonNestables'], $nonNestables)
|
||||
: $nonNestables
|
||||
;
|
||||
|
||||
# the text that comes before the inline
|
||||
$unmarkedText = substr($text, 0, $Inline['position']);
|
||||
|
@ -1182,7 +1200,7 @@ class Parsedown
|
|||
$Elements[] = $InlineText['element'];
|
||||
|
||||
# compile the inline
|
||||
$Elements[] = $Inline['element'];
|
||||
$Elements[] = $this->extractElement($Inline);
|
||||
|
||||
# remove the examined text
|
||||
$text = substr($text, $Inline['position'] + $Inline['extent']);
|
||||
|
@ -1203,14 +1221,13 @@ class Parsedown
|
|||
$InlineText = $this->inlineText($text);
|
||||
$Elements[] = $InlineText['element'];
|
||||
|
||||
$Elements = array_map(
|
||||
function ($Element) {
|
||||
$Element['autobreak'] = isset($Element['autobreak'])
|
||||
? $Element['autobreak'] : false;
|
||||
return $Element;
|
||||
},
|
||||
$Elements
|
||||
);
|
||||
foreach ($Elements as &$Element)
|
||||
{
|
||||
if ( ! isset($Element['autobreak']))
|
||||
{
|
||||
$Element['autobreak'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $Elements;
|
||||
}
|
||||
|
@ -1223,33 +1240,17 @@ class Parsedown
|
|||
{
|
||||
$Inline = array(
|
||||
'extent' => strlen($text),
|
||||
'element' => array(
|
||||
'elements' => array(),
|
||||
),
|
||||
'element' => array(),
|
||||
);
|
||||
|
||||
if ($this->breaksEnabled)
|
||||
{
|
||||
$Inline['element']['elements'] = self::pregReplaceElements(
|
||||
'/[ ]*\n/',
|
||||
array(
|
||||
array('name' => 'br'),
|
||||
array('text' => "\n"),
|
||||
),
|
||||
$text
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$Inline['element']['elements'] = self::pregReplaceElements(
|
||||
'/(?:[ ][ ]+|[ ]*\\\\)\n/',
|
||||
array(
|
||||
array('name' => 'br'),
|
||||
array('text' => "\n"),
|
||||
),
|
||||
$text
|
||||
);
|
||||
}
|
||||
$Inline['element']['elements'] = self::pregReplaceElements(
|
||||
$this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
|
||||
array(
|
||||
array('name' => 'br'),
|
||||
array('text' => "\n"),
|
||||
),
|
||||
$text
|
||||
);
|
||||
|
||||
return $Inline;
|
||||
}
|
||||
|
@ -1258,10 +1259,10 @@ class Parsedown
|
|||
{
|
||||
$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 = preg_replace("/[ ]*\n/", ' ', $text);
|
||||
$text = preg_replace('/[ ]*+\n/', ' ', $text);
|
||||
|
||||
return array(
|
||||
'extent' => strlen($matches[0]),
|
||||
|
@ -1287,7 +1288,7 @@ class Parsedown
|
|||
|
||||
if ( ! isset($matches[2]))
|
||||
{
|
||||
$url = 'mailto:' . $url;
|
||||
$url = "mailto:$url";
|
||||
}
|
||||
|
||||
return array(
|
||||
|
@ -1417,7 +1418,7 @@ class Parsedown
|
|||
return;
|
||||
}
|
||||
|
||||
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
|
||||
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
|
||||
{
|
||||
$Element['attributes']['href'] = $matches[1];
|
||||
|
||||
|
@ -1466,7 +1467,7 @@ class Parsedown
|
|||
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(
|
||||
'element' => array('rawHtml' => $matches[0]),
|
||||
|
@ -1474,7 +1475,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(
|
||||
'element' => array('rawHtml' => $matches[0]),
|
||||
|
@ -1482,7 +1483,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(
|
||||
'element' => array('rawHtml' => $matches[0]),
|
||||
|
@ -1493,10 +1494,11 @@ class Parsedown
|
|||
|
||||
protected function inlineSpecialCharacter($Excerpt)
|
||||
{
|
||||
if (preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches))
|
||||
{
|
||||
if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
|
||||
and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
|
||||
) {
|
||||
return array(
|
||||
'element' => array('rawHtml' => '&'.$matches[1].';'),
|
||||
'element' => array('rawHtml' => '&' . $matches[1] . ';'),
|
||||
'extent' => strlen($matches[0]),
|
||||
);
|
||||
}
|
||||
|
@ -1534,8 +1536,9 @@ class Parsedown
|
|||
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];
|
||||
|
||||
$Inline = array(
|
||||
|
@ -1556,7 +1559,7 @@ class Parsedown
|
|||
|
||||
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];
|
||||
|
||||
|
@ -1598,6 +1601,7 @@ class Parsedown
|
|||
{
|
||||
$function = $Element['handler'];
|
||||
$argument = $Element['text'];
|
||||
unset($Element['text']);
|
||||
$destination = 'rawHtml';
|
||||
}
|
||||
else
|
||||
|
@ -1613,9 +1617,9 @@ class Parsedown
|
|||
{
|
||||
$Element = $this->handle($Element);
|
||||
}
|
||||
}
|
||||
|
||||
unset($Element['handler']);
|
||||
unset($Element['handler']);
|
||||
}
|
||||
|
||||
return $Element;
|
||||
}
|
||||
|
@ -1632,6 +1636,8 @@ class Parsedown
|
|||
|
||||
protected function elementApplyRecursive($closure, array $Element)
|
||||
{
|
||||
$Element = call_user_func($closure, $Element);
|
||||
|
||||
if (isset($Element['elements']))
|
||||
{
|
||||
$Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
|
||||
|
@ -1641,6 +1647,20 @@ class Parsedown
|
|||
$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);
|
||||
|
||||
return $Element;
|
||||
|
@ -1648,14 +1668,22 @@ class Parsedown
|
|||
|
||||
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)
|
||||
|
@ -1674,7 +1702,7 @@ class Parsedown
|
|||
|
||||
if ($hasName)
|
||||
{
|
||||
$markup .= '<'.$Element['name'];
|
||||
$markup .= '<' . $Element['name'];
|
||||
|
||||
if (isset($Element['attributes']))
|
||||
{
|
||||
|
@ -1685,7 +1713,7 @@ class Parsedown
|
|||
continue;
|
||||
}
|
||||
|
||||
$markup .= ' '.$name.'="'.self::escape($value).'"';
|
||||
$markup .= " $name=\"".self::escape($value).'"';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1732,7 +1760,7 @@ class Parsedown
|
|||
}
|
||||
}
|
||||
|
||||
$markup .= $hasName ? '</'.$Element['name'].'>' : '';
|
||||
$markup .= $hasName ? '</' . $Element['name'] . '>' : '';
|
||||
}
|
||||
elseif ($hasName)
|
||||
{
|
||||
|
@ -1750,8 +1778,13 @@ class Parsedown
|
|||
|
||||
foreach ($Elements as $Element)
|
||||
{
|
||||
$autoBreakNext = (isset($Element['autobreak']) && $Element['autobreak']
|
||||
|| ! isset($Element['autobreak']) && isset($Element['name'])
|
||||
if (empty($Element))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$autoBreakNext = (isset($Element['autobreak'])
|
||||
? $Element['autobreak'] : isset($Element['name'])
|
||||
);
|
||||
// (autobreak === false) covers both sides of an element
|
||||
$autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
|
||||
|
@ -1928,12 +1961,12 @@ class Parsedown
|
|||
# Read-Only
|
||||
|
||||
protected $specialCharacters = array(
|
||||
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
|
||||
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
|
||||
);
|
||||
|
||||
protected $StrongRegex = array(
|
||||
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
|
||||
'_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
|
||||
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
|
||||
'_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
|
||||
);
|
||||
|
||||
protected $EmRegex = array(
|
||||
|
@ -1941,7 +1974,7 @@ class Parsedown
|
|||
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
|
||||
);
|
||||
|
||||
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
|
||||
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
|
||||
|
||||
protected $voidElements = array(
|
||||
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
|
||||
|
|
68
README.md
68
README.md
|
@ -1,60 +1,61 @@
|
|||
> I also make [Caret](https://caret.io?ref=parsedown) - a Markdown editor for Mac and PC.
|
||||
<!-- ![Parsedown](https://i.imgur.com/yE8afYV.png) -->
|
||||
|
||||
## Parsedown
|
||||
<p align="center"><img alt="Parsedown" src="https://i.imgur.com/fKVY6Kz.png" width="240" /></p>
|
||||
|
||||
[![Build Status](https://img.shields.io/travis/erusev/parsedown/master.svg?style=flat-square)](https://travis-ci.org/erusev/parsedown)
|
||||
<!--[![Total Downloads](http://img.shields.io/packagist/dt/erusev/parsedown.svg?style=flat-square)](https://packagist.org/packages/erusev/parsedown)-->
|
||||
<h1>Parsedown</h1>
|
||||
|
||||
Better Markdown Parser in PHP
|
||||
[![Build Status](https://travis-ci.org/erusev/parsedown.svg)](https://travis-ci.org/erusev/parsedown)
|
||||
[![Total Downloads](https://poser.pugx.org/erusev/parsedown/d/total.svg)](https://packagist.org/packages/erusev/parsedown)
|
||||
[![Version](https://poser.pugx.org/erusev/parsedown/v/stable.svg)](https://packagist.org/packages/erusev/parsedown)
|
||||
[![License](https://poser.pugx.org/erusev/parsedown/license.svg)](https://packagist.org/packages/erusev/parsedown)
|
||||
|
||||
[Demo](http://parsedown.org/demo) |
|
||||
[Benchmarks](http://parsedown.org/speed) |
|
||||
[Tests](http://parsedown.org/tests/) |
|
||||
[Documentation](https://github.com/erusev/parsedown/wiki/)
|
||||
Better Markdown Parser in PHP - <a href="http://parsedown.org/demo">Demo</a>.
|
||||
|
||||
### Features
|
||||
## Features
|
||||
|
||||
* One File
|
||||
* No Dependencies
|
||||
* Super Fast
|
||||
* [Super Fast](http://parsedown.org/speed)
|
||||
* Extensible
|
||||
* [GitHub flavored](https://help.github.com/articles/github-flavored-markdown)
|
||||
* Tested in 5.3 to 7.2 and in HHVM
|
||||
* [GitHub flavored](https://github.github.com/gfm)
|
||||
* [Tested](http://parsedown.org/tests/) in 5.3 to 7.3
|
||||
* [Markdown Extra extension](https://github.com/erusev/parsedown-extra)
|
||||
|
||||
### Installation
|
||||
#### Composer
|
||||
Install the [composer package] by running the following command:
|
||||
## Installation
|
||||
|
||||
Install the [composer package]:
|
||||
|
||||
composer require erusev/parsedown
|
||||
|
||||
#### Manual
|
||||
1. Download the "Source code" from the [latest release]
|
||||
2. Include `Parsedown.php`
|
||||
Or download the [latest release] and include `Parsedown.php`
|
||||
|
||||
[composer package]: https://packagist.org/packages/erusev/parsedown "The Parsedown package on packagist.org"
|
||||
[latest release]: https://github.com/erusev/parsedown/releases/latest "The latest release of Parsedown"
|
||||
|
||||
### Example
|
||||
## Example
|
||||
|
||||
``` php
|
||||
```php
|
||||
$Parsedown = new Parsedown();
|
||||
|
||||
echo $Parsedown->text('Hello _Parsedown_!'); # prints: <p>Hello <em>Parsedown</em>!</p>
|
||||
// you can also parse inline markdown only
|
||||
```
|
||||
|
||||
You can also parse inline markdown only:
|
||||
|
||||
```php
|
||||
echo $Parsedown->line('Hello _Parsedown_!'); # prints: Hello <em>Parsedown</em>!
|
||||
```
|
||||
|
||||
More examples in [the wiki](https://github.com/erusev/parsedown/wiki/) and in [this video tutorial](http://youtu.be/wYZBY8DEikI).
|
||||
|
||||
### Security
|
||||
## Security
|
||||
|
||||
Parsedown is capable of escaping user-input within the HTML that it generates. Additionally Parsedown will apply sanitisation to additional scripting vectors (such as scripting link destinations) that are introduced by the markdown syntax itself.
|
||||
|
||||
To tell Parsedown that it is processing untrusted user-input, use the following:
|
||||
|
||||
```php
|
||||
$parsedown = new Parsedown;
|
||||
$parsedown->setSafeMode(true);
|
||||
$Parsedown->setSafeMode(true);
|
||||
```
|
||||
|
||||
If instead, you wish to allow HTML within untrusted user-input, but still want output to be free from XSS it is recommended that you make use of a HTML sanitiser that allows HTML tags to be whitelisted, like [HTML Purifier](http://htmlpurifier.org/).
|
||||
|
@ -65,18 +66,19 @@ In both cases you should strongly consider employing defence-in-depth measures,
|
|||
|
||||
Safe mode does not necessarily yield safe results when using extensions to Parsedown. Extensions should be evaluated on their own to determine their specific safety against XSS.
|
||||
|
||||
### Escaping HTML
|
||||
> ⚠️ **WARNING:** This method isn't safe from XSS!
|
||||
## Escaping HTML
|
||||
|
||||
> **WARNING:** This method isn't safe from XSS!
|
||||
|
||||
If you wish to escape HTML **in trusted input**, you can use the following:
|
||||
|
||||
```php
|
||||
$parsedown = new Parsedown;
|
||||
$parsedown->setMarkupEscaped(true);
|
||||
$Parsedown->setMarkupEscaped(true);
|
||||
```
|
||||
|
||||
Beware that this still allows users to insert unsafe scripting vectors, such as links like `[xss](javascript:alert%281%29)`.
|
||||
|
||||
### Questions
|
||||
## Questions
|
||||
|
||||
**How does Parsedown work?**
|
||||
|
||||
|
@ -90,8 +92,12 @@ It passes most of the CommonMark tests. Most of the tests that don't pass deal w
|
|||
|
||||
**Who uses it?**
|
||||
|
||||
[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [Statamic CMS](http://www.statamic.com/), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony demo](https://github.com/symfony/symfony-demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents).
|
||||
[Laravel Framework](https://laravel.com/), [Bolt CMS](http://bolt.cm/), [Grav CMS](http://getgrav.org/), [Herbie CMS](http://www.getherbie.org/), [Kirby CMS](http://getkirby.com/), [October CMS](http://octobercms.com/), [Pico CMS](http://picocms.org), [Statamic CMS](http://www.statamic.com/), [phpDocumentor](http://www.phpdoc.org/), [RaspberryPi.org](http://www.raspberrypi.org/), [Symfony Demo](https://github.com/symfony/demo) and [more](https://packagist.org/packages/erusev/parsedown/dependents).
|
||||
|
||||
**How can I help?**
|
||||
|
||||
Use it, star it, share it and if you feel generous, [donate](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=528P3NZQMP8N2).
|
||||
|
||||
**What else should I know?**
|
||||
|
||||
I also make [Nota](https://nota.md/) — a writing app designed for Markdown files :)
|
||||
|
|
|
@ -52,7 +52,6 @@ class ParsedownTest extends TestCase
|
|||
|
||||
$this->Parsedown->setSafeMode(substr($test, 0, 3) === 'xss');
|
||||
$this->Parsedown->setStrictMode(substr($test, 0, 6) === 'strict');
|
||||
$this->Parsedown->setLiteralBreaks(substr($test, 0, 14) === 'literal_breaks');
|
||||
|
||||
$actualMarkup = $this->Parsedown->text($markdown);
|
||||
|
||||
|
|
|
@ -5,4 +5,9 @@ echo $message;</code></pre>
|
|||
<hr />
|
||||
<pre><code>> not a quote
|
||||
- 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>
|
|
@ -7,4 +7,11 @@
|
|||
|
||||
> not a quote
|
||||
- not a list item
|
||||
[not a reference]: http://foo.com
|
||||
[not a reference]: http://foo.com
|
||||
|
||||
---
|
||||
|
||||
foo
|
||||
|
||||
|
||||
bar
|
|
@ -11,4 +11,8 @@ echo "Hello World";
|
|||
<a href="http://auraphp.com" >Aura Project</a></code></pre>
|
||||
<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>
|
|
@ -28,4 +28,11 @@ echo "Hello World";
|
|||
the following isn't quite enough to close
|
||||
```
|
||||
still a fenced code block
|
||||
````
|
||||
````
|
||||
|
||||
```
|
||||
foo
|
||||
|
||||
|
||||
bar
|
||||
```
|
|
@ -1,6 +0,0 @@
|
|||
<p>first line
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
sixth line</p>
|
|
@ -1,6 +0,0 @@
|
|||
first line
|
||||
|
||||
|
||||
|
||||
|
||||
sixth line
|
|
@ -66,4 +66,10 @@
|
|||
<td>cell 2.1</td>
|
||||
</tr>
|
||||
</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>
|
|
@ -22,4 +22,12 @@ cell 2.1
|
|||
header 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
|
|
@ -1,3 +1,4 @@
|
|||
<p><del>strikethrough</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>
|
|
@ -2,4 +2,6 @@
|
|||
|
||||
here's ~~one~~ followed by ~~another one~~
|
||||
|
||||
~~ this ~~ is not one neither is ~this~
|
||||
~~ this ~~ is not one neither is ~this~
|
||||
|
||||
escaped \~\~this\~\~
|
Loading…
Reference in New Issue
Block a user