/**
  * @param Text $text
  *
  * @return array
  */
 protected function parseAttributes(Text $text)
 {
     $patterns = ['id' => '/^#([a-zA-Z0-9_-]+)/', 'class' => '/^\\.([a-zA-Z0-9_-]+)/', 'attr' => '/^\\[([^\\]]+)\\]/', 'ident' => '/^(.)/'];
     $tokens = ['id' => [], 'class' => [], 'attr' => [], 'ident' => []];
     while (!$text->isEmpty()) {
         foreach ($patterns as $name => $pattern) {
             if ($text->match($pattern, $matches)) {
                 $tokens[$name][] = $matches[1];
                 $text->setString(substr($text->getString(), strlen($matches[0])));
                 break;
             }
         }
     }
     $attributes = array();
     if (count($tokens['id'])) {
         $attributes['id'] = array_pop($tokens['id']);
     }
     if (count($tokens['class'])) {
         $attributes['class'] = implode(' ', $tokens['class']);
     }
     if (count($tokens['attr'])) {
         foreach ($tokens['attr'] as $raw) {
             $items = explode(' ', $raw);
             foreach ($items as $item) {
                 if (strpos($item, '=') !== false) {
                     list($key, $value) = explode('=', $item);
                     $attributes[$key] = trim($value, '"');
                 } else {
                     $attributes[$item] = $item;
                 }
             }
         }
     }
     return $attributes;
 }
Beispiel #2
0
 /**
  * {@inheritdoc}
  */
 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
         (([ ]*(\\[([ ]|x)\\]) [ \\t]+)?(?s:.+?)  # list item text = $4, checkbox = $5, checked = %6
         (\\n{1,2}))
         (?= \\n* (\\z | \\2 (' . $this->getPattern() . ') [ \\t]+))
     }mx', function (Text $w, Text $leadingLine, Text $ls, Text $m, Text $item, Text $checkbox, Text $check) use($options, $level) {
         if (!$checkbox->isEmpty()) {
             $item->replace('/^\\[( |x)\\]/', function (Text $w, Text $check) {
                 $attr = array('type' => 'checkbox');
                 if ($check == 'x') {
                     $attr['checked'] = 'checked';
                 }
                 return $this->getRenderer()->renderTag('input', new Text(), Tag::TYPE_INLINE, array('attr' => $attr));
             });
         }
         if ((string) $leadingLine || $item->match('/\\n{2,}/')) {
             $this->getEmitter()->emit('outdent', array($item));
             $this->getEmitter()->emit('block', array($item));
         } else {
             $this->getEmitter()->emit('outdent', array($item));
             $this->processList($item, $options, ++$level);
             $item->rtrim();
             $this->getEmitter()->emit('inline', array($item));
         }
         return $this->getRenderer()->renderListItem($item) . "\n";
     });
 }
 /**
  * @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";
     });
 }
 /**
  * @param Text  $text
  */
 public function processHorizontalRule(Text $text)
 {
     $marks = array('\\*', '-', '_');
     foreach ($marks as $mark) {
         $text->replace('/^[ ]{0,2}([ ]?' . $mark . '[ ]?){3,}[ \\t]*$/m', $this->getRenderer()->renderHorizontalRule() . "\n\n");
     }
 }
Beispiel #5
0
 /**
  * @param Text $text
  */
 public function processMentions(Text $text)
 {
     // Turn @username into [@username](http://example.com/user/username)
     $text->replace('/(?:^|[^a-zA-Z0-9.])@([A-Za-z0-9]+)/', function (Text $w, Text $username) {
         return ' [@' . $username . '](' . $this->config->site->url . '/user/0/' . $username . ')';
     });
 }
Beispiel #6
0
 /**
  * @param Text $text
  */
 public function processMentions(Text $text)
 {
     // Turn @username into [@username](http://example.com/user/username)
     $text->replace('/(?:^|[^a-zA-Z0-9.])@([A-Za-z]+[A-Za-z0-9]+)/', function (Text $w, Text $username) {
         return ' [@' . $username . '](http://forum.phalconphp.com/user/0/' . $username . ')';
     });
 }
 /**
  * Strike-through `~~word~~` to `<del>word</del>`
  *
  * @param Text $text
  */
 public function processStrikeThrough(Text $text)
 {
     /** @noinspection PhpUnusedParameterInspection */
     $text->replace('{ (~~) (?=\\S) (.+?) (?<=\\S) \\1 }sx', function (Text $w, Text $a, Text $target) {
         return $this->getRenderer()->renderTag('del', $target, Tag::TYPE_INLINE);
     });
 }
Beispiel #8
0
 /**
  * @param Text $text
  */
 public function processMentions(Text $text)
 {
     /**
      * Turn @username into [@username](http://example.com/user/username)
      */
     $text->replace('/(?:^|[^a-zA-Z0-9.])@([A-Za-z]+[A-Za-z0-9]+)/', function (Text $w, Text $username) {
         return sprintf('[@%s](https://github.com/%s)', $username, $username);
     });
 }
