private function findBlocks(array $lines, $blockContext = null)
{
$block = null;
$context = null;
$contextData = null;
foreach ($lines as $line) {
$indentedLine = $line;
$indentation = 0;
while (isset($line[$indentation]) and $line[$indentation] === ' ') {
$indentation++;
}
if ($indentation > 0) {
$line = ltrim($line);
}
# ~
switch ($context) {
case null:
$contextData = null;
if ($line === '') {
continue 2;
}
break;
# ~~~ javascript
# var message = 'Hello!';
# ~~~ javascript
# var message = 'Hello!';
case 'fenced code':
if ($line === '') {
$block['content'][0]['content'] .= "\n";
continue 2;
}
if (preg_match('/^[ ]*' . $contextData['marker'] . '{3,}[ ]*$/', $line)) {
$context = null;
} else {
if ($block['content'][0]['content']) {
$block['content'][0]['content'] .= "\n";
}
$string = htmlspecialchars($indentedLine, ENT_NOQUOTES, 'UTF-8');
$block['content'][0]['content'] .= $string;
}
continue 2;
case 'markup':
if (stripos($line, $contextData['start']) !== false) {
$contextData['depth']++;
}
if (stripos($line, $contextData['end']) !== false) {
if ($contextData['depth'] > 0) {
$contextData['depth']--;
} else {
$context = null;
}
}
$block['content'] .= "\n" . $indentedLine;
continue 2;
case 'li':
if ($line === '') {
$contextData['interrupted'] = true;
continue 2;
}
if ($contextData['indentation'] === $indentation and preg_match('/^' . $contextData['marker'] . '[ ]+(.*)/', $line, $matches)) {
if (isset($contextData['interrupted'])) {
$nestedBlock['content'][] = '';
unset($contextData['interrupted']);
}
unset($nestedBlock);
$nestedBlock = array('name' => 'li', 'content type' => 'lines', 'content' => array($matches[1]));
$block['content'][] =& $nestedBlock;
continue 2;
}
if (empty($contextData['interrupted'])) {
$value = $line;
if ($indentation > $contextData['baseline']) {
$value = str_repeat(' ', $indentation - $contextData['baseline']) . $value;
}
$nestedBlock['content'][] = $value;
continue 2;
}
if ($indentation > 0) {
$nestedBlock['content'][] = '';
$value = $line;
if ($indentation > $contextData['baseline']) {
$value = str_repeat(' ', $indentation - $contextData['baseline']) . $value;
}
$nestedBlock['content'][] = $value;
unset($contextData['interrupted']);
continue 2;
}
$context = null;
break;
case 'quote':
if ($line === '') {
$contextData['interrupted'] = true;
continue 2;
}
if (preg_match('/^>[ ]?(.*)/', $line, $matches)) {
$block['content'][] = $matches[1];
continue 2;
}
if (empty($contextData['interrupted'])) {
$block['content'][] = $line;
continue 2;
}
$context = null;
break;
case 'code':
if ($line === '') {
$contextData['interrupted'] = true;
continue 2;
}
if ($indentation >= 4) {
if (isset($contextData['interrupted'])) {
$block['content'][0]['content'] .= "\n";
unset($contextData['interrupted']);
}
$block['content'][0]['content'] .= "\n";
$string = htmlspecialchars($line, ENT_NOQUOTES, 'UTF-8');
$string = str_repeat(' ', $indentation - 4) . $string;
$block['content'][0]['content'] .= $string;
continue 2;
}
$context = null;
break;
case 'table':
if ($line === '') {
$context = null;
continue 2;
}
if (strpos($line, '|') !== false) {
$nestedBlocks = array();
$substring = preg_replace('/^[|][ ]*/', '', $line);
$substring = preg_replace('/[|]?[ ]*$/', '', $substring);
$parts = explode('|', $substring);
foreach ($parts as $index => $part) {
$substring = trim($part);
$nestedBlock = array('name' => 'td', 'content type' => 'line', 'content' => $substring);
if (isset($contextData['alignments'][$index])) {
$nestedBlock['attributes'] = array('align' => $contextData['alignments'][$index]);
}
$nestedBlocks[] = $nestedBlock;
}
$nestedBlock = array('name' => 'tr', 'content type' => 'blocks', 'content' => $nestedBlocks);
$block['content'][1]['content'][] = $nestedBlock;
continue 2;
}
$context = null;
break;
case 'paragraph':
if ($line === '') {
$block['name'] = 'p';
# dense li
$context = null;
continue 2;
}
if ($line[0] === '=' and chop($line, '=') === '') {
$block['name'] = 'h1';
$context = null;
continue 2;
}
if ($line[0] === '-' and chop($line, '-') === '') {
$block['name'] = 'h2';
$context = null;
continue 2;
}
if (strpos($line, '|') !== false and strpos($block['content'], '|') !== false and chop($line, ' -:|') === '') {
$values = array();
$substring = trim($line, ' |');
$parts = explode('|', $substring);
foreach ($parts as $part) {
$substring = trim($part);
$value = null;
if ($substring[0] === ':') {
$value = 'left';
}
if (substr($substring, -1) === ':') {
$value = $value === 'left' ? 'center' : 'right';
}
$values[] = $value;
}
# ~
$nestedBlocks = array();
$substring = preg_replace('/^[|][ ]*/', '', $block['content']);
$substring = preg_replace('/[|]?[ ]*$/', '', $substring);
$parts = explode('|', $substring);
foreach ($parts as $index => $part) {
$substring = trim($part);
$nestedBlock = array('name' => 'th', 'content type' => 'line', 'content' => $substring);
if (isset($values[$index])) {
$value = $values[$index];
$nestedBlock['attributes'] = array('align' => $value);
}
$nestedBlocks[] = $nestedBlock;
}
# ~
$block = array('name' => 'table', 'content type' => 'blocks', 'content' => array());
$block['content'][] = array('name' => 'thead', 'content type' => 'blocks', 'content' => array());
$block['content'][] = array('name' => 'tbody', 'content type' => 'blocks', 'content' => array());
$block['content'][0]['content'][] = array('name' => 'tr', 'content type' => 'blocks', 'content' => array());
$block['content'][0]['content'][0]['content'] = $nestedBlocks;
# ~
$context = 'table';
$contextData = array('alignments' => $values);
# ~
continue 2;
}
break;
default:
throw new Exception('Unrecognized context - ' . $context);
}
if ($indentation >= 4) {
$blocks[] = $block;
$string = htmlspecialchars($line, ENT_NOQUOTES, 'UTF-8');
$string = str_repeat(' ', $indentation - 4) . $string;
$block = array('name' => 'pre', 'content type' => 'blocks', 'content' => array(array('name' => 'code', 'content type' => null, 'content' => $string)));
$context = 'code';
continue;
}
switch ($line[0]) {
case '#':
if (isset($line[1])) {
$blocks[] = $block;
$level = 1;
while (isset($line[$level]) and $line[$level] === '#') {
$level++;
}
$string = trim($line, '# ');
$string = $this->parseLine($string);
$block = array('name' => 'h' . $level, 'content type' => 'line', 'content' => $string);
$context = null;
continue 2;
}
break;
case '<':
$position = strpos($line, '>');
if ($position > 1) {
$substring = substr($line, 1, $position - 1);
$substring = chop($substring);
if (substr($substring, -1) === '/') {
$isClosing = true;
$substring = substr($substring, 0, -1);
}
$position = strpos($substring, ' ');
if ($position) {
$name = substr($substring, 0, $position);
} else {
$name = $substring;
}
$name = strtolower($name);
if ($name[0] == 'h' and strpos('r123456', $name[1]) !== false) {
if ($name == 'hr') {
$isClosing = true;
}
} elseif (!ctype_alpha($name)) {
break;
}
if (in_array($name, self::$textLevelElements)) {
break;
}
$blocks[] = $block;
$block = array('name' => null, 'content type' => null, 'content' => $indentedLine);
if (isset($isClosing)) {
unset($isClosing);
continue 2;
}
$context = 'markup';
$contextData = array('start' => '<' . $name . '>', 'end' => '</' . $name . '>', 'depth' => 0);
if (stripos($line, $contextData['end']) !== false) {
$context = null;
}
continue 2;
}
break;
case '>':
if (preg_match('/^>[ ]?(.*)/', $line, $matches)) {
$blocks[] = $block;
$block = array('name' => 'blockquote', 'content type' => 'lines', 'content' => array($matches[1]));
$context = 'quote';
$contextData = array();
continue 2;
}
break;
case '[':
$position = strpos($line, ']:');
if ($position) {
$reference = array();
$label = substr($line, 1, $position - 1);
$label = strtolower($label);
$substring = substr($line, $position + 2);
$substring = trim($substring);
if ($substring === '') {
break;
}
if ($substring[0] === '<') {
$position = strpos($substring, '>');
if ($position === false) {
break;
}
$reference['link'] = substr($substring, 1, $position - 1);
$substring = substr($substring, $position + 1);
} else {
$position = strpos($substring, ' ');
if ($position === false) {
$reference['link'] = $substring;
$substring = false;
} else {
$reference['link'] = substr($substring, 0, $position);
$substring = substr($substring, $position + 1);
}
}
if ($substring !== false) {
if ($substring[0] !== '"' and $substring[0] !== "'" and $substring[0] !== '(') {
break;
}
$lastChar = substr($substring, -1);
if ($lastChar !== '"' and $lastChar !== "'" and $lastChar !== ')') {
break;
}
$reference['title'] = substr($substring, 1, -1);
}
$this->referenceMap[$label] = $reference;
continue 2;
}
break;
case '`':
case '~':
if (preg_match('/^([`]{3,}|[~]{3,})[ ]*(\\w+)?[ ]*$/', $line, $matches)) {
$blocks[] = $block;
$block = array('name' => 'pre', 'content type' => 'blocks', 'content' => array(array('name' => 'code', 'content type' => null, 'content' => '')));
if (isset($matches[2])) {
$block['content'][0]['attributes'] = array('class' => 'language-' . $matches[2]);
}
$context = 'fenced code';
$contextData = array('marker' => $matches[1][0]);
continue 2;
}
break;
case '-':
case '*':
case '_':
if (preg_match('/^([-*_])([ ]{0,2}\\1){2,}[ ]*$/', $line)) {
$blocks[] = $block;
$block = array('name' => 'hr', 'content' => null);
continue 2;
}
}
switch (true) {
case $line[0] <= '-' and preg_match('/^([*+-][ ]+)(.*)/', $line, $matches):
case $line[0] <= '9' and preg_match('/^([0-9]+[.][ ]+)(.*)/', $line, $matches):
$blocks[] = $block;
$name = $line[0] >= '0' ? 'ol' : 'ul';
$block = array('name' => $name, 'content type' => 'blocks', 'content' => array());
unset($nestedBlock);
$nestedBlock = array('name' => 'li', 'content type' => 'lines', 'content' => array($matches[2]));
$block['content'][] =& $nestedBlock;
$baseline = $indentation + strlen($matches[1]);
$marker = $line[0] >= '0' ? '[0-9]+[.]' : '[*+-]';
$context = 'li';
$contextData = array('indentation' => $indentation, 'baseline' => $baseline, 'marker' => $marker, 'lines' => array($matches[2]));
continue 2;
}
if ($context === 'paragraph') {
$block['content'] .= "\n" . $line;
continue;
} else {
$blocks[] = $block;
$block = array('name' => 'p', 'content type' => 'line', 'content' => $line);
if ($blockContext === 'li' and empty($blocks[1])) {
$block['name'] = null;
}
$context = 'paragraph';
}
}
if ($blockContext === 'li' and $block['name'] === null) {
return $block['content'];
}
$blocks[] = $block;
unset($blocks[0]);
return $blocks;
}