/** * @param Text $text * @param array $options */ public function processFencedCodeBlock(Text $text, array $options = []) { /** @noinspection PhpUnusedParameterInspection */ $text->replace('{ (?:\\n\\n|\\A) (?: ([`~]{3})[ ]* #1 fence ` or ~ ([a-zA-Z0-9]*?)? #2 language [optional] \\n+ (.*?)\\n #3 code block \\1 # matched #1 ) }smx', function (Text $w, Text $fence, Text $lang, Text $code) use($options) { $rendererOptions = []; if (!$lang->isEmpty()) { if ($options['pygments'] && class_exists('KzykHys\\Pygments\\Pygments')) { $pygments = new Pygments(); $html = $pygments->highlight($code, $lang, 'html'); return "\n\n" . $html . "\n\n"; } $rendererOptions = ['attr' => ['class' => 'prettyprint lang-' . $lang->lower()]]; } $code->escapeHtml(ENT_NOQUOTES); $this->markdown->emit('detab', array($code)); $code->replace('/\\A\\n+/', ''); $code->replace('/\\s+\\z/', ''); return "\n\n" . $this->getRenderer()->renderCodeBlock($code, $rendererOptions) . "\n\n"; }); }
/** * Convert line breaks * * @param Text $text */ public function initialize(Text $text) { $text->replaceString("\r\n", "\n"); $text->replaceString("\r", "\n"); $text->append("\n\n"); $this->markdown->emit('detab', array($text)); $text->replace('/^[ \\t]+$/m', ''); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $this->hashes = $this->generateSpecialCharsHash(); $markdown->on('escape.special_chars', array($this, 'escapeSpecialChars')); $markdown->on('inline', array($this, 'escapeSpecialChars'), 20); $markdown->on('inline', array($this, 'escapeAmpsAndBrackets'), 60); $markdown->on('finalize', array($this, 'unescapeSpecialChars'), 20); }
protected function guessLineOfCode() { $pattern = sprintf('/%s/m', preg_quote($this->text, '/')); $rawContent = $this->markdown->getRawContent(); $rawContent->replaceString("\r\n", "\n"); $rawContent->replaceString("\r", "\n"); $lines = $rawContent->split('/\\n/'); $lines->each(function (Text $line, $index) use($pattern) { if ($line->match($pattern)) { $this->markdownLineNo = $index + 1; return false; } return true; }); }
/** * handle inline images: ![alt text](url "optional title") * * @param Text $text * @param array $options */ public function processInlineImage(Text $text, array $options = array()) { if (!$text->contains('![')) { return; } /** @xnoinspection PhpUnusedParameterInspection */ $text->replace('{ ( # wrap whole match in $1 !\\[ (.*?) # alt text = $2 \\] \\( # literal paren [ \\t]* <?(\\S+?)>? # src url = $3 [ \\t]* ( # $4 ([\'"]) # quote char = $5 (.*?) # title = $6 \\5 # matching quote [ \\t]* )? # title is optional \\) ) }xs', function (Text $w, Text $whole, Text $alt, Text $url, Text $a = null, Text $q = null, Text $title = null) use($options) { $attr = array('alt' => $alt->replace('/"/', '"')); $this->markdown->emit('escape.special_chars', [$url->replace('/(?<!\\\\)_/', '\\\\_')]); $url->escapeHtml(); if ($title) { $attr['title'] = $title->replace('/"/', '"')->escapeHtml(); } return $this->getRenderer()->renderImage($url, array('attr' => $attr)); }); }
/** * @param Text $text */ public function processAtxHeader(Text $text) { /** @noinspection PhpUnusedParameterInspection */ $text->replace('{ ^(\\#{1,6}) # $1 = string of #\'s [ \\t]* (.+?) # $2 = Header text [ \\t]* \\#* # optional closing #\'s (not counted) \\n+ }mx', function (Text $whole, Text $marks, Text $content) { $level = strlen($marks); $this->markdown->emit('inline', array($content)); return $this->getRenderer()->renderHeader($content, array('level' => $level)) . "\n\n"; }); }
/** * @param Text $text */ public function buildParagraph(Text $text) { $parts = $text->replace('/\\A\\n+/', '')->replace('/\\n+\\z/', '')->split('/\\n{2,}/', PREG_SPLIT_NO_EMPTY); $parts->apply(function (Text $part) { if (!$this->markdown->getHashRegistry()->exists($part)) { $this->markdown->emit('inline', array($part)); $part->replace('/^([ \\t]*)/', ''); $part->setString($this->getRenderer()->renderParagraph((string) $part)); } return $part; }); $parts->apply(function (Text $part) { if ($this->markdown->getHashRegistry()->exists($part)) { $part->setString(trim($this->markdown->getHashRegistry()->get($part))); } return $part; }); $text->setString($parts->join("\n\n")); }
/** * @param Text $text * @param array $options */ public function processCodeBlock(Text $text, array $options = array()) { /** @noinspection PhpUnusedParameterInspection */ $text->replace('{ (?:\\n\\n|\\A) ( # $1 = the code block -- one or more lines, starting with a space/tab (?: (?:[ ]{' . $options['tabWidth'] . '} | \\t) # Lines must start with a tab or a tab-width of spaces .*\\n+ )+ ) (?:(?=^[ ]{0,' . $options['tabWidth'] . '}\\S)|\\Z) # Lookahead for non-space at line-start, or end of doc }mx', function (Text $whole, Text $code) { $this->markdown->emit('outdent', array($code)); $code->escapeHtml(ENT_NOQUOTES); $this->markdown->emit('detab', array($code)); $code->replace('/\\A\\n+/', ''); $code->replace('/\\s+\\z/', ''); return "\n\n" . $this->getRenderer()->renderCodeBlock($code) . "\n\n"; }); }
/** * @param Text $text */ public function processBlockQuote(Text $text) { $text->replace('{ (?: (?: ^[ \\t]*>[ \\t]? # > at the start of a line .+\\n # rest of the first line (?:.+\\n)* # subsequent consecutive lines \\n* # blanks )+ ) }mx', function (Text $bq) { $bq->replace('/^[ \\t]*>[ \\t]?/m', ''); $bq->replace('/^[ \\t]+$/m', ''); $this->markdown->emit('block', [$bq]); $bq->replace('|\\s*<pre>.+?</pre>|s', function (Text $pre) { return $pre->replace('/^ /m', ''); }); return $this->getRenderer()->renderBlockQuote($bq) . "\n\n"; }); }
/** * @param Text $text */ public function processFencedCodeBlock(Text $text) { /** @noinspection PhpUnusedParameterInspection */ $text->replace('{ (?:\\n\\n|\\A) (?: ([`~]{3})[ ]* #1 fence ` or ~ ([a-zA-Z0-9]*?)? #2 language [optional] \\n+ (.*?)\\n #3 code block \\1 # matched #1 ) }smx', function (Text $w, Text $fence, Text $lang, Text $code) { $options = array(); if (!$lang->isEmpty()) { $options = array('attr' => array('class' => 'prettyprint lang-' . $lang->lower())); } $code->escapeHtml(ENT_NOQUOTES); $this->markdown->emit('detab', array($code)); $code->replace('/\\A\\n+/', ''); $code->replace('/\\s+\\z/', ''); return "\n\n" . $this->getRenderer()->renderCodeBlock($code, $options) . "\n\n"; }); }
/** * Make links out of things like `<http://example.com/>` * * @param Text $text */ public function processAutoLink(Text $text) { if (!$text->contains('<')) { return; } $text->replace('{<((?:https?|ftp):[^\'">\\s]+)>}', function (Text $w, Text $url) { $this->markdown->emit('escape.special_chars', [$url->replace('/(?<!\\\\)_/', '\\\\_')]); return $this->getRenderer()->renderLink($url, ['href' => $url->getString()]); }); /** @noinspection PhpUnusedParameterInspection */ $text->replace('{ < (?:mailto:)? ( [-.\\w]+ \\@ [-a-z0-9]+(\\.[-a-z0-9]+)*\\.[a-z]+ ) > }ix', function (Text $w, Text $address) { $address = "mailto:" . $address; $encode = array(function ($char) { return '&#' . ord($char) . ';'; }, function ($char) { return '&#x' . dechex(ord($char)) . ';'; }, function ($char) { return $char; }); $chars = new Collection(str_split($address)); $chars->apply(function ($char) use($encode) { if ($char == '@') { return $encode[rand(0, 1)]($char); } elseif ($char != ':') { $rand = rand(0, 100); $key = $rand > 90 ? 2 : ($rand < 45 ? 0 : 1); return $encode[$key]($char); } return $char; }); $address = $chars->join(); $text = $chars->slice(7)->join(); return $this->getRenderer()->renderLink($text, ['href' => $address]); }); }
/** * Process the contents of a single ordered or unordered list, splitting it into individual list items. * * @param Text $list * @param array $options * @param int $level */ public function processListItems(Text $list, array $options = array(), $level = 0) { $list->replace('/\\n{2,}\\z/', "\n"); /** @noinspection PhpUnusedParameterInspection */ $list->replace('{ (\\n)? # leading line = $1 (^[ \\t]*) # leading whitespace = $2 (' . $this->getPattern() . ') [ \\t]+ # list marker = $3 ((?s:.+?) # list item text = $4 (\\n{1,2})) (?= \\n* (\\z | \\2 (' . $this->getPattern() . ') [ \\t]+)) }mx', function (Text $w, Text $leadingLine, Text $ls, Text $m, Text $item) use($options, $level) { if ((string) $leadingLine || $item->match('/\\n{2,}/')) { $this->markdown->emit('outdent', array($item)); $this->markdown->emit('block', array($item)); } else { $this->markdown->emit('outdent', array($item)); $this->processList($item, $options, ++$level); $item->rtrim(); $this->markdown->emit('inline', array($item)); } return $this->getRenderer()->renderListItem($item) . "\n"; }); }
/** * @param Text $body * @param Collection $baseTags * * @return Collection */ protected function parseBody(Text $body, Collection $baseTags) { $rows = new Collection(); $body->split('/\\n/')->each(function (Text $row, $index) use($baseTags, &$rows) { $row->trim()->trim('|'); $cells = new Collection(); try { $row->split('/\\|/')->each(function (Text $cell, $index) use(&$baseTags, &$cells) { /* @var Tag $tag */ $tag = clone $baseTags->get($index); $this->markdown->emit('inline', array($cell)); $tag->setText($cell->trim()); $cells->add($tag); }); } catch (\OutOfBoundsException $e) { throw new SyntaxError(sprintf('Too much cells on table body (row #%d).', $index), $this, $row, $this->markdown, $e); } if ($baseTags->count() != $cells->count()) { throw new SyntaxError('Unexpected number of table cells in body.', $this, $row, $this->markdown); } $rows->add($cells); }); return $rows; }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('inline', [$this, 'processStandardUrl'], 35); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('inline', array($this, 'processBold'), 70); $markdown->on('inline', array($this, 'processItalic'), 71); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('block', array($this, 'processHorizontalRule'), 20); }
public function testDefaultOptions() { $markdown = new Markdown(new HtmlRenderer()); $this->assertEquals(['tabWidth' => 4, 'nestedTagLevel' => 3, 'strict' => false, 'pygments' => false], $markdown->getOptions()); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('block', array($this, 'processComment'), 50); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('inline', array($this, 'processMultipleUnderScore'), 10); $markdown->on('inline', array($this, 'processStrikeThrough'), 70); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('tag', array($this, 'processBlockTags')); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('inline', [$this, 'processNewLines']); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $this->markdown = $markdown; $markdown->on('inline', array($this, 'processPullRequest')); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('inline', array($this, 'processHardBreak'), 75); }
/** * @param Text $text */ public function unhashHtmlBlocks(Text $text) { foreach ($this->markdown->getHashRegistry() as $hash => $html) { $text->replaceString($hash, trim($html)); } }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('block', array($this, 'processDefinitionList'), 30); $markdown->on('block', array($this, 'processWikiDefinitionList'), 30); }
/** * {@inheritdoc} */ public function register(Markdown $markdown) { $markdown->on('block', array($this, 'processHeader'), 10); }