mirror of
https://github.com/erusev/parsedown.git
synced 2023-08-10 21:13:06 +03:00
Compare commits
23 Commits
1.0.0-rc.4
...
1.0.1
Author | SHA1 | Date | |
---|---|---|---|
d24439ada0 | |||
1ae100beab | |||
82a5a78a36 | |||
4ede4340ab | |||
170a6bf770 | |||
21db821324 | |||
b384839d15 | |||
2da10d277b | |||
532b5ede35 | |||
2bd2f81f4f | |||
e318e66de5 | |||
0820d0a607 | |||
b8d1cfe91a | |||
d85a233611 | |||
973d4a866d | |||
d19c2b6942 | |||
4dde57451d | |||
44686c4f1e | |||
db02ecf259 | |||
aa004d4595 | |||
1bb65457ed | |||
0c9a4af8ab | |||
cc94c1b584 |
438
Parsedown.php
438
Parsedown.php
@ -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->definitions = 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,24 +86,31 @@ 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'),
|
||||
);
|
||||
|
||||
protected $definitionMarkers = array(
|
||||
# ~
|
||||
|
||||
protected $DefinitionTypes = array(
|
||||
'[' => array('Reference'),
|
||||
);
|
||||
|
||||
# ~
|
||||
|
||||
protected $unmarkedBlockTypes = array(
|
||||
'CodeBlock',
|
||||
);
|
||||
|
||||
#
|
||||
# Blocks
|
||||
#
|
||||
|
||||
private function lines(array $lines)
|
||||
{
|
||||
$CurrentBlock = null;
|
||||
@ -134,7 +140,7 @@ class Parsedown
|
||||
|
||||
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
|
||||
|
||||
# Multiline block types define "addTo" methods.
|
||||
# ~
|
||||
|
||||
if (isset($CurrentBlock['incomplete']))
|
||||
{
|
||||
@ -161,17 +167,15 @@ class Parsedown
|
||||
|
||||
$marker = $text[0];
|
||||
|
||||
# Definitions
|
||||
|
||||
if (isset($this->definitionMarkers[$marker]))
|
||||
if (isset($this->DefinitionTypes[$marker]))
|
||||
{
|
||||
foreach ($this->definitionMarkers[$marker] as $definitionType)
|
||||
foreach ($this->DefinitionTypes[$marker] as $definitionType)
|
||||
{
|
||||
$Definition = $this->{'identify'.$definitionType}($Line, $CurrentBlock);
|
||||
|
||||
if (isset($Definition))
|
||||
{
|
||||
$this->definitions[$definitionType][$Definition['id']] = $Definition['data'];
|
||||
$this->Definitions[$definitionType][$Definition['id']] = $Definition['data'];
|
||||
|
||||
continue 2;
|
||||
}
|
||||
@ -182,9 +186,9 @@ class Parsedown
|
||||
|
||||
$blockTypes = $this->unmarkedBlockTypes;
|
||||
|
||||
if (isset($this->blockMarkers[$marker]))
|
||||
if (isset($this->BlockTypes[$marker]))
|
||||
{
|
||||
foreach ($this->blockMarkers[$marker] as $blockType)
|
||||
foreach ($this->BlockTypes[$marker] as $blockType)
|
||||
{
|
||||
$blockTypes []= $blockType;
|
||||
}
|
||||
@ -195,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'];
|
||||
|
||||
$Block['identified'] = true;
|
||||
}
|
||||
|
||||
# Multiline block types define "addTo" methods.
|
||||
|
||||
if (method_exists($this, 'addTo'.$blockType))
|
||||
{
|
||||
$Block['incomplete'] = true;
|
||||
@ -225,7 +225,7 @@ 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;
|
||||
}
|
||||
@ -233,15 +233,9 @@ class Parsedown
|
||||
{
|
||||
$Elements []= $CurrentBlock['element'];
|
||||
|
||||
$CurrentBlock = array(
|
||||
'type' => 'Paragraph',
|
||||
'identified' => true,
|
||||
'element' => array(
|
||||
'name' => 'p',
|
||||
'text' => $text,
|
||||
'handler' => 'line',
|
||||
),
|
||||
);
|
||||
$CurrentBlock = $this->buildParagraph($Line);
|
||||
|
||||
$CurrentBlock['identified'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -312,104 +313,72 @@ class Parsedown
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Reference
|
||||
|
||||
protected function identifyReference($Line)
|
||||
protected function addToCodeBlock($Line, $Block)
|
||||
{
|
||||
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
|
||||
if ($Line['indent'] >= 4)
|
||||
{
|
||||
$Definition = array(
|
||||
'id' => strtolower($matches[1]),
|
||||
'data' => array(
|
||||
'url' => $matches[2],
|
||||
),
|
||||
);
|
||||
|
||||
if (isset($matches[3]))
|
||||
if (isset($Block['interrupted']))
|
||||
{
|
||||
$Definition['data']['title'] = $matches[3];
|
||||
$Block['element']['text']['text'] .= "\n";
|
||||
|
||||
unset($Block['interrupted']);
|
||||
}
|
||||
|
||||
return $Definition;
|
||||
}
|
||||
}
|
||||
$Block['element']['text']['text'] .= "\n";
|
||||
|
||||
#
|
||||
# Setext
|
||||
$text = substr($Line['body'], 4);
|
||||
|
||||
protected function identifySetext($Line, array $Block = null)
|
||||
{
|
||||
if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (chop($Line['text'], $Line['text'][0]) === '')
|
||||
{
|
||||
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
|
||||
$Block['element']['text']['text'] .= $text;
|
||||
|
||||
return $Block;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Markup
|
||||
|
||||
protected function identifyMarkup($Line)
|
||||
protected function completeCodeBlock($Block)
|
||||
{
|
||||
if (preg_match('/^<(\w[\w\d]*)(?:[ ][^>\/]*)?(\/?)[ ]*>/', $Line['text'], $matches))
|
||||
{
|
||||
if (in_array($matches[1], $this->textLevelElements))
|
||||
{
|
||||
return;
|
||||
}
|
||||
$text = $Block['element']['text']['text'];
|
||||
|
||||
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
$Block['element']['text']['text'] = $text;
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
#
|
||||
# Comment
|
||||
|
||||
protected function identifyComment($Line)
|
||||
{
|
||||
if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
|
||||
{
|
||||
$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 (preg_match('/-->$/', $Line['text']))
|
||||
{
|
||||
if ($Block['depth'] > 0)
|
||||
{
|
||||
$Block['depth'] --;
|
||||
}
|
||||
else
|
||||
{
|
||||
$Block['closed'] = true;
|
||||
}
|
||||
$Block['closed'] = true;
|
||||
}
|
||||
|
||||
$Block['element'] .= "\n".$Line['body'];
|
||||
|
||||
return $Block;
|
||||
}
|
||||
|
||||
@ -610,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;
|
||||
}
|
||||
@ -766,57 +829,42 @@ 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);
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
protected function addToCodeBlock($Line, $Block)
|
||||
{
|
||||
if ($Line['indent'] >= 4)
|
||||
{
|
||||
if (isset($Block['interrupted']))
|
||||
if (isset($matches[3]))
|
||||
{
|
||||
$Block['element']['text']['text'] .= "\n";
|
||||
|
||||
unset($Block['interrupted']);
|
||||
$Definition['data']['title'] = $matches[3];
|
||||
}
|
||||
|
||||
$Block['element']['text']['text'] .= "\n";
|
||||
|
||||
$text = substr($Line['body'], 4);
|
||||
|
||||
$Block['element']['text']['text'] .= $text;
|
||||
|
||||
return $Block;
|
||||
return $Definition;
|
||||
}
|
||||
}
|
||||
|
||||
protected function completeCodeBlock($Block)
|
||||
#
|
||||
# ~
|
||||
#
|
||||
|
||||
protected function buildParagraph($Line)
|
||||
{
|
||||
$text = $Block['element']['text']['text'];
|
||||
|
||||
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
$Block['element']['text']['text'] = $text;
|
||||
$Block = array(
|
||||
'element' => array(
|
||||
'name' => 'p',
|
||||
'text' => $Line['text'],
|
||||
'handler' => 'line',
|
||||
),
|
||||
);
|
||||
|
||||
return $Block;
|
||||
}
|
||||
@ -825,7 +873,7 @@ class Parsedown
|
||||
# ~
|
||||
#
|
||||
|
||||
private function element(array $Element)
|
||||
protected function element(array $Element)
|
||||
{
|
||||
$markup = '<'.$Element['name'];
|
||||
|
||||
@ -860,7 +908,7 @@ class Parsedown
|
||||
return $markup;
|
||||
}
|
||||
|
||||
private function elements(array $Elements)
|
||||
protected function elements(array $Elements)
|
||||
{
|
||||
$markup = '';
|
||||
|
||||
@ -873,7 +921,7 @@ class Parsedown
|
||||
|
||||
$markup .= "\n";
|
||||
|
||||
if (is_string($Element)) # because of markup
|
||||
if (is_string($Element)) # because of Markup
|
||||
{
|
||||
$markup .= $Element;
|
||||
|
||||
@ -892,7 +940,7 @@ class Parsedown
|
||||
# Spans
|
||||
#
|
||||
|
||||
protected $spanMarkers = array(
|
||||
protected $SpanTypes = array(
|
||||
'!' => array('Link'), # ?
|
||||
'&' => array('Ampersand'),
|
||||
'*' => array('Emphasis'),
|
||||
@ -905,8 +953,14 @@ class Parsedown
|
||||
'\\' => array('EscapeSequence'),
|
||||
);
|
||||
|
||||
# ~
|
||||
|
||||
protected $spanMarkerList = '*_!&[</`~\\';
|
||||
|
||||
#
|
||||
# ~
|
||||
#
|
||||
|
||||
public function line($text)
|
||||
{
|
||||
$markup = '';
|
||||
@ -915,17 +969,19 @@ 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))
|
||||
{
|
||||
@ -950,7 +1006,7 @@ class Parsedown
|
||||
|
||||
$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']);
|
||||
|
||||
@ -961,7 +1017,7 @@ class Parsedown
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$remainder = substr($markedExcerpt, 1);
|
||||
$remainder = substr($excerpt, 1);
|
||||
|
||||
$markerPosition ++;
|
||||
}
|
||||
@ -975,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('&', '<'), $matches[0][0]);
|
||||
|
||||
@ -1000,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' => '&',
|
||||
@ -1011,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]),
|
||||
@ -1031,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,
|
||||
);
|
||||
}
|
||||
@ -1050,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('&', '<'), $matches[1]);
|
||||
|
||||
@ -1069,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]),
|
||||
@ -1086,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],
|
||||
@ -1097,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');
|
||||
@ -1116,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->definitions['Reference'][$Link['label']]))
|
||||
if (isset($this->Definitions['Reference'][$Link['label']]))
|
||||
{
|
||||
$Link += $this->definitions['Reference'][$Link['label']];
|
||||
$Link += $this->Definitions['Reference'][$Link['label']];
|
||||
|
||||
$extent += strlen($matches[0]);
|
||||
}
|
||||
@ -1143,9 +1199,9 @@ class Parsedown
|
||||
return;
|
||||
}
|
||||
}
|
||||
elseif (isset($this->definitions['Reference'][$Link['label']]))
|
||||
elseif (isset($this->Definitions['Reference'][$Link['label']]))
|
||||
{
|
||||
$Link += $this->definitions['Reference'][$Link['label']];
|
||||
$Link += $this->Definitions['Reference'][$Link['label']];
|
||||
|
||||
if (preg_match('/^[ ]*\[\]/', $substring, $matches))
|
||||
{
|
||||
@ -1175,7 +1231,7 @@ class Parsedown
|
||||
|
||||
$url = str_replace(array('&', '<'), array('&', '<'), $Link['url']);
|
||||
|
||||
if ($excerpt[0] === '!')
|
||||
if ($Excerpt['text'][0] === '!')
|
||||
{
|
||||
$Element = array(
|
||||
'name' => 'img',
|
||||
@ -1208,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';
|
||||
}
|
||||
@ -1313,7 +1369,7 @@ class Parsedown
|
||||
# Fields
|
||||
#
|
||||
|
||||
protected $definitions;
|
||||
protected $Definitions;
|
||||
|
||||
#
|
||||
# Read-only
|
||||
@ -1322,12 +1378,12 @@ class Parsedown
|
||||
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!',
|
||||
);
|
||||
|
||||
protected $strongRegex = array(
|
||||
protected $StrongRegex = array(
|
||||
'*' => '/^[*]{2}((?:[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
|
||||
'_' => '/^__((?:[^_]|_[^_]*_)+?)__(?!_)/us',
|
||||
);
|
||||
|
||||
protected $emRegex = array(
|
||||
protected $EmRegex = array(
|
||||
'*' => '/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
|
||||
'_' => '/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us',
|
||||
);
|
||||
|
18
README.md
18
README.md
@ -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 doesn’t 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).
|
||||
|
5
test/data/HTML_Comment.html
Normal file
5
test/data/HTML_Comment.html
Normal file
@ -0,0 +1,5 @@
|
||||
<!-- single line -->
|
||||
<p>paragraph</p>
|
||||
<!--
|
||||
multiline -->
|
||||
<p>paragraph</p>
|
8
test/data/HTML_Comment.md
Normal file
8
test/data/HTML_Comment.md
Normal file
@ -0,0 +1,8 @@
|
||||
<!-- single line -->
|
||||
|
||||
paragraph
|
||||
|
||||
<!--
|
||||
multiline -->
|
||||
|
||||
paragraph
|
@ -1,5 +1,8 @@
|
||||
<div>_content_</div>
|
||||
<p>sparse:</p>
|
||||
<div>
|
||||
<div class="inner">
|
||||
_content_
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>paragraph</p>
|
@ -3,5 +3,9 @@
|
||||
sparse:
|
||||
|
||||
<div>
|
||||
<div class="inner">
|
||||
_content_
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
paragraph
|
@ -1,28 +0,0 @@
|
||||
<p>Headings:</p>
|
||||
<h2 id="overview">Overview</h2>
|
||||
<p>blah</p>
|
||||
<H2 id="block">Block Elements</H2>
|
||||
<p>blah</p>
|
||||
<h3 id="span">
|
||||
Span Elements
|
||||
</h3>
|
||||
<p>blah</p>
|
||||
<p>Hr's:</p>
|
||||
<hr>
|
||||
<p>blah</p>
|
||||
<hr/>
|
||||
<p>blah</p>
|
||||
<hr />
|
||||
<p>blah</p>
|
||||
<hr>
|
||||
<p>blah</p>
|
||||
<hr/>
|
||||
<p>blah</p>
|
||||
<hr />
|
||||
<p>blah</p>
|
||||
<hr class="foo" id="bar" />
|
||||
<p>blah</p>
|
||||
<hr class="foo" id="bar"/>
|
||||
<p>blah</p>
|
||||
<hr class="foo" id="bar" >
|
||||
<p>blah</p>
|
@ -1,39 +0,0 @@
|
||||
Headings:
|
||||
|
||||
<h2 id="overview">Overview</h2>
|
||||
blah
|
||||
<H2 id="block">Block Elements</H2>
|
||||
blah
|
||||
<h3 id="span">
|
||||
Span Elements
|
||||
</h3>
|
||||
blah
|
||||
|
||||
Hr's:
|
||||
|
||||
<hr>
|
||||
blah
|
||||
|
||||
<hr/>
|
||||
blah
|
||||
|
||||
<hr />
|
||||
blah
|
||||
|
||||
<hr>
|
||||
blah
|
||||
|
||||
<hr/>
|
||||
blah
|
||||
|
||||
<hr />
|
||||
blah
|
||||
|
||||
<hr class="foo" id="bar" />
|
||||
blah
|
||||
|
||||
<hr class="foo" id="bar"/>
|
||||
blah
|
||||
|
||||
<hr class="foo" id="bar" >
|
||||
blah
|
@ -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>
|
@ -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"
|
@ -1,4 +0,0 @@
|
||||
<hr />
|
||||
<p>attributes:</p>
|
||||
<hr style="background: #9bd;" />
|
||||
<p>...</p>
|
@ -1,7 +0,0 @@
|
||||
<hr />
|
||||
|
||||
attributes:
|
||||
|
||||
<hr style="background: #9bd;" />
|
||||
|
||||
...
|
12
test/data/self-closing_html.html
Normal file
12
test/data/self-closing_html.html
Normal 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>
|
12
test/data/self-closing_html.md
Normal file
12
test/data/self-closing_html.md
Normal 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
|
@ -1,4 +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><b>inline tag</b> at the beginning</p>
|
||||
<p><span><a href="http://example.com">http://example.com</a></span></p>
|
@ -3,4 +3,6 @@ an <b>important</b> <a href=''>link</a>
|
||||
broken<br/>
|
||||
line
|
||||
|
||||
<b>inline tag</b> at the beginning
|
||||
<b>inline tag</b> at the beginning
|
||||
|
||||
<span>http://example.com</span>
|
Reference in New Issue
Block a user