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

Compare commits

...

102 Commits

Author SHA1 Message Date
Emanuil Rusev
6598f3860c
update readme 2020-08-09 17:12:21 +03:00
Emanuil Rusev
1e5080190c
update readme
we already say "warning" in bold and upper case — the warning emoji feels unnecessary
2020-08-09 17:06:19 +03:00
Emanuil Rusev
1610e4747c
Merge pull request #741 from GrahamCampbell/patch-1
Update .travis.yml
2020-02-18 12:38:52 +02:00
Emanuil Rusev
3159a9d3cd
Merge pull request #751 from jeanmonod/patch-1
Update GitHub flavored markdown url
2020-01-22 09:53:45 +02:00
Jeanmonod David
dbee8ab4f2
Update GitHub flavored markdown url 2020-01-21 22:07:12 +01:00
Graham Campbell
dba4125b59
Update .travis.yml 2019-12-20 00:05:10 +00:00
Aidan Woods
bfaa76d370
Reflect travis breadth in README
Closes https://github.com/erusev/parsedown/issues/692
2019-04-07 16:36:22 +01:00
Aidan Woods
3825db53a2 Merge branch '1.8.x-beta' 2019-04-06 17:58:29 +01:00
Aidan Woods
fe7a50eceb
New release due to mislabeled previous tag 2019-03-17 18:47:21 +00:00
Aidan Woods
bce642f2d7
7.3 was released 2019-03-17 18:38:54 +00:00
Aidan Woods
7d4c06cb52
Bump version 2019-03-17 17:19:07 +00:00
Aidan Woods
f7b66e6b20
Merge pull request #701 from aidantwoods/fix/spaces-in-class-names-1.8.x-beta
[1.8.x-beta] Fix spaces in class names
2019-03-17 17:10:10 +00:00
Aidan Woods
811bc32726
Fix test platforms 2019-03-17 17:04:25 +00:00
Aidan Woods
8fd5464c46
[1.8.x-beta] Fix spaces in class names 2019-03-17 17:01:52 +00:00
Aidan Woods
21c8c792de
Merge pull request #698 from cybernet/patch-1
Symfony Demo link update
2019-03-14 18:49:20 +00:00
cybernet
6ca29539e1
Symfony Demo link update 2019-03-12 20:49:21 +00:00
Emanuil Rusev
a503c1a69b composer/composer#7990 2019-03-02 12:08:48 +02:00
Emanuil Rusev
819c68899d
Simplify installation instructions 2018-12-28 13:17:22 +02:00
Emanuil Rusev
4c2d79fc6a
More consistent code blocks in readme 2018-12-28 12:50:30 +02:00
Emanuil Rusev
48a2fb26fe
Add badges to readme 2018-12-28 12:47:13 +02:00
Emanuil Rusev
33b79d2446
More logo padding and no underline 2018-12-28 02:21:55 +02:00
Emanuil Rusev
d6d2d96459
Add repo name as h1 2018-12-28 02:19:44 +02:00
Emanuil Rusev
d5b6ab5198
Readme logo to be centered 2018-12-28 02:14:25 +02:00
Emanuil Rusev
15e8439c7f
Back to smaller padding in readme logo 2018-12-28 02:04:16 +02:00
Emanuil Rusev
ff6148f9b9
Improve readme badges 2018-12-28 01:56:45 +02:00
Emanuil Rusev
dfd8657bc5
h1 around logo in readme 2018-12-28 01:28:59 +02:00
Emanuil Rusev
ee64646765
More padding for logo in readme 2018-12-28 01:21:51 +02:00
Emanuil Rusev
c956090b55
Update readme logo 2018-12-28 01:07:49 +02:00
Emanuil Rusev
0be26550f3
Update readme logo 2018-12-28 00:55:48 +02:00
Emanuil Rusev
8e26a65a6f
More consistent letter case in readme 2018-12-28 00:41:14 +02:00
Emanuil Rusev
2a24a8583b
More consistent formatting in readme 2018-12-28 00:36:04 +02:00
Emanuil Rusev
1d55344e92
Simpler readme header 2018-12-27 23:59:37 +02:00
Emanuil Rusev
5dc8d1cc39
Simpler readme examples 2018-12-27 23:51:07 +02:00
Emanuil Rusev
33cf0f0b99
Centered header in readme 2018-12-27 23:46:53 +02:00
Emanuil Rusev
dc1ff7d6c2
Remove horizontal rule from readme 2018-12-27 22:50:39 +02:00
Emanuil Rusev
0f0987571d
Bigger headings in readme 2018-12-27 22:32:54 +02:00
Emanuil Rusev
18eaa649b5
Add logo to readme 2018-12-27 22:23:17 +02:00
Aidan Woods
e124572b60
Merge pull request #675 from andreybolonin/patch-1
add php 7.3
2018-11-16 07:45:32 +00:00
Andrey Bolonin
1686a34469
add php 7.3 to allow_failures 2018-11-16 09:34:17 +02:00
Aidan Woods
2bd7113c55
Merge pull request #676 from aidantwoods/fix/uninitialized-string-offset
Fix access to potentially uninitialised offset
2018-11-06 21:49:46 +00:00
Aidan Woods
29fce0ec37
Fix access to potentially uninitialised offset 2018-11-06 21:10:23 +00:00
Andrey Bolonin
b0bbc275d4
add php 7.3 2018-10-31 15:48:28 +02:00
Aidan Woods
72f9ca92ae
Merge pull request #671 from aidantwoods/fix/line-ending-standardisation
Ensure line-breaks get standardised when using Parsedown via `line` method
2018-10-16 18:51:05 +01:00
Aidan Woods
89c3fa05d9
Ensure line-breaks get standardised when using via line method
As noted in https://github.com/erusev/parsedown/pull/624 there are
occasions where line break standardisation is assumed (e.g. where
`inlineCode` replaces line breaks with a space).

