X-Git-Url: http://www.aleph1.co.uk/gitweb/?p=yaffs-website;a=blobdiff_plain;f=vendor%2Fcebe%2Fmarkdown%2FParser.php;fp=vendor%2Fcebe%2Fmarkdown%2FParser.php;h=293f8c67b5a59290348367b66196a3455217552b;hp=0000000000000000000000000000000000000000;hb=a2bd1bf0c2c1f1a17d188f4dc0726a45494cefae;hpb=57c063afa3f66b07c4bbddc2d6129a96d90f0aad diff --git a/vendor/cebe/markdown/Parser.php b/vendor/cebe/markdown/Parser.php new file mode 100644 index 000000000..293f8c67b --- /dev/null +++ b/vendor/cebe/markdown/Parser.php @@ -0,0 +1,388 @@ + + */ +abstract class Parser +{ + /** + * @var integer the maximum nesting level for language elements. + */ + public $maximumNestingLevel = 32; + + /** + * @var string the current context the parser is in. + * TODO remove in favor of absy + */ + protected $context = []; + /** + * @var array these are "escapeable" characters. When using one of these prefixed with a + * backslash, the character will be outputted without the backslash and is not interpreted + * as markdown. + */ + protected $escapeCharacters = [ + '\\', // backslash + ]; + + private $_depth = 0; + + + /** + * Parses the given text considering the full language. + * + * This includes parsing block elements as well as inline elements. + * + * @param string $text the text to parse + * @return string parsed markup + */ + public function parse($text) + { + $this->prepare(); + + if (empty($text)) { + return ''; + } + + $text = str_replace(["\r\n", "\n\r", "\r"], "\n", $text); + + $this->prepareMarkers($text); + + $absy = $this->parseBlocks(explode("\n", $text)); + $markup = $this->renderAbsy($absy); + + $this->cleanup(); + return $markup; + } + + /** + * Parses a paragraph without block elements (block elements are ignored). + * + * @param string $text the text to parse + * @return string parsed markup + */ + public function parseParagraph($text) + { + $this->prepare(); + + if (empty($text)) { + return ''; + } + + $text = str_replace(["\r\n", "\n\r", "\r"], "\n", $text); + + $this->prepareMarkers($text); + + $absy = $this->parseInline($text); + $markup = $this->renderAbsy($absy); + + $this->cleanup(); + return $markup; + } + + /** + * This method will be called before `parse()` and `parseParagraph()`. + * You can override it to do some initialization work. + */ + protected function prepare() + { + } + + /** + * This method will be called after `parse()` and `parseParagraph()`. + * You can override it to do cleanup. + */ + protected function cleanup() + { + } + + + // block parsing + + private $_blockTypes; + + /** + * @return array a list of block element types available. + */ + protected function blockTypes() + { + if ($this->_blockTypes === null) { + // detect block types via "identify" functions + $reflection = new \ReflectionClass($this); + $this->_blockTypes = array_filter(array_map(function($method) { + $name = $method->getName(); + return strncmp($name, 'identify', 8) === 0 ? strtolower(substr($name, 8)) : false; + }, $reflection->getMethods(ReflectionMethod::IS_PROTECTED))); + + sort($this->_blockTypes); + } + return $this->_blockTypes; + } + + /** + * Given a set of lines and an index of a current line it uses the registed block types to + * detect the type of this line. + * @param array $lines + * @param integer $current + * @return string name of the block type in lower case + */ + protected function detectLineType($lines, $current) + { + $line = $lines[$current]; + $blockTypes = $this->blockTypes(); + foreach($blockTypes as $blockType) { + if ($this->{'identify' . $blockType}($line, $lines, $current)) { + return $blockType; + } + } + return 'paragraph'; + } + + /** + * Parse block elements by calling `identifyLine()` to identify them + * and call consume function afterwards. + * The blocks are then rendered by the corresponding rendering methods. + */ + protected function parseBlocks($lines) + { + if ($this->_depth >= $this->maximumNestingLevel) { + // maximum depth is reached, do not parse input + return [['text', implode("\n", $lines)]]; + } + $this->_depth++; + + $blocks = []; + + $blockTypes = $this->blockTypes(); + + // convert lines to blocks + for ($i = 0, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + if (!empty($line) && rtrim($line) !== '') { // skip empty lines + // identify a blocks beginning + $identified = false; + foreach($blockTypes as $blockType) { + if ($this->{'identify' . $blockType}($line, $lines, $i)) { + // call consume method for the detected block type to consume further lines + list($block, $i) = $this->{'consume' . $blockType}($lines, $i); + if ($block !== false) { + $blocks[] = $block; + } + $identified = true; + break 1; + } + } + // consider the line a normal paragraph + if (!$identified) { + list($block, $i) = $this->consumeParagraph($lines, $i); + $blocks[] = $block; + } + } + } + + $this->_depth--; + + return $blocks; + } + + protected function renderAbsy($blocks) + { + $output = ''; + foreach ($blocks as $block) { + array_unshift($this->context, $block[0]); + $output .= $this->{'render' . $block[0]}($block); + array_shift($this->context); + } + return $output; + } + + /** + * Consume lines for a paragraph + * + * @param $lines + * @param $current + * @return array + */ + protected function consumeParagraph($lines, $current) + { + // consume until newline + $content = []; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + if (ltrim($lines[$i]) !== '') { + $content[] = $lines[$i]; + } else { + break; + } + } + $block = [ + 'paragraph', + 'content' => $this->parseInline(implode("\n", $content)), + ]; + return [$block, --$i]; + } + + /** + * Render a paragraph block + * + * @param $block + * @return string + */ + protected function renderParagraph($block) + { + return '