Beispiel #9
0
 public function testTrim()
 {
     $text = new Text('  #Test##    ');
     $this->assertEquals('#Test##', $text->trim());
     $this->assertEquals('Test', $text->trim('#'));
     $text = new Text('Test##    ');
     $this->assertEquals('Test##', $text->rtrim());
     $this->assertEquals('Test', $text->rtrim('#'));
 }
Beispiel #10
0
 /**
  * @param Text $text
  */
 public function processIssues(Text $text)
 {
     /**
      * Turn the token to a github issue URL
      */
     $text->replace('(\\[GI:(\\d+)\\])', function (Text $w, Text $issue) {
         return sprintf($this->issueUrl, $issue, $issue);
     });
 }
 /**
  * Newlines
  *
  * The biggest difference that GFM introduces is in the handling of line breaks.
  * With SM you can hard wrap paragraphs of text and they will be combined into a single paragraph.
  * We find this to be the cause of a huge number of unintentional formatting errors.
  * GFM treats newlines in paragraph-like content as real line breaks, which is probably what you intended.
  *
  * @param Text $text
  */
 public function processHardBreak(Text $text)
 {
     $text->replace('/^[\\S\\<][^\\n]*\\n+(?!( |\\t)*<)/m', function (Text $w) {
         if ($w->match('/\\n{2}/') || $w->match('/  \\n/')) {
             return $w;
         }
         return $w->trim()->append("  \n");
     });
 }
Beispiel #12
0
 /**
  * @param Text $text
  *
  * @throws \Exception
  */
 public function processPullRequest(Text $text)
 {
     if (true === empty($this->accountName) || true === empty($this->projectName)) {
         throw new \Exception('Github account name or project are not set');
     }
     /**
      * Turn the token to a github issue URL
      */
     $text->replace('(\\[GPR:(\\d+)\\])', function (Text $w, Text $issue) {
         return sprintf($this->issueUrl, $issue, $this->accountName, $this->projectName, $issue);
     });
 }
 /**
  * @param Text $text
  */
 public function processItalic(Text $text)
 {
     if (!$text->contains('*') && !$text->contains('_')) {
         return;
     }
     /** @noinspection PhpUnusedParameterInspection */
     $text->replace('{ ([^\\*_\\s]?) (\\*|_) (?=\\S) (.+?) (?<=\\S) \\2 ([^\\*_\\s]?) }sx', function (Text $w, Text $prevChar, Text $a, Text $target, Text $nextChar) {
         if (!$prevChar->isEmpty() && !$nextChar->isEmpty() && $target->contains(' ')) {
             $this->getEmitter()->emit('escape.special_chars', [$w->replaceString(['*', '_'], ['\\*', '\\_'])]);
             return $w;
         }
         return $prevChar . $this->getRenderer()->renderItalicText($target) . $nextChar;
     });
 }
Beispiel #14
0
 /**
  * Turn standard URL into markdown URL
  *
  * @param Text $text
  */
 public function processStandardUrl(Text $text)
 {
     $hashes = [];
     // escape <code>
     $text->replace('{<code>.*?</code>}m', function (Text $w) use(&$hashes) {
         $md5 = md5($w);
         $hashes[$md5] = $w;
         return "{gfm-extraction-{$md5}}";
     });
     $text->replace('{(?<!]\\(|"|<|\\[)((?:https?|ftp)://[^\'"\\)>\\s]+)(?!>|\\"|\\])}', '<\\1>');
     /** @noinspection PhpUnusedParameterInspection */
     $text->replace('/\\{gfm-extraction-([0-9a-f]{32})\\}/m', function (Text $w, Text $md5) use(&$hashes) {
         return $hashes[(string) $md5];
     });
 }
Beispiel #15
0
 /**
  * @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";
     });
 }
 public function processTest(Text $text)
 {
     $hashes = [];
     // escape <code>
     $text->replace('{<code>.*?</code>}m', function (Text $w) use(&$hashes) {
         $md5 = md5($w);
         $hashes[$md5] = $w;
         return "{gfm-extraction-{$md5}}";
     });
     $text->replace('#(?:(?<=[href|src]=\\"|\')(?:[a-z0-9]+:\\/\\/)?|(?:[a-z0-9]+:\\/\\/))([^\'">\\s]+_+[^\'">\\s]*)+#i', function (Text $w) {
         $w->replaceString('_', '%5');
         return $w;
     });
     /** @noinspection PhpUnusedParameterInspection */
     $text->replace('/\\{gfm-extraction-([0-9a-f]{32})\\}/m', function (Text $w, Text $md5) use(&$hashes) {
         return $hashes[(string) $md5];
     });
 }