Closes #624
2018-10-16 18:41:42 +01:00
Aidan Woods
69163d6e88
Merge pull request #670 from tillkruss/patch-1
Add "tel:" to whitelist
2018-10-14 21:48:40 +01:00
Till Krüss
3a0c964291
Add "tel:" to whitelist 2018-10-02 16:38:21 -07:00
Aidan Woods
1829106e60
Merge pull request #668 from itshoro/block-heading-min
remove redundant header level cap
2018-09-19 21:28:02 +01:00
horodev
464f5f9329 removed min function as it is redundant in the context 2018-09-19 17:36:40 +02:00
Aidan Woods
c26a2ee4bf
Bump beta version 2018-06-11 19:15:32 +01:00
Aidan Woods
ba3b60d6e4
Merge pull request #641 from aidantwoods/fix/api-stability-complete-function-removal
Restore existence of protected API methods
2018-06-08 14:38:42 +01:00
Aidan Woods
0b1e6b8c86
Restore existence of protected API methods 2018-06-07 19:47:09 +01:00
Aidan Woods
1f69f7e697
Bump version 2018-05-08 22:46:15 +01:00
Aidan Woods
c83af0a7d5
Merge pull request #628 from aidantwoods/fix/revert-rawHtml-breaks-insertion
Preserve plain-text in AST to avoid blinding extensions to it
2018-05-08 22:41:44 +01:00
Aidan Woods
4686daf8c2
Preserve plain-text in AST to avoid blinding extensions to it 2018-05-08 22:32:57 +01:00
Aidan Woods
c9e7183cfa
Merge pull request #627 from aidantwoods/fix/hidden-blocks
Intepret special "hidden" key as an empty element
2018-05-08 22:07:51 +01:00
Aidan Woods
9eed1104e7
Intepret special "hidden" key as an empty element 2018-05-08 21:54:30 +01:00
Aidan Woods
fd95703da5
Version bump 2018-05-07 14:26:12 +01:00
Aidan Woods
8d172a2994
Merge pull request #614 from aidantwoods/enhancement/performance-tweaks
General and performance tweaks
2018-05-07 11:18:21 +01:00
Aidan Woods
dfab7240a4
Merge pull request #621 from paukenba/master
Tilde characters may be escaped
2018-04-24 17:39:09 +01:00
Attila Vachter
113c6d2b21 Tilde characters may be escaped 2018-04-23 15:09:30 +02:00
Aidan Woods
a9764ec90f
Remove complex string interpolation expressions 2018-04-14 15:27:06 +01:00
Aidan Woods
0a842fb5b1
Merge pull request #615 from aidantwoods/fix/old-handler-compat
Compatability fixes
2018-04-12 22:29:29 +01:00
Aidan Woods
7f4318dbdb
PHP 5.3 == 💩 2018-04-12 22:22:53 +01:00
Aidan Woods
3e70819a20
Readability improvements, thanks @PhrozenByte 2018-04-12 22:16:25 +01:00
Aidan Woods
2bf7ca41a0
Add compat for extensions using old markup key. 2018-04-12 21:25:50 +01:00
Aidan Woods
b75fd409ff
Must unset text key so that our destination is preferred as content 2018-04-12 21:10:09 +01:00
Aidan Woods
88a3f31dd7
Rewrite as one statement 2018-04-12 19:33:01 +01:00
Aidan Woods
726d4ef44a
Sanity checks before starting regex engine 2018-04-09 18:09:45 +01:00
Aidan Woods
450a74fedf
More expensive statement last 2018-04-09 18:09:45 +01:00
Aidan Woods
7e15d99d90
Remove regex from block rule 2018-04-09 18:09:44 +01:00
Aidan Woods
d2dd736e1b
Remove regex from fenced code block
Also remove unused function
2018-04-09 18:09:44 +01:00
Aidan Woods
e74a5bd7ed
In theory PHP stores the length of strings, so looking this up should be quick 2018-04-09 18:09:44 +01:00
Aidan Woods
b53aa74a72
Use standard library function 2018-04-09 18:09:44 +01:00
Aidan Woods
3ea08140b6
Remove use of array 2018-04-09 18:09:44 +01:00
Aidan Woods
c45e41950f
Use standard library over while loop 2018-04-09 18:09:44 +01:00
Aidan Woods
2faba6fef5
Remove unneeded complete function 2018-04-09 18:09:44 +01:00
Aidan Woods
b42add3762
Make some regexes possesive 2018-04-09 18:09:43 +01:00
Aidan Woods
107223d3a0
Avoid recomputation 2018-04-09 18:09:43 +01:00
Aidan Woods
d4f1ac465c
String interpolation is slightly faster than concat 2018-04-09 18:09:43 +01:00
Aidan Woods
d6e306d620
Optimise commonly used regexes to fail fast 2018-04-09 18:09:04 +01:00
Aidan Woods
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
Aidan Woods
70f5c02d47
Use non-nestable values as keys for O(1) lookup 2018-04-09 18:09:04 +01:00
Aidan Woods
90ad738933
General readability 2018-04-09 18:09:04 +01:00
Aidan Woods
f2327023c1
No need to unset if not set 2018-04-09 18:09:04 +01:00
Aidan Woods
6f13f97674
Use mutating loop instead of array_map 2018-04-09 18:08:58 +01:00
Aidan Woods
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
Aidan Woods
cb33daf0e6
Assert table header does not contain new lines 2018-04-09 16:38:03 +01:00
Aidan Woods
c440c91af5
Add failing test case 2018-04-09 16:32:36 +01:00
Aidan Woods
3514881e14
Merge pull request #611 from aidantwoods/enhancement/paragraph-block-semantics
Paragraph block semantics
2018-04-09 16:30:33 +01:00
Aidan Woods
043c55e4c6
Give paragraph block semantics for overloading 2018-04-09 15:12:17 +01:00
Aidan Woods
e4cd13350b
Remove setLiteralBreaks 2018-04-09 15:11:45 +01:00
Aidan Woods
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
Aidan Woods
5353ebb524
Avoid needing two arrays
We only need to collect elements, we can discard finished blocks
2018-04-09 14:48:39 +01:00
Aidan Woods
39df7d4f8e
Swap 'hidden' blocks for empty elements 2018-04-09 14:46:24 +01:00
Aidan Woods
50f15add44
Merge pull request #610 from aidantwoods/fix/lost-line-breaks
Fix lost line breaks
2018-04-09 14:19:38 +01:00
Aidan Woods
3f5b0ee781
Count number of interrupts 2018-04-09 14:13:10 +01:00
Aidan Woods
9a021b2130
Add failing test cases 2018-04-09 14:11:49 +01:00
Aidan Woods
43d25a74fe
Fix function name 2018-04-08 18:40:50 +01:00
Aidan Woods
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
Aidan Woods
86940be224
Use mutating loop instead of creating new array 2018-04-08 17:49:36 +01:00
Aidan Woods
cdaf86b039
Add seperate depth-first function instead of replacing recursive method 2018-04-08 17:39:24 +01:00
Aidan Woods
1d65fb858a
Restore file permission to that of 1.7.1 2018-04-08 14:30:23 +01:00
14 changed files with 348 additions and 282 deletions

View File

@ -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
View 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',

View File

@ -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 :)

View File

@ -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);

View File

@ -5,4 +5,9 @@ echo $message;</code></pre>
<hr />
<pre><code>&gt; 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>

View File

@ -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

View File

@ -11,4 +11,8 @@ echo "Hello World";
&lt;a href="http://auraphp.com" &gt;Aura Project&lt;/a&gt;</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>

View File

@ -28,4 +28,11 @@ echo "Hello World";
the following isn't quite enough to close
```
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

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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\~\~