mirror of
https://github.com/erusev/parsedown.git
synced 2023-08-10 21:13:06 +03:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
85ad014f74 | |||
22336a1bcc | |||
f713e380ee | |||
5b01915a63 | |||
18d112a614 | |||
1b9641ad03 | |||
8baf537c12 | |||
05823567bc | |||
b7029ab176 | |||
102a947c7a | |||
7bb70186c1 | |||
3225c66863 | |||
d6dc5ba25b | |||
f5451a9eff | |||
849a89b121 | |||
28064a63b3 | |||
800aac5b56 | |||
b15d40e8a3 | |||
ddc5b7e2dd | |||
5a563008aa | |||
b6f795962f | |||
cdb2646063 | |||
e3b8026e39 | |||
d96f668c42 | |||
96bf75bd91 | |||
67b51794d8 | |||
a9d6232705 | |||
b91629ad94 | |||
24d300ea5d | |||
d54712b989 | |||
6ef043ba7d | |||
fe27b70bdb | |||
18d3dbf4f6 | |||
4758f58f73 | |||
5fa3eb1b2f | |||
38300323a6 | |||
96609329b9 |
@ -5,7 +5,3 @@ php:
|
||||
- 5.4
|
||||
- 5.3
|
||||
- 5.2
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- php: 5.2
|
816
Parsedown.php
816
Parsedown.php
@ -46,17 +46,17 @@ class Parsedown
|
||||
|
||||
function parse($text)
|
||||
{
|
||||
# Removes UTF-8 BOM and marker characters.
|
||||
# removes UTF-8 BOM and marker characters
|
||||
$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);
|
||||
|
||||
# Removes \r characters.
|
||||
# removes \r characters
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
$text = str_replace("\r", "\n", $text);
|
||||
|
||||
# Replaces tabs with spaces.
|
||||
# replaces tabs with spaces
|
||||
$text = str_replace("\t", ' ', $text);
|
||||
|
||||
# Encodes escape sequences.
|
||||
# encodes escape sequences
|
||||
|
||||
if (strpos($text, '\\') !== FALSE)
|
||||
{
|
||||
@ -84,7 +84,7 @@ class Parsedown
|
||||
|
||||
$text = $this->parse_block_elements($lines);
|
||||
|
||||
# Decodes escape sequences (leaves out backslashes).
|
||||
# decodes escape sequences
|
||||
|
||||
foreach ($this->escape_sequence_map as $code => $escape_sequence)
|
||||
{
|
||||
@ -110,28 +110,55 @@ class Parsedown
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
# Block-Level HTML
|
||||
# fenced elements
|
||||
|
||||
if ($element['type'] === 'block' and ! isset($element['closed']))
|
||||
switch ($element['type'])
|
||||
{
|
||||
if (preg_match('{<'.$element['subtype'].'>$}', $line)) # <open>
|
||||
{
|
||||
$element['depth']++;
|
||||
}
|
||||
case 'fenced_code_block':
|
||||
|
||||
if (preg_match('{</'.$element['subtype'].'>$}', $line)) # </close>
|
||||
{
|
||||
$element['depth'] > 0
|
||||
? $element['depth']--
|
||||
: $element['closed'] = true;
|
||||
}
|
||||
if ( ! isset($element['closed']))
|
||||
{
|
||||
if (preg_match('/^[ ]*'.$element['fence'][0].'{3,}[ ]*$/', $line))
|
||||
{
|
||||
$element['closed'] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$element['text'] !== '' and $element['text'] .= "\n";
|
||||
|
||||
$element['text'] .= "\n".$line;
|
||||
$element['text'] .= $line;
|
||||
}
|
||||
|
||||
continue;
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'markup':
|
||||
|
||||
if ( ! isset($element['closed']))
|
||||
{
|
||||
if (preg_match('{<'.$element['subtype'].'>$}', $line)) # opening tag
|
||||
{
|
||||
$element['depth']++;
|
||||
}
|
||||
|
||||
if (preg_match('{</'.$element['subtype'].'>$}', $line)) # closing tag
|
||||
{
|
||||
$element['depth'] > 0
|
||||
? $element['depth']--
|
||||
: $element['closed'] = true;
|
||||
}
|
||||
|
||||
$element['text'] .= "\n".$line;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
# Empty
|
||||
# *
|
||||
|
||||
if ($line === '')
|
||||
{
|
||||
@ -140,269 +167,329 @@ class Parsedown
|
||||
continue;
|
||||
}
|
||||
|
||||
# Lazy Blockquote
|
||||
# composite elements
|
||||
|
||||
if ($element['type'] === 'blockquote' and ! isset($element['interrupted']))
|
||||
switch ($element['type'])
|
||||
{
|
||||
$line = preg_replace('/^[ ]*>[ ]?/', '', $line);
|
||||
case 'blockquote':
|
||||
|
||||
$element['lines'] []= $line;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# Lazy List Item
|
||||
|
||||
if ($element['type'] === 'li')
|
||||
{
|
||||
if (preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches))
|
||||
{
|
||||
if ($element['indentation'] !== $matches[1])
|
||||
if ( ! isset($element['interrupted']))
|
||||
{
|
||||
$line = preg_replace('/^[ ]*>[ ]?/', '', $line);
|
||||
|
||||
$element['lines'] []= $line;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'li':
|
||||
|
||||
if (preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches))
|
||||
{
|
||||
if ($element['indentation'] !== $matches[1])
|
||||
{
|
||||
$element['lines'] []= $line;
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($element['last']);
|
||||
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'li',
|
||||
'indentation' => $matches[1],
|
||||
'last' => true,
|
||||
'lines' => array(
|
||||
preg_replace('/^[ ]{0,4}/', '', $matches[3]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
if ($line[0] === ' ')
|
||||
{
|
||||
$element['lines'] []= '';
|
||||
|
||||
$line = preg_replace('/^[ ]{0,4}/', '', $line);
|
||||
|
||||
$element['lines'] []= $line;
|
||||
|
||||
unset($element['interrupted']);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unset($element['last']);
|
||||
$line = preg_replace('/^[ ]{0,4}/', '', $line);
|
||||
|
||||
$element['lines'] []= $line;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
# indentation sensitive types
|
||||
|
||||
$deindented_line = $line;
|
||||
|
||||
switch ($line[0])
|
||||
{
|
||||
case ' ':
|
||||
|
||||
# ~
|
||||
|
||||
$deindented_line = ltrim($line);
|
||||
|
||||
if ($deindented_line === '')
|
||||
{
|
||||
continue 2;
|
||||
}
|
||||
|
||||
# code block
|
||||
|
||||
if (preg_match('/^[ ]{4}(.*)/', $line, $matches))
|
||||
{
|
||||
if ($element['type'] === 'code_block')
|
||||
{
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
$element['text'] .= "\n";
|
||||
|
||||
unset ($element['interrupted']);
|
||||
}
|
||||
|
||||
$element['text'] .= "\n".$matches[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'code_block',
|
||||
'text' => $matches[1],
|
||||
);
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '#':
|
||||
|
||||
# atx heading (#)
|
||||
|
||||
if (preg_match('/^(#{1,6})[ ]*(.+?)[ ]*#*$/', $line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$level = strlen($matches[1]);
|
||||
|
||||
$element = array(
|
||||
'type' => 'h.',
|
||||
'text' => $matches[2],
|
||||
'level' => $level,
|
||||
);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '-':
|
||||
|
||||
# setext heading (---)
|
||||
|
||||
if ($line[0] === '-' and $element['type'] === 'p' and ! isset($element['interrupted']) and preg_match('/^[-]+[ ]*$/', $line))
|
||||
{
|
||||
$element['type'] = 'h.';
|
||||
$element['level'] = 2;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '=':
|
||||
|
||||
# setext heading (===)
|
||||
|
||||
if ($line[0] === '=' and $element['type'] === 'p' and ! isset($element['interrupted']) and preg_match('/^[=]+[ ]*$/', $line))
|
||||
{
|
||||
$element['type'] = 'h.';
|
||||
$element['level'] = 1;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
# indentation insensitive types
|
||||
|
||||
switch ($deindented_line[0])
|
||||
{
|
||||
case '<':
|
||||
|
||||
# self-closing tag
|
||||
|
||||
if (preg_match('{^<.+?/>$}', $deindented_line))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => '',
|
||||
'text' => $deindented_line,
|
||||
);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
# opening tag
|
||||
|
||||
if (preg_match('{^<(\w+)(?:[ ].*?)?>}', $deindented_line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'markup',
|
||||
'subtype' => strtolower($matches[1]),
|
||||
'text' => $deindented_line,
|
||||
'depth' => 0,
|
||||
);
|
||||
|
||||
preg_match('{</'.$matches[1].'>\s*$}', $deindented_line) and $element['closed'] = true;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '>':
|
||||
|
||||
# quote
|
||||
|
||||
if (preg_match('/^>[ ]?(.*)/', $deindented_line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'blockquote',
|
||||
'lines' => array(
|
||||
$matches[1],
|
||||
),
|
||||
);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '[':
|
||||
|
||||
# reference
|
||||
|
||||
if (preg_match('/^\[(.+?)\]:[ ]*([^ ]+)/', $deindented_line, $matches))
|
||||
{
|
||||
$label = strtolower($matches[1]);
|
||||
|
||||
$this->reference_map[$label] = trim($matches[2], '<>');;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '`':
|
||||
case '~':
|
||||
|
||||
# fenced code block
|
||||
|
||||
if (preg_match('/^([`]{3,}|[~]{3,})[ ]*(\S+)?[ ]*$/', $deindented_line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'fenced_code_block',
|
||||
'text' => '',
|
||||
'fence' => $matches[1],
|
||||
);
|
||||
|
||||
isset($matches[2]) and $element['language'] = $matches[2];
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '*':
|
||||
case '+':
|
||||
case '-':
|
||||
case '_':
|
||||
|
||||
# hr
|
||||
|
||||
if (preg_match('/^([-*_])([ ]{0,2}\1){2,}[ ]*$/', $deindented_line))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'hr',
|
||||
);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
# li
|
||||
|
||||
if (preg_match('/^([ ]*)[*+-][ ](.*)/', $line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'li',
|
||||
'ordered' => false,
|
||||
'indentation' => $matches[1],
|
||||
'last' => true,
|
||||
'lines' => array(
|
||||
preg_replace('/^[ ]{0,4}/', '', $matches[3]),
|
||||
preg_replace('/^[ ]{0,4}/', '', $matches[2]),
|
||||
),
|
||||
);
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
if ($line[0] === ' ')
|
||||
{
|
||||
$element['lines'] []= '';
|
||||
|
||||
$line = preg_replace('/^[ ]{0,4}/', '', $line);;
|
||||
|
||||
$element['lines'] []= $line;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$line = preg_replace('/^[ ]{0,4}/', '', $line);;
|
||||
|
||||
$element['lines'] []= $line;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
# Quick Paragraph
|
||||
# li
|
||||
|
||||
if ($line[0] >= 'a' or $line[0] >= 'A' and $line[0] <= 'Z')
|
||||
{
|
||||
goto paragraph;
|
||||
}
|
||||
|
||||
# Code Block
|
||||
|
||||
if ($line[0] === ' ' and preg_match('/^[ ]{4}(.*)/', $line, $matches))
|
||||
{
|
||||
if (trim($line) === '')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($element['type'] === 'code')
|
||||
{
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
$element['text'] .= "\n";
|
||||
|
||||
unset ($element['interrupted']);
|
||||
}
|
||||
|
||||
$element['text'] .= "\n".$matches[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'code',
|
||||
'text' => $matches[1],
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# Setext Header (---)
|
||||
|
||||
if ($line[0] === '-' and $element['type'] === 'p' and ! isset($element['interrupted']) and preg_match('/^[-]+[ ]*$/', $line))
|
||||
{
|
||||
$element['type'] = 'h.';
|
||||
$element['level'] = 2;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# Atx Header (#)
|
||||
|
||||
if ($line[0] === '#' and preg_match('/^(#{1,6})[ ]*(.+?)[ ]*#*$/', $line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$level = strlen($matches[1]);
|
||||
|
||||
$element = array(
|
||||
'type' => 'h.',
|
||||
'text' => $matches[2],
|
||||
'level' => $level,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# Setext Header (===)
|
||||
|
||||
if ($line[0] === '=' and $element['type'] === 'p' and ! isset($element['interrupted']) and preg_match('/^[=]+[ ]*$/', $line))
|
||||
{
|
||||
$element['type'] = 'h.';
|
||||
$element['level'] = 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
$pure_line = $line[0] !== ' ' ? $line : ltrim($line);
|
||||
|
||||
if ($pure_line === '')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
# Link Reference
|
||||
|
||||
if ($pure_line[0] === '[' and preg_match('/^\[(.+?)\]:[ ]*([^ ]+)/', $pure_line, $matches))
|
||||
{
|
||||
$label = strtolower($matches[1]);
|
||||
$url = trim($matches[2], '<>');
|
||||
|
||||
$this->reference_map[$label] = $url;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# Blockquote
|
||||
|
||||
if ($pure_line[0] === '>' and preg_match('/^>[ ]?(.*)/', $pure_line, $matches))
|
||||
{
|
||||
if ($element['type'] === 'blockquote')
|
||||
{
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
$element['lines'] []= '';
|
||||
|
||||
unset($element['interrupted']);
|
||||
}
|
||||
|
||||
$element['lines'] []= $matches[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'blockquote',
|
||||
'lines' => array(
|
||||
$matches[1],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# HTML
|
||||
|
||||
if ($pure_line[0] === '<')
|
||||
{
|
||||
# Block-Level HTML <self-closing/>
|
||||
|
||||
if (preg_match('{^<.+?/>$}', $pure_line))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => '',
|
||||
'text' => $pure_line,
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# Block-Level HTML <open>
|
||||
|
||||
if (preg_match('{^<(\w+)(?:[ ].*?)?>}', $pure_line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'block',
|
||||
'subtype' => strtolower($matches[1]),
|
||||
'text' => $pure_line,
|
||||
'depth' => 0,
|
||||
);
|
||||
|
||||
preg_match('{</'.$matches[1].'>\s*$}', $pure_line) and $element['closed'] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
# Horizontal Rule
|
||||
|
||||
if (preg_match('/^([-*_])([ ]{0,2}\1){2,}[ ]*$/', $pure_line))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'hr',
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# List Item
|
||||
|
||||
if (preg_match('/^([ ]*)(\d+[.]|[*+-])[ ](.*)/', $line, $matches))
|
||||
if ($deindented_line[0] <= '9' and $deindented_line >= '0' and preg_match('/^([ ]*)\d+[.][ ](.*)/', $line, $matches))
|
||||
{
|
||||
$elements []= $element;
|
||||
|
||||
$element = array(
|
||||
'type' => 'li',
|
||||
'ordered' => isset($matches[2][1]),
|
||||
'ordered' => true,
|
||||
'indentation' => $matches[1],
|
||||
'last' => true,
|
||||
'lines' => array(
|
||||
preg_replace('/^[ ]{0,4}/', '', $matches[3]),
|
||||
preg_replace('/^[ ]{0,4}/', '', $matches[2]),
|
||||
),
|
||||
);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
paragraph:
|
||||
# paragraph
|
||||
|
||||
if ($element['type'] === 'p')
|
||||
{
|
||||
@ -432,7 +519,7 @@ class Parsedown
|
||||
|
||||
$elements []= $element;
|
||||
|
||||
array_shift($elements);
|
||||
unset($elements[0]);
|
||||
|
||||
#
|
||||
# ~
|
||||
@ -440,10 +527,71 @@ class Parsedown
|
||||
|
||||
$markup = '';
|
||||
|
||||
foreach ($elements as $index => $element)
|
||||
foreach ($elements as $element)
|
||||
{
|
||||
switch ($element['type'])
|
||||
{
|
||||
case 'p':
|
||||
|
||||
$text = $this->parse_span_elements($element['text']);
|
||||
|
||||
$text = preg_replace('/[ ]{2}\n/', '<br />'."\n", $text);
|
||||
|
||||
if ($context === 'li' and $markup === '')
|
||||
{
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
$markup .= "\n".'<p>'.$text.'</p>'."\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$markup .= $text;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$markup .= '<p>'.$text.'</p>'."\n";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'blockquote':
|
||||
|
||||
$text = $this->parse_block_elements($element['lines']);
|
||||
|
||||
$markup .= '<blockquote>'."\n".$text.'</blockquote>'."\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'code_block':
|
||||
case 'fenced_code_block':
|
||||
|
||||
$text = htmlspecialchars($element['text'], ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
strpos($text, "\x1A\\") !== FALSE and $text = strtr($text, $this->escape_sequence_map);
|
||||
|
||||
$markup .= isset($element['language'])
|
||||
? '<pre><code class="language-'.$element['language'].'">'.$text.'</code></pre>'
|
||||
: '<pre><code>'.$text.'</code></pre>';
|
||||
|
||||
$markup .= "\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'h.':
|
||||
|
||||
$text = $this->parse_span_elements($element['text']);
|
||||
|
||||
$markup .= '<h'.$element['level'].'>'.$text.'</h'.$element['level'].'>'."\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'hr':
|
||||
|
||||
$markup .= '<hr />'."\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'li':
|
||||
|
||||
if (isset($element['ordered'])) # first
|
||||
@ -466,62 +614,6 @@ class Parsedown
|
||||
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
|
||||
$text = $this->parse_inline_elements($element['text']);
|
||||
|
||||
$text = preg_replace('/[ ]{2}\n/', '<br />'."\n", $text);
|
||||
|
||||
if ($context === 'li' and $index === 0)
|
||||
{
|
||||
if (isset($element['interrupted']))
|
||||
{
|
||||
$markup .= "\n".'<p>'.$text.'</p>'."\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
$markup .= $text;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$markup .= '<p>'.$text.'</p>'."\n";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'code':
|
||||
|
||||
$text = htmlentities($element['text'], ENT_NOQUOTES);
|
||||
|
||||
strpos($text, "\x1A\\") !== FALSE and $text = strtr($text, $this->escape_sequence_map);
|
||||
|
||||
$markup .= '<pre><code>'.$text.'</code></pre>'."\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'blockquote':
|
||||
|
||||
$text = $this->parse_block_elements($element['lines']);
|
||||
|
||||
$markup .= '<blockquote>'."\n".$text.'</blockquote>'."\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'h.':
|
||||
|
||||
$text = $this->parse_inline_elements($element['text']);
|
||||
|
||||
$markup .= '<h'.$element['level'].'>'.$text.'</h'.$element['level'].'>'."\n";
|
||||
|
||||
break;
|
||||
|
||||
case 'hr':
|
||||
|
||||
$markup .= '<hr />'."\n";
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
$markup .= $element['text']."\n";
|
||||
@ -531,44 +623,13 @@ class Parsedown
|
||||
return $markup;
|
||||
}
|
||||
|
||||
private function parse_inline_elements($text)
|
||||
private function parse_span_elements($text)
|
||||
{
|
||||
$map = array();
|
||||
|
||||
$index = 0;
|
||||
|
||||
# Code Span
|
||||
|
||||
if (strpos($text, '`') !== FALSE and preg_match_all('/`(.+?)`/', $text, $matches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach ($matches as $matches)
|
||||
{
|
||||
$element_text = $matches[1];
|
||||
$element_text = htmlentities($element_text, ENT_NOQUOTES);
|
||||
|
||||
# Decodes escape sequences.
|
||||
|
||||
$this->escape_sequence_map
|
||||
and strpos($element_text, "\x1A") !== FALSE
|
||||
and $element_text = strtr($element_text, $this->escape_sequence_map);
|
||||
|
||||
# Composes element.
|
||||
|
||||
$element = '<code>'.$element_text.'</code>';
|
||||
|
||||
# Encodes element.
|
||||
|
||||
$code = "\x1A".'$'.$index;
|
||||
|
||||
$text = str_replace($matches[0], $code, $text);
|
||||
|
||||
$map[$code] = $element;
|
||||
|
||||
$index ++;
|
||||
}
|
||||
}
|
||||
|
||||
# Inline Link / Image
|
||||
# inline link or image (recursive)
|
||||
|
||||
if (strpos($text, '](') !== FALSE and preg_match_all('/(!?)(\[((?:[^\[\]]|(?2))*)\])\((.*?)\)/', $text, $matches, PREG_SET_ORDER)) # inline
|
||||
{
|
||||
@ -584,7 +645,7 @@ class Parsedown
|
||||
}
|
||||
else
|
||||
{
|
||||
$element_text = $this->parse_inline_elements($matches[3]);
|
||||
$element_text = $this->parse_span_elements($matches[3]);
|
||||
|
||||
$element = '<a href="'.$url.'">'.$element_text.'</a>';
|
||||
}
|
||||
@ -601,7 +662,7 @@ class Parsedown
|
||||
}
|
||||
}
|
||||
|
||||
# Reference(d) Link / Image
|
||||
# reference link or image (recursive)
|
||||
|
||||
if ($this->reference_map and strpos($text, '[') !== FALSE and preg_match_all('/(!?)\[(.+?)\](?:\n?[ ]?\[(.*?)\])?/ms', $text, $matches, PREG_SET_ORDER))
|
||||
{
|
||||
@ -625,7 +686,7 @@ class Parsedown
|
||||
}
|
||||
else # anchor
|
||||
{
|
||||
$element_text = $this->parse_inline_elements($matches[2]);
|
||||
$element_text = $this->parse_span_elements($matches[2]);
|
||||
|
||||
$element = '<a href="'.$url.'">'.$element_text.'</a>';
|
||||
}
|
||||
@ -643,21 +704,26 @@ class Parsedown
|
||||
}
|
||||
}
|
||||
|
||||
# Automatic Links
|
||||
# code span
|
||||
|
||||
if (strpos($text, '<') !== FALSE and preg_match_all('/<((https?|ftp|dict):[^\^\s]+?)>/i', $text, $matches, PREG_SET_ORDER))
|
||||
if (strpos($text, '`') !== FALSE and preg_match_all('/`(.+?)`/', $text, $matches, PREG_SET_ORDER))
|
||||
{
|
||||
foreach ($matches as $matches)
|
||||
{
|
||||
$url = $matches[1];
|
||||
$element_text = $matches[1];
|
||||
$element_text = htmlspecialchars($element_text, ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
strpos($url, '&') !== FALSE and $url = preg_replace('/&(?!#?\w+;)/', '&', $url);
|
||||
# decodes escape sequences
|
||||
|
||||
$element = '<a href=":href">:text</a>';
|
||||
$element = str_replace(':text', $url, $element);
|
||||
$element = str_replace(':href', $url, $element);
|
||||
$this->escape_sequence_map
|
||||
and strpos($element_text, "\x1A") !== FALSE
|
||||
and $element_text = strtr($element_text, $this->escape_sequence_map);
|
||||
|
||||
# ~
|
||||
# composes element
|
||||
|
||||
$element = '<code>'.$element_text.'</code>';
|
||||
|
||||
# encodes element
|
||||
|
||||
$code = "\x1A".'$'.$index;
|
||||
|
||||
@ -669,6 +735,40 @@ class Parsedown
|
||||
}
|
||||
}
|
||||
|
||||
# automatic link
|
||||
|
||||
if (strpos($text, '://') !== FALSE)
|
||||
{
|
||||
switch (TRUE)
|
||||
{
|
||||
case preg_match_all('{<(https?:[/]{2}[^\s]+)>}i', $text, $matches, PREG_SET_ORDER):
|
||||
case preg_match_all('{\b(https?:[/]{2}[^\s]+)\b}i', $text, $matches, PREG_SET_ORDER):
|
||||
|
||||
foreach ($matches as $matches)
|
||||
{
|
||||
$url = $matches[1];
|
||||
|
||||
strpos($url, '&') !== FALSE and $url = preg_replace('/&(?!#?\w+;)/', '&', $url);
|
||||
|
||||
$element = '<a href=":href">:text</a>';
|
||||
$element = str_replace(':text', $url, $element);
|
||||
$element = str_replace(':href', $url, $element);
|
||||
|
||||
# ~
|
||||
|
||||
$code = "\x1A".'$'.$index;
|
||||
|
||||
$text = str_replace($matches[0], $code, $text);
|
||||
|
||||
$map[$code] = $element;
|
||||
|
||||
$index ++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
# ~
|
||||
|
||||
strpos($text, '&') !== FALSE and $text = preg_replace('/&(?!#?\w+;)/', '&', $text);
|
||||
@ -676,16 +776,26 @@ class Parsedown
|
||||
|
||||
# ~
|
||||
|
||||
if (strpos($text, '~~') !== FALSE)
|
||||
{
|
||||
$text = preg_replace('/~~(?=\S)(.+?)(?<=\S)~~/s', '<del>$1</del>', $text);
|
||||
}
|
||||
|
||||
if (strpos($text, '_') !== FALSE)
|
||||
{
|
||||
$text = preg_replace('/__(?=\S)(.+?)(?<=\S)__(?!_)/s', '<strong>$1</strong>', $text);
|
||||
$text = preg_replace('/_(?=\S)(.+?)(?<=\S)_/s', '<em>$1</em>', $text);
|
||||
$text = preg_replace('/__(?=\S)([^_]+?)(?<=\S)__/s', '<strong>$1</strong>', $text, -1, $count);
|
||||
$count or $text = preg_replace('/__(?=\S)(.+?)(?<=\S)__(?!_)/s', '<strong>$1</strong>', $text);
|
||||
|
||||
$text = preg_replace('/\b_(?=\S)(.+?)(?<=\S)_\b/s', '<em>$1</em>', $text);
|
||||
}
|
||||
|
||||
if (strpos($text, '*') !== FALSE)
|
||||
{
|
||||
$text = preg_replace('/\*\*(?=\S)(.+?)(?<=\S)\*\*(?!\*)/s', '<strong>$1</strong>', $text);
|
||||
$text = preg_replace('/\*(?=\S)(.+?)(?<=\S)\*/s', '<em>$1</em>', $text);
|
||||
$text = preg_replace('/\*\*(?=\S)([^*]+?)(?<=\S)\*\*/s', '<strong>$1</strong>', $text, -1, $count);
|
||||
$count or $text = preg_replace('/\*\*(?=\S)(.+?)(?<=\S)\*\*(?!\*)/s', '<strong>$1</strong>', $text);
|
||||
|
||||
$text = preg_replace('/\*(?=\S)([^*]+?)(?<=\S)\*/s', '<em>$1</em>', $text, -1, $count);
|
||||
$count or $text = preg_replace('/\*(?=\S)(.+?)(?<=\S)\*(?!\*)/s', '<em>$1</em>', $text);
|
||||
}
|
||||
|
||||
$text = strtr($text, $map);
|
||||
|
18
README.md
18
README.md
@ -1,8 +1,20 @@
|
||||
## Parsedown PHP
|
||||
## Parsedown
|
||||
|
||||
Parsedown PHP is a parser for Markdown. It reads Markdown the way people do. First, it breaks texts into lines. Then, it looks at how these lines start and relate to each other. Finally, it looks for special characters to identify inline elements. As a result, Parsedown PHP is (very) fast and consistent.
|
||||
Better [Markdown](http://daringfireball.net/projects/markdown/) parser for PHP.
|
||||
|
||||
[Home](http://parsedown.org) · [Demo](http://parsedown.org/explorer/) · [Tests](http://parsedown.org/tests/)
|
||||
***
|
||||
|
||||
[demo](http://parsedown.org/demo) · [tests](http://parsedown.org/tests/)
|
||||
|
||||
***
|
||||
|
||||
### Features
|
||||
|
||||
* [fast](http://parsedown.org/speed)
|
||||
* [consistent](http://parsedown.org/consistency)
|
||||
* [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 and 5.5
|
||||
* friendly to international input
|
||||
|
||||
### Installation
|
||||
|
||||
|
@ -20,20 +20,29 @@ class Test extends PHPUnit_Framework_TestCase
|
||||
{
|
||||
$provider = array();
|
||||
|
||||
$DirectoryIterator = new DirectoryIterator(__DIR__ . '/' . self::provider_dir);
|
||||
$path = dirname(__FILE__).'/';
|
||||
|
||||
$DirectoryIterator = new DirectoryIterator($path . '/' . self::provider_dir);
|
||||
|
||||
foreach ($DirectoryIterator as $Item)
|
||||
{
|
||||
if ($Item->isFile() and $Item->getExtension() === 'md')
|
||||
if ($Item->isFile())
|
||||
{
|
||||
$filename = $Item->getFilename();
|
||||
|
||||
$extension = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
|
||||
if ($extension !== 'md')
|
||||
continue;
|
||||
|
||||
$basename = $Item->getBasename('.md');
|
||||
|
||||
$markdown = file_get_contents(__DIR__ . '/' . self::provider_dir . $basename . '.md');
|
||||
$markdown = file_get_contents($path . '/' . self::provider_dir . $basename . '.md');
|
||||
|
||||
if (!$markdown)
|
||||
continue;
|
||||
|
||||
$expected_markup = file_get_contents(__DIR__ . '/' . self::provider_dir . $basename . '.html');
|
||||
$expected_markup = file_get_contents($path . '/' . self::provider_dir . $basename . '.html');
|
||||
$expected_markup = str_replace("\r\n", "\n", $expected_markup);
|
||||
$expected_markup = str_replace("\r", "\n", $expected_markup);
|
||||
|
||||
@ -44,4 +53,3 @@ class Test extends PHPUnit_Framework_TestCase
|
||||
return $provider;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
<p><strong><em>em strong</em></strong></p>
|
||||
<p><strong><em>one</em> at the start</strong></p>
|
||||
<p><strong>one at the <em>end</em></strong></p>
|
||||
<p><strong>one <em>in the</em> middle</strong></p>
|
||||
<p><strong>one with <em>asterisks</em></strong></p>
|
||||
<p><strong><em>em strong</em> strong</strong></p>
|
||||
<p><strong>strong <em>em strong</em></strong></p>
|
||||
<p><strong>strong <em>em strong</em> strong</strong></p>
|
||||
<p><strong>strong <em>em strong</em></strong></p>
|
@ -1,9 +1,7 @@
|
||||
___em strong___
|
||||
___em strong_ strong__
|
||||
|
||||
___one_ at the start__
|
||||
__strong _em strong___
|
||||
|
||||
__one at the _end___
|
||||
__strong _em strong_ strong__
|
||||
|
||||
__one _in the_ middle__
|
||||
|
||||
**one with *asterisks***
|
||||
**strong *em strong***
|
@ -2,4 +2,5 @@
|
||||
<p><em>multiline
|
||||
emphasis</em></p>
|
||||
<p>_ this _ is not an emphasis, neither is _ this_, _this _, or _this*</p>
|
||||
<p>this_is_not_an_emphasis</p>
|
||||
<p>an empty emphasis __ ** is not an emphasis</p>
|
@ -5,4 +5,6 @@ emphasis_
|
||||
|
||||
_ this _ is not an emphasis, neither is _ this_, _this _, or _this*
|
||||
|
||||
this_is_not_an_emphasis
|
||||
|
||||
an empty emphasis __ ** is not an emphasis
|
5
tests/data/fenced_code_block.html
Normal file
5
tests/data/fenced_code_block.html
Normal file
@ -0,0 +1,5 @@
|
||||
<pre><code><?php
|
||||
|
||||
$message = 'fenced code block';
|
||||
echo $message;</code></pre>
|
||||
<pre><code>tilde</code></pre>
|
10
tests/data/fenced_code_block.md
Normal file
10
tests/data/fenced_code_block.md
Normal file
@ -0,0 +1,10 @@
|
||||
```
|
||||
<?php
|
||||
|
||||
$message = 'fenced code block';
|
||||
echo $message;
|
||||
```
|
||||
|
||||
~~~
|
||||
tilde
|
||||
~~~
|
@ -1,2 +1,3 @@
|
||||
<p><a href="http://example.com">link</a></p>
|
||||
<p><a href="http://example.com"><code>link</code></a></p>
|
||||
<p><a href="http://example.com"><img alt="MD Logo" src="http://parsedown.org/md.png"></a></p>
|
@ -1,3 +1,5 @@
|
||||
[link](http://example.com)
|
||||
|
||||
[`link`](http://example.com)
|
||||
|
||||
[](http://example.com)
|
7
tests/data/multiline_list_paragraph.html
Normal file
7
tests/data/multiline_list_paragraph.html
Normal file
@ -0,0 +1,7 @@
|
||||
<ul>
|
||||
<li>
|
||||
<p>li</p>
|
||||
<p>line
|
||||
line</p>
|
||||
</li>
|
||||
</ul>
|
4
tests/data/multiline_list_paragraph.md
Normal file
4
tests/data/multiline_list_paragraph.md
Normal file
@ -0,0 +1,4 @@
|
||||
- li
|
||||
|
||||
line
|
||||
line
|
3
tests/data/strikethrough.html
Normal file
3
tests/data/strikethrough.html
Normal file
@ -0,0 +1,3 @@
|
||||
<p><del>strikethrough</del></p>
|
||||
<p>in the <del>middle</del> of a sentence</p>
|
||||
<p>in the middle of a w<del>or</del>d</p>
|
5
tests/data/strikethrough.md
Normal file
5
tests/data/strikethrough.md
Normal file
@ -0,0 +1,5 @@
|
||||
~~strikethrough~~
|
||||
|
||||
in the ~~middle~~ of a sentence
|
||||
|
||||
in the middle of a w~~or~~d
|
6
tests/data/strong_em.html
Normal file
6
tests/data/strong_em.html
Normal file
@ -0,0 +1,6 @@
|
||||
<p><em><strong>strong em</strong></em> </p>
|
||||
<p><em>em <strong>strong em</strong></em></p>
|
||||
<p><em><strong>strong em</strong> em</em></p>
|
||||
<p><em><strong>strong em</strong></em></p>
|
||||
<p><em>em <strong>strong em</strong></em></p>
|
||||
<p><em><strong>strong em</strong> em</em></p>
|
11
tests/data/strong_em.md
Normal file
11
tests/data/strong_em.md
Normal file
@ -0,0 +1,11 @@
|
||||
***strong em***
|
||||
|
||||
*em **strong em***
|
||||
|
||||
***strong em** em*
|
||||
|
||||
___strong em___
|
||||
|
||||
_em __strong em___
|
||||
|
||||
___strong em__ em_
|
@ -4,3 +4,4 @@
|
||||
<p><a href="http://example.com">multiline
|
||||
one</a> defined on 2 lines</p>
|
||||
<p><a href="http://example.com">one</a> with an upper case label</p>
|
||||
<p><a href="http://example.com"><code>link</code></a></p>
|
@ -14,3 +14,5 @@ one][website] defined on 2 lines
|
||||
[one][label] with an upper case label
|
||||
|
||||
[LABEL]: http://example.com
|
||||
|
||||
[`link`][website]
|
1
tests/data/url_autolinking.html
Normal file
1
tests/data/url_autolinking.html
Normal file
@ -0,0 +1 @@
|
||||
<p>Here's an autolink <a href="http://example.com">http://example.com</a>.</p>
|
1
tests/data/url_autolinking.md
Normal file
1
tests/data/url_autolinking.md
Normal file
@ -0,0 +1 @@
|
||||
Here's an autolink http://example.com.
|
Reference in New Issue
Block a user