Beispiel #17
0
 /**
  * @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"));
 }
Beispiel #18
0
 /**
  * @param Text $text
  */
 public function processCodeSpan(Text $text)
 {
     if (!$text->contains('`')) {
         return;
     }
     $chars = ['\\\\', '`', '\\*', '_', '{', '}', '\\[', '\\]', '\\(', '\\)', '>', '#', '\\+', '\\-', '\\.', '!'];
     $chars = implode('|', $chars);
     /** @noinspection PhpUnusedParameterInspection */
     $text->replace('{
         (`+)        # $1 = Opening run of `
         (.+?)       # $2 = The code block
         (?<!`)
         \\1          # Matching closer
         (?!`)
     }x', function (Text $w, Text $b, Text $code) use($chars) {
         $code->trim()->escapeHtml(ENT_NOQUOTES);
         $code->replace(sprintf('/(?<!\\\\)(%s)/', $chars), '\\\\${1}');
         return $this->getRenderer()->renderCodeSpan($code);
     });
 }
 /**
  * {@inheritdoc}
  */
 public function renderList($content, array $options = array())
 {
     if (!$content instanceof Text) {
         $content = new Text($content);
     }
     $options = $this->createResolver()->setRequired(array('type'))->setAllowedValues(array('type' => array('ul', 'ol')))->setDefaults(array('type' => 'ul'))->resolve($options);
     $tag = new Tag($options['type']);
     $tag->setText($content->prepend("\n"));
     $tag->setAttributes($options['attr']);
     return $tag->render();
 }
Beispiel #20
0
 /**
  * @param Text $text
  */
 public function processBlockQuote(Text $text)
 {
     $text->replace('{
         (?:
         (?:
           ^[ \\t]*&gt;[ \\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]*&gt;[ \\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";
     });
 }
Beispiel #21
0
 /**
  * @param Text $text
  */
 public function processHeader(Text $text)
 {
     $text->replace('{
         ^h([1-6])  #1 Level
         (|=|>)\\.  #2 Align marker
         [ \\t]*
         (.+)
         [ \\t]*\\n+
     }mx', function (Text $w, Text $level, Text $mark, Text $header) {
         $attributes = [];
         switch ((string) $mark) {
             case '>':
                 $attributes['align'] = 'right';
                 break;
             case '=':
                 $attributes['align'] = 'center';
                 break;
         }
         return $this->getRenderer()->renderHeader($header, ['level' => (int) $level->getString(), 'attr' => $attributes]) . "\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";
     });
 }
 /**
  * @param Text $text
  */
 public function processWikiDefinitionList(Text $text)
 {
     $text->replace('{
             (^
                 ;[ \\t]*.+\\n
                 (:[ \\t]*.+\\n){1,}
             ){1,}
             \\n+
         }mx', function (Text $w) {
         $w->replace('/^;[ \\t]*(.+)\\n((:[ \\t]*.+\\n){1,})/m', function (Text $w, Text $item, Text $content) {
             $dt = Tag::create('dt')->setText($item);
             $lines = $content->trim()->ltrim(':')->split('/\\n?^:[ \\t]*/m', PREG_SPLIT_NO_EMPTY);
             if (count($lines) > 1) {
                 $dd = Tag::create('dd')->setText(Tag::create('p')->setText(trim($lines->join($this->getRenderer()->renderLineBreak() . "\n"))));
             } else {
                 $dd = Tag::create('dd')->setText($content->trim());
             }
             return $dt->render() . "\n" . $dd->render() . "\n";
         });
         $tag = Tag::create('dl')->setText("\n" . $w->trim() . "\n");
         return $tag->render();
     });
 }
Beispiel #24
0
 /**
  * @param Text $text
  */
 public function unhashHtmlBlocks(Text $text)
 {
     foreach ($this->markdown->getHashRegistry() as $hash => $html) {
         $text->replaceString($hash, trim($html));
     }
 }
Beispiel #25
0
 /**
  * @param Text $text
  */
 public function unescapeSpecialChars(Text $text)
 {
     foreach ($this->hashes as $char => $hash) {
         $text->replaceString($hash, $char);
     }
 }
 /**
  * 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]);
     });
 }
Beispiel #27
0
 public function processNewLines(Text $text)
 {
     $text->replace('/\\n/', '<br>');
     return $text;
 }
Beispiel #28
0
 /**
  * @param Text $text
  */
 protected function unescapePipes(Text $text)
 {
     $text->replaceString($this->hash, '|');
 }
Beispiel #29
0
 /**
  * @param Text $text
  */
 public function processComment(Text $text)
 {
     $text->replace('/^###\\.[ \\t]*(.+?)\\n{2,}/m', "\n\n");
 }
Beispiel #30
0
 /**
  * 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";
     });
 }