mirror of
https://github.com/erusev/parsedown.git
synced 2023-08-10 21:13:06 +03:00
separate compiling from parsing
This commit is contained in:
parent
d306ee3db5
commit
f8119fa3cb
450
Parsedown.php
450
Parsedown.php
@ -89,10 +89,12 @@ class Parsedown
|
|||||||
|
|
||||||
# ~
|
# ~
|
||||||
|
|
||||||
$text = trim($text, "\n");
|
|
||||||
$text = preg_replace('/\n\s*\n/', "\n\n", $text);
|
$text = preg_replace('/\n\s*\n/', "\n\n", $text);
|
||||||
|
$text = trim($text, "\n");
|
||||||
|
|
||||||
$text = $this->parse_lines($text);
|
$lines = explode("\n", $text);
|
||||||
|
|
||||||
|
$text = $this->parse_block_elements($lines);
|
||||||
|
|
||||||
# Decodes escape sequences (leaves out backslashes).
|
# Decodes escape sequences (leaves out backslashes).
|
||||||
|
|
||||||
@ -110,270 +112,340 @@ class Parsedown
|
|||||||
# Private Methods
|
# Private Methods
|
||||||
#
|
#
|
||||||
|
|
||||||
private function parse_lines($text, $context = null)
|
private function parse_block_elements(array $lines, $context = '')
|
||||||
{
|
{
|
||||||
$lines = explode("\n", $text);
|
$elements = array();
|
||||||
$lines []= null;
|
|
||||||
|
|
||||||
$line_count = count($lines);
|
$element = array(
|
||||||
|
'type' => '',
|
||||||
|
);
|
||||||
|
|
||||||
$markup = '';
|
foreach ($lines as $line)
|
||||||
|
|
||||||
foreach ($lines as $index => $line)
|
|
||||||
{
|
{
|
||||||
# ~
|
# Empty
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and $line[0] >= 'A')
|
if ($line === '')
|
||||||
{
|
{
|
||||||
$simple_line = $line;
|
$element['interrupted'] = true;
|
||||||
|
|
||||||
unset($line);
|
$element['type'] === 'code' and $element['text'] .= "\n";
|
||||||
}
|
|
||||||
|
|
||||||
# Setext Heading (-)
|
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and isset($paragraph) and preg_match('/^[-]+[ ]*$/', $line))
|
|
||||||
{
|
|
||||||
$setext_heading_text = $this->parse_inline_elements($paragraph);
|
|
||||||
|
|
||||||
$markup .= '<h2>'.$setext_heading_text.'</h2>'."\n";
|
|
||||||
|
|
||||||
unset($paragraph, $line);
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Rule
|
# Lazy Blockquote
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and preg_match('/^[ ]{0,3}([-*_])([ ]{0,2}\1){2,}[ ]*$/', $line))
|
if ($element['type'] === 'blockquote' and ! isset($element['interrupted']))
|
||||||
{
|
{
|
||||||
$rule = true;
|
$line = preg_replace('/^[ ]*>[ ]?/', '', $line);
|
||||||
|
|
||||||
unset($line);
|
$element['lines'] []= $line;
|
||||||
}
|
|
||||||
elseif (isset($rule))
|
|
||||||
{
|
|
||||||
$markup .= '<hr />'."\n";
|
|
||||||
|
|
||||||
unset($rule);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
# List
|
# Lazy List Item
|
||||||
|
|
||||||
# Unlike other types, consequent lines of type "list items" may not
|
if ($element['type'] === 'li')
|
||||||
# belong to the same block.
|
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches)) # list item
|
|
||||||
{
|
{
|
||||||
$list_item_indentation = $matches[1];
|
if (preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches))
|
||||||
$list_item_type = ($matches[2] === '-' or $matches[2] === '+' or $matches[2] === '*')
|
|
||||||
? 'ul'
|
|
||||||
: 'ol';
|
|
||||||
|
|
||||||
if (isset($list_items)) # subsequent
|
|
||||||
{
|
{
|
||||||
if ($list_item_indentation === $list_indentation and $list_item_type === $list_type)
|
if ($element['indentation'] !== $matches[1])
|
||||||
{
|
{
|
||||||
$list_items []= $list_item;
|
$element['lines'] []= $line;
|
||||||
|
|
||||||
$list_item = $matches[3];
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$list_item .= "\n".$line;
|
unset($element['last']);
|
||||||
|
|
||||||
|
$elements []= $element;
|
||||||
|
|
||||||
|
$element = array(
|
||||||
|
'type' => 'li',
|
||||||
|
'indentation' => $matches[1],
|
||||||
|
'last' => true,
|
||||||
|
'lines' => array(
|
||||||
|
preg_replace('/^[ ]{0,4}/', '', $matches[3]),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else # first
|
|
||||||
|
if (isset($element['interrupted']))
|
||||||
{
|
{
|
||||||
$list_indentation = $list_item_indentation;
|
if ($line[0] === ' ')
|
||||||
$list_type = $list_item_type;
|
|
||||||
|
|
||||||
$list_item = $matches[3];
|
|
||||||
|
|
||||||
$list_items = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
unset($line);
|
|
||||||
}
|
|
||||||
elseif (isset($list_items)) # incomplete list item
|
|
||||||
{
|
{
|
||||||
if (isset($line) and ($line === '' or $line[0] === ' '))
|
$element['lines'] []= '';
|
||||||
{
|
|
||||||
$line and $line = preg_replace('/^[ ]{0,4}/', '', $line);;
|
|
||||||
|
|
||||||
$list_item .= "\n".$line;
|
$line = preg_replace('/^[ ]{0,4}/', '', $line);;
|
||||||
|
|
||||||
unset($line);
|
$element['lines'] []= $line;
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else # line is consumed or does not belong to the list item
|
|
||||||
{
|
|
||||||
$list_item = rtrim($list_item, "\n");
|
|
||||||
|
|
||||||
$list_items []= $list_item;
|
|
||||||
|
|
||||||
$markup .= '<'.$list_type.'>'."\n";
|
|
||||||
|
|
||||||
foreach ($list_items as $list_item)
|
|
||||||
{
|
|
||||||
$list_item_text = strpos($list_item, "\n") !== false
|
|
||||||
? $this->parse_lines($list_item, 'li')
|
|
||||||
: $this->parse_inline_elements($list_item);
|
|
||||||
|
|
||||||
$markup .= '<li>'.$list_item_text.'</li>'."\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
$markup .= '</'.$list_type.'>'."\n";
|
|
||||||
|
|
||||||
unset($list_items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Code Block
|
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and preg_match('/^[ ]{4}(.*)/', $line, $matches))
|
|
||||||
{
|
|
||||||
if (isset($code_block))
|
|
||||||
{
|
|
||||||
$code_block .= "\n".$matches[1];
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$code_block = $matches[1];
|
$line = preg_replace('/^[ ]{0,4}/', '', $line);;
|
||||||
|
|
||||||
|
$element['lines'] []= $line;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($line);
|
# Quick Paragraph
|
||||||
}
|
|
||||||
elseif (isset($code_block))
|
|
||||||
{
|
|
||||||
if (isset($line) and $line === '')
|
|
||||||
{
|
|
||||||
$code_block .= "\n";
|
|
||||||
|
|
||||||
# » continue;
|
if ($line[0] >= 'A')
|
||||||
|
{
|
||||||
|
goto paragraph; # trust me
|
||||||
|
}
|
||||||
|
|
||||||
|
# Setext Header (---)
|
||||||
|
|
||||||
|
if ($element['type'] === 'p' and preg_match('/^[-]+[ ]*$/', $line))
|
||||||
|
{
|
||||||
|
$element['type'] = 'h.';
|
||||||
|
$element['level'] = 2;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Horizontal Rule
|
||||||
|
|
||||||
|
if (preg_match('/^[ ]{0,3}([-*_])([ ]{0,2}\1){2,}[ ]*$/', $line))
|
||||||
|
{
|
||||||
|
$elements []= $element;
|
||||||
|
|
||||||
|
$element = array(
|
||||||
|
'type' => 'hr',
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# List Item
|
||||||
|
|
||||||
|
if (preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ](.*)/', $line, $matches))
|
||||||
|
{
|
||||||
|
$elements []= $element;
|
||||||
|
|
||||||
|
$element = array(
|
||||||
|
'type' => 'li',
|
||||||
|
'ordered' => isset($matches[2][1]),
|
||||||
|
'indentation' => $matches[1],
|
||||||
|
'last' => true,
|
||||||
|
'lines' => array(
|
||||||
|
preg_replace('/^[ ]{0,4}/', '', $matches[3]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Code
|
||||||
|
|
||||||
|
if (preg_match('/^[ ]{4}(.*)/', $line, $matches))
|
||||||
|
{
|
||||||
|
if ($element['type'] === 'code')
|
||||||
|
{
|
||||||
|
$element['text'] .= "\n".$matches[1];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$code_block = rtrim($code_block);
|
$elements []= $element;
|
||||||
|
|
||||||
$code_block_text = htmlentities($code_block, ENT_NOQUOTES);
|
$element = array(
|
||||||
|
'type' => 'code',
|
||||||
# Decodes encoded escape sequences if present.
|
'text' => $matches[1],
|
||||||
strpos($code_block_text, "\x1A\\") !== FALSE and $code_block_text = strtr($code_block_text, $this->escape_sequence_map);
|
);
|
||||||
|
|
||||||
$markup .= '<pre><code>'.$code_block_text.'</code></pre>'."\n";
|
|
||||||
|
|
||||||
unset($code_block);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Blockquote
|
# Blockquote
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and preg_match('/^[ ]*>[ ]?(.*)/', $line, $matches))
|
if (preg_match('/^[ ]*>[ ]?(.*)/', $line, $matches))
|
||||||
{
|
{
|
||||||
if (isset($blockquote))
|
if ($element['type'] === 'blockquote')
|
||||||
{
|
{
|
||||||
$blockquote .= "\n".$matches[1];
|
if (isset($element['interrupted']))
|
||||||
|
{
|
||||||
|
$element['lines'] []= '';
|
||||||
|
|
||||||
|
unset($element['interrupted']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$element['lines'] []= $matches[1];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$blockquote = $matches[1];
|
$elements []= $element;
|
||||||
|
|
||||||
|
$element = array(
|
||||||
|
'type' => 'blockquote',
|
||||||
|
'lines' => array(
|
||||||
|
$matches[1],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($line);
|
|
||||||
}
|
|
||||||
elseif (isset($blockquote))
|
|
||||||
{
|
|
||||||
if (isset($line) and $line === '')
|
|
||||||
{
|
|
||||||
$blockquote .= "\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$blockquote = $this->parse_lines($blockquote);
|
|
||||||
|
|
||||||
$markup .= '<blockquote>'."\n".$blockquote.'</blockquote>'."\n";
|
|
||||||
|
|
||||||
unset($blockquote);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Atx Heading
|
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and $line[0] === '#' and preg_match('/^(#{1,6})[ ]*(.+?)[ ]*#*$/', $line, $matches))
|
|
||||||
{
|
|
||||||
$atx_heading_level = strlen($matches[1]);
|
|
||||||
|
|
||||||
$atx_heading = $this->parse_inline_elements($matches[2]);
|
|
||||||
|
|
||||||
unset($line);
|
|
||||||
}
|
|
||||||
elseif (isset($atx_heading))
|
|
||||||
{
|
|
||||||
$markup .= '<h'.$atx_heading_level.'>'.$atx_heading.'</h'.$atx_heading_level.'>'."\n";
|
|
||||||
|
|
||||||
unset($atx_heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Setext Heading (=)
|
|
||||||
|
|
||||||
if (isset($line) and $line !== '' and isset($paragraph) and preg_match('/^[=]+[ ]*$/', $line))
|
|
||||||
{
|
|
||||||
$setext_heading_text = $this->parse_inline_elements($paragraph);
|
|
||||||
|
|
||||||
$markup .= '<h1>'.$setext_heading_text.'</h1>'."\n";
|
|
||||||
|
|
||||||
unset($paragraph, $line);
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Paragraph
|
# Setext Header (===)
|
||||||
|
|
||||||
if (isset($simple_line))
|
if ($element['type'] === 'p' and preg_match('/^[=]+[ ]*$/', $line))
|
||||||
{
|
{
|
||||||
$line = $simple_line;
|
$element['type'] = 'h.';
|
||||||
|
$element['level'] = 1;
|
||||||
|
|
||||||
unset($simple_line);
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($line) and $line !== '')
|
# ~
|
||||||
{
|
|
||||||
substr($line, -2) === ' ' and $line = substr_replace($line, '<br />', -2);
|
|
||||||
|
|
||||||
if (isset($paragraph))
|
paragraph:
|
||||||
|
|
||||||
|
if ($element['type'] === 'p')
|
||||||
{
|
{
|
||||||
$paragraph .= "\n".$line;
|
if (isset($element['interrupted']))
|
||||||
|
{
|
||||||
|
$elements []= $element;
|
||||||
|
|
||||||
|
$element['text'] = $line;
|
||||||
|
|
||||||
|
unset($element['interrupted']);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$paragraph = $line;
|
$element['text'] .= "\n".$line;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif (isset($paragraph))
|
|
||||||
{
|
|
||||||
$paragraph_text = $this->parse_inline_elements($paragraph);
|
|
||||||
|
|
||||||
if ($context === 'li')
|
|
||||||
{
|
|
||||||
if ( ! $markup and $index + 1 === $line_count)
|
|
||||||
{
|
|
||||||
$text_is_simple = true;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$markup or $markup .= "\n";
|
$elements []= $element;
|
||||||
|
|
||||||
|
$element = array(
|
||||||
|
'type' => 'p',
|
||||||
|
'text' => $line,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$markup .= isset($text_is_simple)
|
$elements []= $element;
|
||||||
? $paragraph_text
|
|
||||||
: '<p>'.$paragraph_text.'</p>'."\n";
|
array_shift($elements);
|
||||||
|
|
||||||
|
#
|
||||||
|
# ~
|
||||||
|
#
|
||||||
|
|
||||||
|
$markup = '';
|
||||||
|
|
||||||
|
foreach ($elements as $index => $element)
|
||||||
|
{
|
||||||
|
switch ($element['type'])
|
||||||
|
{
|
||||||
|
case 'li':
|
||||||
|
|
||||||
|
if (isset($element['ordered'])) # first
|
||||||
|
{
|
||||||
|
$list_type = $element['ordered'] ? 'ol' : 'ul';
|
||||||
|
|
||||||
|
$markup .= '<'.$list_type.'>'."\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($element['interrupted']) and ! isset($element['last']))
|
||||||
|
{
|
||||||
|
$element['lines'] []= '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = $this->parse_block_elements($element['lines'], 'li');
|
||||||
|
|
||||||
|
$markup .= '<li>'.$text.'</li>'."\n";
|
||||||
|
|
||||||
|
isset($element['last']) and $markup .= '</'.$list_type.'>'."\n";
|
||||||
|
|
||||||
|
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
|
else
|
||||||
{
|
{
|
||||||
$markup .= '<p>'.$paragraph_text.'</p>'."\n";
|
$markup .= $text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$markup .= '<p>'.$text.'</p>'."\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
unset($paragraph);
|
break;
|
||||||
|
|
||||||
|
case 'code':
|
||||||
|
|
||||||
|
$text = rtrim($element['text'], "\n");
|
||||||
|
|
||||||
|
$text = htmlentities($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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user