', '\#', '\+', '\-', '\.', '\!'); foreach ($escape_sequences as $index => $escape_sequence) { if (strpos($text, $escape_sequence) !== FALSE) { $code = "\x1A".'\\'.$index; $text = str_replace($escape_sequence, $code, $text); $this->escape_sequence_map[$code] = $escape_sequence; } } } # Extracts link references. if (preg_match_all('/^[ ]{0,3}\[(.+)\][ ]?:[ ]*\n?[ ]*(.+)$/m', $text, $matches, PREG_SET_ORDER)) { foreach ($matches as $matches) { $this->reference_map[strtolower($matches[1])] = $matches[2]; $text = str_replace($matches[0], '', $text); } } # ~ $text = $this->parse_blocks($text); # Decodes escape sequences (leaves out backslashes). foreach ($this->escape_sequence_map as $code => $escape_sequence) { $text = str_replace($code, $escape_sequence[1], $text); } $text = rtrim($text, "\n"); return $text; } # # Private Methods # private function parse_blocks($text) { # Divides text into blocks. $blocks = preg_split('/\n\s*\n/', $text, -1, PREG_SPLIT_NO_EMPTY); # Makes sure compound blocks get rendered. $blocks []= NULL; $markup = ''; # Parses blocks. foreach ($blocks as $block) { if (isset($block) and $block[0] >= 'A') { $quick_block = $block; unset($block); } # List if (isset($block) and preg_match('/^([ ]{0,3})(\d+[.]|[*+-])[ ]/', $block, $matches)) # list item { if (isset($list)) # subsequent { $list .= "\n\n".$block; } else # first { $list = $block; $list_indentation = strlen($matches[1]); list($list_type, $list_marker_pattern) = ($matches[2] === '-' or $matches[2] === '+' or $matches[2] === '*') ? array('ul', '[*+-]') : array('ol', '\d+[.]'); } unset($block); } elseif (isset($block) and isset($list) and $block[0] === ' ') # list item block { $list .= "\n\n".$block; unset($block); } elseif (isset($list)) { $markup .= '<'.$list_type.'>'."\n"; $list_items = preg_split('/^([ ]{'.$list_indentation.'})'.$list_marker_pattern.'[ ]/m', $list, -1, PREG_SPLIT_NO_EMPTY); foreach ($list_items as $list_item) { $markup .= '
'.$code_block_text.'
'."\n";
unset($code_block);
}
# Atx Heading
if (isset($block) and $block[0] === '#' and preg_match('/^(#{1,6})[ ]*(.+?)[ ]*#*$/', $block, $matches))
{
$level = strlen($matches[1]);
$heading = $this->parse_inline_elements($matches[2]);
$markup .= ''."\n".$block.''."\n"; continue; } # Horizontal Line if (isset($block) and preg_match('/^[ ]{0,3}([-*_])([ ]{0,2}\1){2,}$/', $block)) { $markup .= '
'.$element_text.'
'."\n"; $markup .= $element; } } } return $markup; } private function parse_lines($text, $paragraph_based = FALSE) { $text = trim($text, "\n"); $lines = explode("\n", $text); $lines []= NULL; $markup = ''; foreach ($lines as $line) { if (isset($line) and $line === '') { unset($line); } # Paragraph if (isset($line) and $line[0] >= 'A') { $quick_line = $line; unset($line); } # List if (isset($line) and preg_match('/^([ ]*)(\d+[.]|[*+-])[ ](.*)/', $line, $matches)) # list item { $list_item_indentation = strlen($matches[1]); $list_item_type = ($matches[2] === '-' or $matches[2] === '+' or $matches[2] === '*') ? 'ul' : 'ol'; if (isset($list)) # subsequent { if ($list_item_indentation === $list_indentation and $list_item_type === $list_type) { # Adds last list item to the list. $list []= $list_item; # Creates a separate list item. $list_item = $matches[3]; } else { # Adds line to the current list item. $list_item .= "\n".$line; } } else # first { $list = array(); $list_indentation = $list_item_indentation; $list_type = $list_item_type; $list_item = $matches[3]; } unset($line); } else { if (isset($list)) { $list []= $list_item; $markup .= '<'.$list_type.'>'."\n"; foreach ($list as $list_item) { $list_item_text = strpos($list_item, "\n") ? $this->parse_lines($list_item) : $this->parse_inline_elements($list_item); $markup .= ''."\n".$quote.''."\n"; unset($quote); } } # Atx Heading if (isset($atx_heading)) { $markup .= '
'.$paragraph_text.'
'."\n"; unset($paragraph); } } } return $markup; } private function parse_inline_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 = ''.$element_text.'
';
# Encodes element.
$code = "\x1A".'$'.$index;
$text = str_replace($matches[0], $code, $text);
$map[$code] = $element;
$index ++;
}
}
# Reference(d) Link / Image
if ($this->reference_map and strpos($text, '[') !== FALSE and preg_match_all('/(!?)\[(.+?)\](?:[ ]?\[(.*?)\])?/ms', $text, $matches, PREG_SET_ORDER))
{
foreach ($matches as $matches)
{
$link_difinition = isset($matches[3]) && $matches[3]
? $matches[3]
: $matches[2]; # implicit
$link_difinition = strtolower($link_difinition);
if (isset($this->reference_map[$link_difinition]))
{
$url = $this->reference_map[$link_difinition];
if ($matches[1]) # image
{
$element = '';
}
else # anchor
{
$element_text = $this->parse_inline_elements($matches[2]);
$element = ''.$element_text.'';
}
# ~
$code = "\x1A".'$'.$index;
$text = str_replace($matches[0], $code, $text);
$map[$code] = $element;
$index ++;
}
}
}
# Inline Link / Image
if (strpos($text, '](') !== FALSE and preg_match_all('/(!?)(\[((?:[^][]+|(?2))*)\])\((.*?)\)/', $text, $matches, PREG_SET_ORDER)) # inline
{
foreach ($matches as $matches)
{
if ($matches[1]) # image
{
$element = '';
}
else
{
$element_text = $this->parse_inline_elements($matches[3]);
$element = ''.$element_text.'';
}
$element_text = $this->parse_inline_elements($matches[1]);
# ~
$code = "\x1A".'$'.$index;
$text = str_replace($matches[0], $code, $text);
$map[$code] = $element;
$index ++;
}
}
if (strpos($text, '<') !== FALSE and preg_match_all('/<((https?|ftp|dict):[^\^\s]+?)>/i', $text, $matches, PREG_SET_ORDER))
{
foreach ($matches as $matches)
{
$element = ':text';
$element = str_replace(':text', $matches[1], $element);
$element = str_replace(':href', $matches[1], $element);
# ~
$code = "\x1A".'$'.$index;
$text = str_replace($matches[0], $code, $text);
$map[$code] = $element;
$index ++;
}
}
if (strpos($text, '*') !== FALSE or strpos($text, '_') !== FALSE)
{
$text = preg_replace('/(\*\*|__)(.+?[*_]*)(?<=\S)\1/', '$2', $text);
$text = preg_replace('/(\*|_)(.+?)(?<=\S)\1/', '$2', $text);
}
$text = strtr($text, $map);
return $text;
}
}