' . $this->renderAbsy($block['content']) . "

\n"; + } + + + // inline parsing + + + /** + * @var array the set of inline markers to use in different contexts. + */ + private $_inlineMarkers = []; + + /** + * Returns a map of inline markers to the corresponding parser methods. + * + * This array defines handler methods for inline markdown markers. + * When a marker is found in the text, the handler method is called with the text + * starting at the position of the marker. + * + * Note that markers starting with whitespace may slow down the parser, + * you may want to use [[renderText]] to deal with them. + * + * You may override this method to define a set of markers and parsing methods. + * The default implementation looks for protected methods starting with `parse` that + * also have an `@marker` annotation in PHPDoc. + * + * @return array a map of markers to parser methods + */ + protected function inlineMarkers() + { + $markers = []; + // detect "parse" functions + $reflection = new \ReflectionClass($this); + foreach($reflection->getMethods(ReflectionMethod::IS_PROTECTED) as $method) { + $methodName = $method->getName(); + if (strncmp($methodName, 'parse', 5) === 0) { + preg_match_all('/@marker ([^\s]+)/', $method->getDocComment(), $matches); + foreach($matches[1] as $match) { + $markers[$match] = $methodName; + } + } + } + return $markers; + } + + /** + * Prepare markers that are used in the text to parse + * + * Add all markers that are present in markdown. + * Check is done to avoid iterations in parseInline(), good for huge markdown files + * @param string $text + */ + private function prepareMarkers($text) + { + $this->_inlineMarkers = []; + foreach ($this->inlineMarkers() as $marker => $method) { + if (strpos($text, $marker) !== false) { + $m = $marker[0]; + // put the longest marker first + if (isset($this->_inlineMarkers[$m])) { + reset($this->_inlineMarkers[$m]); + if (strlen($marker) > strlen(key($this->_inlineMarkers[$m]))) { + $this->_inlineMarkers[$m] = array_merge([$marker => $method], $this->_inlineMarkers[$m]); + continue; + } + } + $this->_inlineMarkers[$m][$marker] = $method; + } + } + } + + /** + * Parses inline elements of the language. + * + * @param string $text the inline text to parse. + * @return array + */ + protected function parseInline($text) + { + if ($this->_depth >= $this->maximumNestingLevel) { + // maximum depth is reached, do not parse input + return [['text', $text]]; + } + $this->_depth++; + + $markers = implode('', array_keys($this->_inlineMarkers)); + + $paragraph = []; + + while (!empty($markers) && ($found = strpbrk($text, $markers)) !== false) { + + $pos = strpos($text, $found); + + // add the text up to next marker to the paragraph + if ($pos !== 0) { + $paragraph[] = ['text', substr($text, 0, $pos)]; + } + $text = $found; + + $parsed = false; + foreach ($this->_inlineMarkers[$text[0]] as $marker => $method) { + if (strncmp($text, $marker, strlen($marker)) === 0) { + // parse the marker + array_unshift($this->context, $method); + list($output, $offset) = $this->$method($text); + array_shift($this->context); + + $paragraph[] = $output; + $text = substr($text, $offset); + $parsed = true; + break; + } + } + if (!$parsed) { + $paragraph[] = ['text', substr($text, 0, 1)]; + $text = substr($text, 1); + } + } + + $paragraph[] = ['text', $text]; + + $this->_depth--; + + return $paragraph; + } + + /** + * Parses escaped special characters. + * @marker \ + */ + protected function parseEscape($text) + { + if (isset($text[1]) && in_array($text[1], $this->escapeCharacters)) { + return [['text', $text[1]], 2]; + } + return [['text', $text[0]], 1]; + } + + /** + * This function renders plain text sections in the markdown text. + * It can be used to work on normal text sections for example to highlight keywords or + * do special escaping. + */ + protected function renderText($block) + { + return $block[1]; + } +}