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

Compare commits

...

42 Commits

Author SHA1 Message Date
d24439ada0 improve test suite 2014-05-21 23:20:46 +03:00
1ae100beab improve comment 2014-05-17 17:37:17 +03:00
82a5a78a36 improve readme 2014-05-17 17:13:00 +03:00
4ede4340ab improve readme 2014-05-16 03:34:43 +03:00
170a6bf770 improve readme 2014-05-16 01:27:54 +03:00
21db821324 improve readme 2014-05-16 01:15:21 +03:00
b384839d15 update readme 2014-05-14 20:07:52 +03:00
2da10d277b resolve #105 2014-05-14 13:14:49 +03:00
532b5ede35 resolve #129 2014-05-14 01:11:05 +03:00
2bd2f81f4f methods should not have more than one optional parameters 2014-05-12 16:18:00 +03:00
e318e66de5 improve consistency 2014-05-12 00:41:00 +03:00
0820d0a607 paragraph doesn't have to use a type 2014-05-12 00:34:47 +03:00
b8d1cfe91a improve extensibility 2014-05-11 22:31:02 +03:00
d85a233611 Merge pull request #171 from scarwu/master
identifyEscapeSequence() needs Array check
2014-05-11 20:57:05 +03:00
973d4a866d add array check 2014-05-11 23:36:01 +08:00
d19c2b6942 improve names 2014-05-10 16:28:00 +03:00
4dde57451d fix consecutive reference links 2014-05-06 17:05:49 +03:00
44686c4f1e improve extensibility 2014-05-06 01:12:27 +03:00
db02ecf259 "reference" is a definition 2014-05-05 14:43:31 +03:00
aa004d4595 improve code organisation 2014-05-05 14:39:40 +03:00
1bb65457ed remove unnecessary comments 2014-05-05 13:46:26 +03:00
0c9a4af8ab improve naming consistency 2014-05-03 18:02:06 +03:00
cc94c1b584 resolve #167 2014-05-02 18:21:10 +03:00
e8d8801db4 resolve #135 2014-05-01 02:44:35 +03:00
521803cdcd resolve #136 2014-05-01 02:42:01 +03:00
0eb480324c resolve #145 2014-05-01 02:02:14 +03:00
7c78aff578 resolve #163 2014-05-01 01:47:14 +03:00
2a5f99547c resolve #162 2014-05-01 01:05:31 +03:00
e373391e7d resolve #164 2014-05-01 00:29:21 +03:00
9fa415bcc5 resolve #160 2014-04-28 21:54:38 +03:00
37416b5f07 simplify folder name 2014-04-28 21:53:34 +03:00
83d3e3dbbf improve readability 2014-04-28 02:27:05 +03:00
307a987cb6 improve naming consistency 2014-04-28 02:14:46 +03:00
eab3cbf255 remove unnecessary nesting 2014-04-28 02:10:18 +03:00
cf7f32f891 definitions are not blocks 2014-04-27 01:54:52 +03:00
4150e00dc4 remove composer dependency 2014-04-26 01:09:28 +03:00
22affa124b travis should be able to run tests in 5.2 2014-04-26 01:06:15 +03:00
5e95242318 improve code consistency 2014-04-26 01:06:14 +03:00
504991a04e Merge pull request #158 from hkdobrev/travis-composer
Run composer install in Travis CI before tests
2014-04-25 00:06:06 +03:00
3d84201d74 Run composer install in Travis CI before tests
Tests are using the Composer autoloader since: cd1c030362

Because of that Composer should have actually dumped the autoloader in the `vendor/` folder,
before the tests are ran.
2014-04-25 00:01:20 +03:00
4f027386b1 "complete" calls should be more consistent 2014-04-24 23:52:42 +03:00
cd1c030362 tests should use autoloader 2014-04-24 22:44:30 +03:00
115 changed files with 552 additions and 451 deletions

View File

@ -7,3 +7,4 @@ php:
- 5.3
- 5.2
- hhvm

View File

@ -18,17 +18,19 @@ class Parsedown
#
# Philosophy
# Markdown is intended to be easy-to-read by humans - those of us who read
# line by line, left to right, top to bottom. In order to take advantage of
# this, Parsedown tries to read in a similar way. It breaks texts into
# lines, it iterates through them and it looks at how they start and relate
# to each other.
# Parsedown recognises that the Markdown syntax is optimised for humans so
# it tries to read like one. It goes through text line by line. It looks at
# how lines start to identify blocks. It looks for special characters to
# identify inline elements.
#
# ~
function text($text)
{
# make sure no definitions are set
$this->Definitions = array();
# standardize line breaks
$text = str_replace("\r\n", "\n", $text);
$text = str_replace("\r", "\n", $text);
@ -48,9 +50,6 @@ class Parsedown
# trim line breaks
$markup = trim($markup, "\n");
# clean up
$this->references = array();
return $markup;
}
@ -58,6 +57,8 @@ class Parsedown
# Setters
#
private $breaksEnabled;
function setBreaksEnabled($breaksEnabled)
{
$this->breaksEnabled = $breaksEnabled;
@ -65,13 +66,11 @@ class Parsedown
return $this;
}
private $breaksEnabled;
#
# Blocks
# Lines
#
protected $blockMarkers = array(
protected $BlockTypes = array(
'#' => array('Atx'),
'*' => array('Rule', 'List'),
'+' => array('List'),
@ -87,55 +86,52 @@ class Parsedown
'8' => array('List'),
'9' => array('List'),
':' => array('Table'),
'<' => array('Markup'),
'<' => array('Comment', 'Markup'),
'=' => array('Setext'),
'>' => array('Quote'),
'[' => array('Reference'),
'_' => array('Rule'),
'`' => array('FencedCode'),
'|' => array('Table'),
'~' => array('FencedCode'),
);
# Draft
protected $definitionMarkers = array(
# ~
protected $DefinitionTypes = array(
'[' => array('Reference'),
);
# ~
protected $unmarkedBlockTypes = array(
'CodeBlock',
);
#
# Blocks
#
private function lines(array $lines)
{
$CurrentBlock = null;
foreach ($lines as $line)
{
$indent = 0;
while (true)
{
if (isset($line[$indent]))
{
if ($line[$indent] === ' ')
{
$indent ++;
}
else
{
break;
}
}
else # blank line
if (chop($line) === '')
{
if (isset($CurrentBlock))
{
$CurrentBlock['interrupted'] = true;
}
continue 2;
continue;
}
$indent = 0;
while (isset($line[$indent]) and $line[$indent] === ' ')
{
$indent ++;
}
$text = $indent > 0 ? substr($line, $indent) : $line;
@ -144,7 +140,7 @@ class Parsedown
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
# Multiline block types define "addTo" methods.
# ~
if (isset($CurrentBlock['incomplete']))
{
@ -158,12 +154,31 @@ class Parsedown
}
else
{
unset($CurrentBlock['incomplete']);
if (method_exists($this, 'complete'.$CurrentBlock['type']))
{
$CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock);
}
unset($CurrentBlock['incomplete']);
}
}
# ~
$marker = $text[0];
if (isset($this->DefinitionTypes[$marker]))
{
foreach ($this->DefinitionTypes[$marker] as $definitionType)
{
$Definition = $this->{'identify'.$definitionType}($Line, $CurrentBlock);
if (isset($Definition))
{
$this->Definitions[$definitionType][$Definition['id']] = $Definition['data'];
continue 2;
}
}
}
@ -171,11 +186,9 @@ class Parsedown
$blockTypes = $this->unmarkedBlockTypes;
$marker = $text[0];
if (isset($this->blockMarkers[$marker]))
if (isset($this->BlockTypes[$marker]))
{
foreach ($this->blockMarkers[$marker] as $blockType)
foreach ($this->BlockTypes[$marker] as $blockType)
{
$blockTypes []= $blockType;
}
@ -186,23 +199,19 @@ class Parsedown
foreach ($blockTypes as $blockType)
{
# Block types define "identify" methods.
$Block = $this->{'identify'.$blockType}($Line, $CurrentBlock);
if (isset($Block))
{
$Block['type'] = $blockType;
if ( ! isset($Block['identified'])) # »
if ( ! isset($Block['identified']))
{
$elements []= $CurrentBlock['element'];
$Elements []= $CurrentBlock['element'];
$Block['identified'] = true;
}
# Multiline block types define "addTo" methods.
if (method_exists($this, 'addTo'.$blockType))
{
$Block['incomplete'] = true;
@ -216,33 +225,36 @@ class Parsedown
# ~
if ($CurrentBlock['type'] === 'Paragraph' and ! isset($CurrentBlock['interrupted']))
if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
{
$CurrentBlock['element']['text'] .= "\n".$text;
}
else
{
$elements []= $CurrentBlock['element'];
$Elements []= $CurrentBlock['element'];
$CurrentBlock = array(
'type' => 'Paragraph',
'identified' => true,
'element' => array(
'name' => 'p',
'text' => $text,
'handler' => 'line',
),
);
$CurrentBlock = $this->buildParagraph($Line);
$CurrentBlock['identified'] = true;
}
}
$elements []= $CurrentBlock['element'];
unset($elements[0]);
# ~
$markup = $this->elements($elements);
if (isset($CurrentBlock['incomplete']) and method_exists($this, 'complete'.$CurrentBlock['type']))
{
$CurrentBlock = $this->{'complete'.$CurrentBlock['type']}($CurrentBlock);
}
# ~
$Elements []= $CurrentBlock['element'];
unset($Elements[0]);
# ~
$markup = $this->elements($Elements);
# ~
@ -278,15 +290,22 @@ class Parsedown
}
#
# Rule
# Code
protected function identifyRule($Line)
protected function identifyCodeBlock($Line)
{
if (preg_match('/^(['.$Line['text'][0].'])([ ]{0,2}\1){2,}[ ]*$/', $Line['text']))
if ($Line['indent'] >= 4)
{
$text = substr($Line['body'], 4);
$Block = array(
'element' => array(
'name' => 'hr'
'name' => 'pre',
'handler' => 'element',
'text' => array(
'name' => 'code',
'text' => $text,
),
),
);
@ -294,106 +313,71 @@ class Parsedown
}
}
#
# Reference
protected function identifyReference($Line)
protected function addToCodeBlock($Line, $Block)
{
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
if ($Line['indent'] >= 4)
{
$label = strtolower($matches[1]);
$this->references[$label] = array(
'url' => $matches[2],
);
if (isset($matches[3]))
if (isset($Block['interrupted']))
{
$this->references[$label]['title'] = $matches[3];
$Block['element']['text']['text'] .= "\n";
unset($Block['interrupted']);
}
$Block = array(
'element' => null,
);
$Block['element']['text']['text'] .= "\n";
$text = substr($Line['body'], 4);
$Block['element']['text']['text'] .= $text;
return $Block;
}
}
#
# Setext
protected function completeCodeBlock($Block)
{
$text = $Block['element']['text']['text'];
protected function identifySetext($Line, array $Block = null)
{
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
{
return;
}
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
if (chop($Line['text'], $Line['text'][0]) === '')
{
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
$Block['element']['text']['text'] = $text;
return $Block;
}
}
#
# Markup
# Comment
protected function identifyMarkup($Line)
protected function identifyComment($Line)
{
if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>\/]*)?(\/?)[ ]*>/', $Line['text'], $matches))
if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
{
if (in_array($matches[1], $this->textLevelElements))
{
return;
}
$Block = array(
'element' => $Line['body'],
);
if ($matches[2] or $matches[1] === 'hr' or preg_match('/<\/'.$matches[1].'>[ ]*$/', $Line['text']))
if (preg_match('/-->$/', $Line['text']))
{
$Block['closed'] = true;
}
else
{
$Block['depth'] = 0;
$Block['start'] = '<'.$matches[1].'>';
$Block['end'] = '</'.$matches[1].'>';
}
return $Block;
}
}
protected function addToMarkup($Line, array $Block)
protected function addToComment($Line, array $Block)
{
if (isset($Block['closed']))
{
return;
}
if (stripos($Line['text'], $Block['start']) !== false) # opening tag
{
$Block['depth'] ++;
}
$Block['element'] .= "\n" . $Line['body'];
if (stripos($Line['text'], $Block['end']) !== false) # closing tag
{
if ($Block['depth'] > 0)
{
$Block['depth'] --;
}
else
if (preg_match('/-->$/', $Line['text']))
{
$Block['closed'] = true;
}
}
$Block['element'] .= "\n".$Line['body'];
return $Block;
}
@ -403,7 +387,7 @@ class Parsedown
protected function identifyFencedCode($Line)
{
if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*(\w+)?[ ]*$/', $Line['text'], $matches))
if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
{
$Element = array(
'name' => 'code',
@ -455,9 +439,18 @@ class Parsedown
return $Block;
}
$string = htmlspecialchars($Line['body'], ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] .= "\n".$Line['body'];;
$Block['element']['text']['text'] .= "\n".$string;;
return $Block;
}
protected function completeFencedCode($Block)
{
$text = $Block['element']['text']['text'];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] = $text;
return $Block;
}
@ -522,7 +515,7 @@ class Parsedown
if ( ! isset($Block['interrupted']))
{
$text = preg_replace('/^[ ]{0,2}/', '', $Line['body']);
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
$Block['li']['text'] []= $text;
@ -533,7 +526,7 @@ class Parsedown
{
$Block['li']['text'] []= '';
$text = preg_replace('/^[ ]{0,2}/', '', $Line['body']);
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
$Block['li']['text'] []= $text;
@ -569,6 +562,8 @@ class Parsedown
if (isset($Block['interrupted']))
{
$Block['element']['text'] []= '';
unset($Block['interrupted']);
}
$Block['element']['text'] []= $matches[1];
@ -584,12 +579,106 @@ class Parsedown
}
}
#
# Rule
protected function identifyRule($Line)
{
if (preg_match('/^(['.$Line['text'][0].'])([ ]{0,2}\1){2,}[ ]*$/', $Line['text']))
{
$Block = array(
'element' => array(
'name' => 'hr'
),
);
return $Block;
}
}
#
# Setext
protected function identifySetext($Line, array $Block = null)
{
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
{
return;
}
if (chop($Line['text'], $Line['text'][0]) === '')
{
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
return $Block;
}
}
#
# Markup
protected function identifyMarkup($Line)
{
if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>\/]*)?(\/?)[ ]*>/', $Line['text'], $matches))
{
if (in_array($matches[1], $this->textLevelElements))
{
return;
}
$Block = array(
'element' => $Line['body'],
);
if ($matches[2] or $matches[1] === 'hr' or preg_match('/<\/'.$matches[1].'>[ ]*$/', $Line['text']))
{
$Block['closed'] = true;
}
else
{
$Block['depth'] = 0;
$Block['name'] = $matches[1];
}
return $Block;
}
}
protected function addToMarkup($Line, array $Block)
{
if (isset($Block['closed']))
{
return;
}
if (preg_match('/<'.$Block['name'].'([ ][^\/]+)?>/', $Line['text'])) # opening tag
{
$Block['depth'] ++;
}
if (stripos($Line['text'], '</'.$Block['name'].'>') !== false) # closing tag
{
if ($Block['depth'] > 0)
{
$Block['depth'] --;
}
else
{
$Block['closed'] = true;
}
}
$Block['element'] .= "\n".$Line['body'];
return $Block;
}
#
# Table
protected function identifyTable($Line, array $Block = null)
{
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
{
return;
}
@ -740,49 +829,26 @@ class Parsedown
}
#
# Code
# Definitions
#
protected function identifyCodeBlock($Line)
protected function identifyReference($Line)
{
if ($Line['indent'] >= 4)
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
{
$text = substr($Line['body'], 4);
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block = array(
'element' => array(
'name' => 'pre',
'handler' => 'element',
'text' => array(
'name' => 'code',
'text' => $text,
),
$Definition = array(
'id' => strtolower($matches[1]),
'data' => array(
'url' => $matches[2],
),
);
return $Block;
}
if (isset($matches[3]))
{
$Definition['data']['title'] = $matches[3];
}
protected function addToCodeBlock($Line, $Block)
{
if ($Line['indent'] >= 4)
{
if (isset($Block['interrupted']))
{
$Block['element']['text']['text'] .= "\n";
unset($Block['interrupted']);
}
$Block['element']['text']['text'] .= "\n";
$text = substr($Line['body'], 4);
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$Block['element']['text']['text'] .= $text;
return $Block;
return $Definition;
}
}
@ -790,7 +856,24 @@ class Parsedown
# ~
#
private function element(array $Element)
protected function buildParagraph($Line)
{
$Block = array(
'element' => array(
'name' => 'p',
'text' => $Line['text'],
'handler' => 'line',
),
);
return $Block;
}
#
# ~
#
protected function element(array $Element)
{
$markup = '<'.$Element['name'];
@ -825,7 +908,7 @@ class Parsedown
return $markup;
}
private function elements(array $Elements)
protected function elements(array $Elements)
{
$markup = '';
@ -838,7 +921,7 @@ class Parsedown
$markup .= "\n";
if (is_string($Element)) # because of markup
if (is_string($Element)) # because of Markup
{
$markup .= $Element;
@ -857,7 +940,7 @@ class Parsedown
# Spans
#
protected $spanMarkers = array(
protected $SpanTypes = array(
'!' => array('Link'), # ?
'&' => array('Ampersand'),
'*' => array('Emphasis'),
@ -870,8 +953,14 @@ class Parsedown
'\\' => array('EscapeSequence'),
);
# ~
protected $spanMarkerList = '*_!&[</`~\\';
#
# ~
#
public function line($text)
{
$markup = '';
@ -880,20 +969,25 @@ class Parsedown
$markerPosition = 0;
while ($markedExcerpt = strpbrk($remainder, $this->spanMarkerList))
while ($excerpt = strpbrk($remainder, $this->spanMarkerList))
{
$marker = $markedExcerpt[0];
$marker = $excerpt[0];
$markerPosition += strpos($remainder, $marker);
foreach ($this->spanMarkers[$marker] as $spanType)
$Excerpt = array('text' => $excerpt, 'context' => $text);
foreach ($this->SpanTypes[$marker] as $spanType)
{
$handler = 'identify'.$spanType;
$Span = $this->$handler($markedExcerpt, $text);
$Span = $this->$handler($Excerpt);
if (isset($Span))
if ( ! isset($Span))
{
continue;
}
# The identified span can be ahead of the marker.
if (isset($Span['position']) and $Span['position'] > $markerPosition)
@ -908,11 +1002,11 @@ class Parsedown
$Span['position'] = $markerPosition;
}
$unmarkedText = substr($text, 0, $Span['position']);
$plainText = substr($text, 0, $Span['position']);
$markup .= $this->readPlainText($unmarkedText);
$markup .= $this->readPlainText($plainText);
$markup .= isset($Span['element']) ? $this->element($Span['element']) : $Span['markup'];
$markup .= isset($Span['markup']) ? $Span['markup'] : $this->element($Span['element']);
$text = substr($text, $Span['position'] + $Span['extent']);
@ -922,9 +1016,8 @@ class Parsedown
continue 2;
}
}
$remainder = substr($markedExcerpt, 1);
$remainder = substr($excerpt, 1);
$markerPosition ++;
}
@ -938,14 +1031,14 @@ class Parsedown
# ~
#
protected function identifyUrl($excerpt, $text)
protected function identifyUrl($Excerpt)
{
if ( ! isset($excerpt[1]) or $excerpt[1] !== '/')
if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '/')
{
return;
}
if (preg_match('/\bhttps?:[\/]{2}[^\s]+\b\/*/ui', $text, $matches, PREG_OFFSET_CAPTURE))
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
{
$url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[0][0]);
@ -963,9 +1056,9 @@ class Parsedown
}
}
protected function identifyAmpersand($excerpt)
protected function identifyAmpersand($Excerpt)
{
if ( ! preg_match('/^&#?\w+;/', $excerpt))
if ( ! preg_match('/^&#?\w+;/', $Excerpt['text']))
{
return array(
'markup' => '&amp;',
@ -974,14 +1067,14 @@ class Parsedown
}
}
protected function identifyStrikethrough($excerpt)
protected function identifyStrikethrough($Excerpt)
{
if ( ! isset($excerpt[1]))
if ( ! isset($Excerpt['text'][1]))
{
return;
}
if ($excerpt[1] === $excerpt[0] and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $excerpt, $matches))
if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
{
return array(
'extent' => strlen($matches[0]),
@ -994,12 +1087,12 @@ class Parsedown
}
}
protected function identifyEscapeSequence($excerpt)
protected function identifyEscapeSequence($Excerpt)
{
if (in_array($excerpt[1], $this->specialCharacters))
if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
{
return array(
'markup' => $excerpt[1],
'markup' => $Excerpt['text'][1],
'extent' => 2,
);
}
@ -1013,9 +1106,9 @@ class Parsedown
);
}
protected function identifyUrlTag($excerpt)
protected function identifyUrlTag($Excerpt)
{
if (strpos($excerpt, '>') !== false and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $excerpt, $matches))
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(https?:[\/]{2}[^\s]+?)>/i', $Excerpt['text'], $matches))
{
$url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
@ -1032,9 +1125,9 @@ class Parsedown
}
}
protected function identifyEmailTag($excerpt)
protected function identifyEmailTag($Excerpt)
{
if (strpos($excerpt, '>') !== false and preg_match('/<(\S+?@\S+?)>/', $excerpt, $matches))
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\S+?@\S+?)>/', $Excerpt['text'], $matches))
{
return array(
'extent' => strlen($matches[0]),
@ -1049,9 +1142,9 @@ class Parsedown
}
}
protected function identifyTag($excerpt)
protected function identifyTag($Excerpt)
{
if (strpos($excerpt, '>') !== false and preg_match('/^<\/?\w.*?>/', $excerpt, $matches))
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<\/?\w.*?>/', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
@ -1060,11 +1153,11 @@ class Parsedown
}
}
protected function identifyInlineCode($excerpt)
protected function identifyInlineCode($Excerpt)
{
$marker = $excerpt[0];
$marker = $Excerpt['text'][0];
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/', $excerpt, $matches))
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/', $Excerpt['text'], $matches))
{
$text = $matches[2];
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
@ -1079,25 +1172,25 @@ class Parsedown
}
}
protected function identifyLink($excerpt)
protected function identifyLink($Excerpt)
{
$extent = $excerpt[0] === '!' ? 1 : 0;
$extent = $Excerpt['text'][0] === '!' ? 1 : 0;
if (strpos($excerpt, ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $excerpt, $matches))
if (strpos($Excerpt['text'], ']') and preg_match('/\[((?:[^][]|(?R))*)\]/', $Excerpt['text'], $matches))
{
$Link = array('text' => $matches[1], 'label' => strtolower($matches[1]));
$extent += strlen($matches[0]);
$substring = substr($excerpt, $extent);
$substring = substr($Excerpt['text'], $extent);
if (preg_match('/^\s*\[(.+?)\]/', $substring, $matches))
if (preg_match('/^\s*\[([^][]+)\]/', $substring, $matches))
{
$Link['label'] = strtolower($matches[1]);
if (isset($this->references[$Link['label']]))
if (isset($this->Definitions['Reference'][$Link['label']]))
{
$Link += $this->references[$Link['label']];
$Link += $this->Definitions['Reference'][$Link['label']];
$extent += strlen($matches[0]);
}
@ -1106,9 +1199,9 @@ class Parsedown
return;
}
}
elseif ($this->references and isset($this->references[$Link['label']]))
elseif (isset($this->Definitions['Reference'][$Link['label']]))
{
$Link += $this->references[$Link['label']];
$Link += $this->Definitions['Reference'][$Link['label']];
if (preg_match('/^[ ]*\[\]/', $substring, $matches))
{
@ -1138,7 +1231,7 @@ class Parsedown
$url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Link['url']);
if ($excerpt[0] === '!')
if ($Excerpt['text'][0] === '!')
{
$Element = array(
'name' => 'img',
@ -1171,20 +1264,20 @@ class Parsedown
);
}
protected function identifyEmphasis($excerpt)
protected function identifyEmphasis($Excerpt)
{
if ( ! isset($excerpt[1]))
if ( ! isset($Excerpt['text'][1]))
{
return;
}
$marker = $excerpt[0];
$marker = $Excerpt['text'][0];
if ($excerpt[1] === $marker and preg_match($this->strongRegex[$marker], $excerpt, $matches))
if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
{
$emphasis = 'strong';
}
elseif (preg_match($this->emRegex[$marker], $excerpt, $matches))
elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
{
$emphasis = 'em';
}
@ -1276,7 +1369,7 @@ class Parsedown
# Fields
#
protected $references = array(); # » Definitions['reference']
protected $Definitions;
#
# Read-only
@ -1285,12 +1378,12 @@ class Parsedown
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!',
);
protected $strongRegex = array(
protected $StrongRegex = array(
'*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
'_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us',
);
protected $emRegex = array(
protected $EmRegex = array(
'*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
'_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us',
);
@ -1298,12 +1391,12 @@ class Parsedown
protected $textLevelElements = array(
'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
'i', 'rp', 'sub', 'code', 'strike', 'marquee',
'q', 'rt', 'sup', 'font', 'strong',
's', 'tt', 'var', 'mark',
'u', 'xm', 'wbr', 'nobr',
'ruby',
'span',
'i', 'rp', 'del', 'code', 'strike', 'marquee',
'q', 'rt', 'ins', 'font', 'strong',
's', 'tt', 'sub', 'mark',
'u', 'xm', 'sup', 'nobr',
'var', 'ruby',
'wbr', 'span',
'time',
);
}

View File

@ -2,8 +2,8 @@
Better [Markdown](http://en.wikipedia.org/wiki/Markdown) parser for PHP.
- [Demo](http://parsedown.org/demo)
- [Tests](http://parsedown.org/tests/)
* [Demo](http://parsedown.org/demo)
* [Test Suite](http://parsedown.org/tests/)
### Features
@ -12,6 +12,7 @@ Better [Markdown](http://en.wikipedia.org/wiki/Markdown) parser for PHP.
* [GitHub Flavored](https://help.github.com/articles/github-flavored-markdown)
* [Tested](https://travis-ci.org/erusev/parsedown) in PHP 5.2, 5.3, 5.4, 5.5, 5.6 and [hhvm](http://www.hhvm.com/)
* Extensible
* [Markdown Extra extension](https://github.com/erusev/parsedown-extra) <sup>new</sup>
### Installation
@ -24,3 +25,16 @@ $Parsedown = new Parsedown();
echo $Parsedown->text('Hello _Parsedown_!'); # prints: <p>Hello <em>Parsedown</em>!</p>
```
More examples in [the wiki](https://github.com/erusev/parsedown/wiki/Usage).
### Questions
**How does Parsedown work?**<br/>
Parsedown recognises that the Markdown syntax is optimised for humans so it tries to read like one. It goes through text line by line. It looks at how lines start to identify blocks. It looks for special characters to identify inline elements.
**Why doesnt Parsedown use namespaces?**<br/>
Using namespaces would mean dropping support for PHP 5.2. Since Parsedown is a single class with an uncommon name, making this trade wouldn't make much sense.
**Who uses Parsedown?**<br/>
[phpDocumentor](http://www.phpdoc.org/), [Bolt CMS](http://bolt.cm/), [RaspberryPi.org](http://www.raspberrypi.org/) and [more](https://www.versioneye.com/php/erusev:parsedown/references).

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<phpunit bootstrap="test/bootstrap.php" colors="true">
<testsuites>
<testsuite>
<file>tests/Test.php</file>
<file>test/Test.php</file>
</testsuite>
</testsuites>
</phpunit>

View File

@ -1,7 +1,5 @@
<?php
include 'Parsedown.php';
class Test extends PHPUnit_Framework_TestCase
{
public function __construct($name = null, array $data = array(), $dataName = '')

3
test/bootstrap.php Normal file
View File

@ -0,0 +1,3 @@
<?php
include 'Parsedown.php';

View File

@ -0,0 +1,5 @@
<!-- single line -->
<p>paragraph</p>
<!--
multiline -->
<p>paragraph</p>

View File

@ -0,0 +1,8 @@
<!-- single line -->
paragraph
<!--
multiline -->
paragraph

View File

@ -1,5 +1,8 @@
<div>_content_</div>
<p>sparse:</p>
<div>
<div class="inner">
_content_
</div>
</div>
<p>paragraph</p>

View File

@ -3,5 +3,9 @@
sparse:
<div>
<div class="inner">
_content_
</div>
</div>
paragraph

View File

@ -1,3 +1,4 @@
<p>an <a href="http://example.com">implicit</a> reference link</p>
<p>an <a href="http://example.com">implicit</a> reference link with an empty link definition</p>
<p>an <a href="http://example.com">implicit</a> reference link followed by <a href="http://cnn.com">another</a></p>
<p>an <a href="http://example.com" title="Example">explicit</a> reference link with a title</p>

View File

@ -4,6 +4,10 @@ an [implicit] reference link
an [implicit][] reference link with an empty link definition
an [implicit][] reference link followed by [another][]
[another]: http://cnn.com
an [explicit][example] reference link with a title
[example]: http://example.com "Example"

View File

@ -1,4 +1,6 @@
<blockquote>
<p>quote
the rest of it</p>
<p>another paragraph
the rest of it</p>
</blockquote>

View File

@ -0,0 +1,5 @@
> quote
the rest of it
> another paragraph
the rest of it

View File

@ -0,0 +1,12 @@
<hr>
<p>paragraph</p>
<hr/>
<p>paragraph</p>
<hr />
<p>paragraph</p>
<hr class="foo" id="bar" />
<p>paragraph</p>
<hr class="foo" id="bar"/>
<p>paragraph</p>
<hr class="foo" id="bar" >
<p>paragraph</p>

View File

@ -0,0 +1,12 @@
<hr>
paragraph
<hr/>
paragraph
<hr />
paragraph
<hr class="foo" id="bar" />
paragraph
<hr class="foo" id="bar"/>
paragraph
<hr class="foo" id="bar" >
paragraph

View File

@ -0,0 +1,9 @@
<ul>
<li>
<p>li</p>
<ul>
<li>li</li>
<li>li</li>
</ul>
</li>
</ul>

View File

@ -0,0 +1,4 @@
- li
- li
- li

View File

@ -0,0 +1,5 @@
<p>an <b>important</b> <a href=''>link</a></p>
<p>broken<br/>
line</p>
<p><b>inline tag</b> at the beginning</p>
<p><span><a href="http://example.com">http://example.com</a></span></p>

View File

@ -0,0 +1,8 @@
an <b>important</b> <a href=''>link</a>
broken<br/>
line
<b>inline tag</b> at the beginning
<span>http://example.com</span>

Some files were not shown because too many files have changed in this diff Show More