āCommonMark is the PHP Leagueās Markdown parser,ā she said. āItās super-configurable⦠you can even use additional extensions to expand its capabilities ā just like this one!ā
``` ## Usage Extensions can be added to any new `Environment`: ``` php use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment; use League\CommonMark\Extension\SmartPunct\SmartPunctExtension; // Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go $environment = Environment::createCommonMarkEnvironment(); // Add this extension $environment->addExtension(new SmartPunctExtension()); // Set your configuration $config = [ 'smartpunct' => [ 'double_quote_opener' => 'ā', 'double_quote_closer' => 'ā', 'single_quote_opener' => 'ā', 'single_quote_closer' => 'ā', ], ]; // Instantiate the converter engine and start converting some Markdown! $converter = new CommonMarkConverter($config, $environment); echo $converter->convertToHtml('# Hello World!'); ``` [link-league-commonmark]: https://github.com/thephpleague/commonmark PK ! ¼.$ $ SmartPunct/QuoteRenderer.phpnu ÕIw¶“ * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\ElementRendererInterface; use League\CommonMark\HtmlElement; use League\CommonMark\Inline\Element\AbstractInline; use League\CommonMark\Inline\Renderer\InlineRendererInterface; final class QuoteRenderer implements InlineRendererInterface { /** * @param AbstractInline $inline * @param ElementRendererInterface $htmlRenderer * * @return HtmlElement|string|null */ public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer) { if (!$inline instanceof Quote) { throw new \InvalidArgumentException(sprintf('Expected an instance of "%s", got "%s" instead', Quote::class, get_class($inline))); } // Handles unpaired quotes which remain after processing delimiters if ($inline->getContent() === Quote::SINGLE_QUOTE) { // Render as an apostrophe return Quote::SINGLE_QUOTE_CLOSER; } elseif ($inline->getContent() === Quote::DOUBLE_QUOTE) { // Render as an opening quote return Quote::DOUBLE_QUOTE_OPENER; } return $inline->getContent(); } } PK ! Äžō ō SmartPunct/PunctuationParser.phpnu ÕIw¶“ * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Inline\Element\Text; use League\CommonMark\Inline\Parser\InlineParserInterface; use League\CommonMark\InlineParserContext; final class PunctuationParser implements InlineParserInterface { /** * @return string[] */ public function getCharacters(): array { return ['-', '.']; } /** * @param InlineParserContext $inlineContext * * @return bool */ public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); $ch = $cursor->getCharacter(); // Ellipses if ($ch === '.' && $matched = $cursor->match('/^\\.( ?\\.)\\1/')) { $inlineContext->getContainer()->appendChild(new Text('ā¦')); return true; } // Em/En-dashes elseif ($ch === '-' && $matched = $cursor->match('/^(?getContainer()->appendChild(new Text( str_repeat($em_dash, $em_count) . str_repeat($en_dash, $en_count) )); return true; } return false; } } PK ! Ņj#ݳ ³ SmartPunct/QuoteParser.phpnu ÕIw¶“ * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Delimiter\Delimiter; use League\CommonMark\Inline\Parser\InlineParserInterface; use League\CommonMark\InlineParserContext; use League\CommonMark\Util\RegexHelper; final class QuoteParser implements InlineParserInterface { public const DOUBLE_QUOTES = [Quote::DOUBLE_QUOTE, Quote::DOUBLE_QUOTE_OPENER, Quote::DOUBLE_QUOTE_CLOSER]; public const SINGLE_QUOTES = [Quote::SINGLE_QUOTE, Quote::SINGLE_QUOTE_OPENER, Quote::SINGLE_QUOTE_CLOSER]; /** * @return string[] */ public function getCharacters(): array { return array_merge(self::DOUBLE_QUOTES, self::SINGLE_QUOTES); } /** * Normalizes any quote characters found and manually adds them to the delimiter stack * * @param InlineParserContext $inlineContext * * @return bool */ public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); $normalizedCharacter = $this->getNormalizedQuoteCharacter($cursor->getCharacter()); $charBefore = $cursor->peek(-1); if ($charBefore === null) { $charBefore = "\n"; } $cursor->advance(); $charAfter = $cursor->getCharacter(); if ($charAfter === null) { $charAfter = "\n"; } [$leftFlanking, $rightFlanking] = $this->determineFlanking($charBefore, $charAfter); $canOpen = $leftFlanking && !$rightFlanking; $canClose = $rightFlanking; $node = new Quote($normalizedCharacter, ['delim' => true]); $inlineContext->getContainer()->appendChild($node); // Add entry to stack to this opener $inlineContext->getDelimiterStack()->push(new Delimiter($normalizedCharacter, 1, $node, $canOpen, $canClose)); return true; } /** * @param string $character * * @return string|null */ private function getNormalizedQuoteCharacter($character) { if (in_array($character, self::DOUBLE_QUOTES)) { return Quote::DOUBLE_QUOTE; } elseif (in_array($character, self::SINGLE_QUOTES)) { return Quote::SINGLE_QUOTE; } return $character; } /** * @param string $charBefore * @param string $charAfter * * @return bool[] */ private function determineFlanking($charBefore, $charAfter) { $afterIsWhitespace = preg_match('/\pZ|\s/u', $charAfter); $afterIsPunctuation = preg_match(RegexHelper::REGEX_PUNCTUATION, $charAfter); $beforeIsWhitespace = preg_match('/\pZ|\s/u', $charBefore); $beforeIsPunctuation = preg_match(RegexHelper::REGEX_PUNCTUATION, $charBefore); $leftFlanking = !$afterIsWhitespace && !($afterIsPunctuation && !$beforeIsWhitespace && !$beforeIsPunctuation); $rightFlanking = !$beforeIsWhitespace && !($beforeIsPunctuation && !$afterIsWhitespace && !$afterIsPunctuation); return [$leftFlanking, $rightFlanking]; } } PK ! ®~ SmartPunct/Quote.phpnu ÕIw¶“ * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Inline\Element\AbstractStringContainer; final class Quote extends AbstractStringContainer { public const DOUBLE_QUOTE = '"'; public const DOUBLE_QUOTE_OPENER = 'ā'; public const DOUBLE_QUOTE_CLOSER = 'ā'; public const SINGLE_QUOTE = "'"; public const SINGLE_QUOTE_OPENER = 'ā'; public const SINGLE_QUOTE_CLOSER = 'ā'; } PK ! @ļ ļ " SmartPunct/SmartPunctExtension.phpnu ÕIw¶“ * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Block\Element\Document; use League\CommonMark\Block\Element\Paragraph; use League\CommonMark\Block\Renderer as CoreBlockRenderer; use League\CommonMark\ConfigurableEnvironmentInterface; use League\CommonMark\Extension\ExtensionInterface; use League\CommonMark\Inline\Element\Text; use League\CommonMark\Inline\Renderer as CoreInlineRenderer; final class SmartPunctExtension implements ExtensionInterface { public function register(ConfigurableEnvironmentInterface $environment) { $environment ->addInlineParser(new QuoteParser(), 10) ->addInlineParser(new PunctuationParser(), 0) ->addDelimiterProcessor(QuoteProcessor::createDoubleQuoteProcessor( $environment->getConfig('smartpunct/double_quote_opener', Quote::DOUBLE_QUOTE_OPENER), $environment->getConfig('smartpunct/double_quote_closer', Quote::DOUBLE_QUOTE_CLOSER) )) ->addDelimiterProcessor(QuoteProcessor::createSingleQuoteProcessor( $environment->getConfig('smartpunct/single_quote_opener', Quote::SINGLE_QUOTE_OPENER), $environment->getConfig('smartpunct/single_quote_closer', Quote::SINGLE_QUOTE_CLOSER) )) ->addBlockRenderer(Document::class, new CoreBlockRenderer\DocumentRenderer(), 0) ->addBlockRenderer(Paragraph::class, new CoreBlockRenderer\ParagraphRenderer(), 0) ->addInlineRenderer(Quote::class, new QuoteRenderer(), 100) ->addInlineRenderer(Text::class, new CoreInlineRenderer\TextRenderer(), 0) ; } } PK ! ɹV SmartPunct/QuoteProcessor.phpnu ÕIw¶“ * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Delimiter\DelimiterInterface; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Inline\Element\AbstractStringContainer; final class QuoteProcessor implements DelimiterProcessorInterface { /** @var string */ private $normalizedCharacter; /** @var string */ private $openerCharacter; /** @var string */ private $closerCharacter; /** * QuoteProcessor constructor. * * @param string $char * @param string $opener * @param string $closer */ private function __construct(string $char, string $opener, string $closer) { $this->normalizedCharacter = $char; $this->openerCharacter = $opener; $this->closerCharacter = $closer; } /** * {@inheritdoc} */ public function getOpeningCharacter(): string { return $this->normalizedCharacter; } /** * {@inheritdoc} */ public function getClosingCharacter(): string { return $this->normalizedCharacter; } /** * {@inheritdoc} */ public function getMinLength(): int { return 1; } /** * {@inheritdoc} */ public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int { return 1; } /** * {@inheritdoc} */ public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse) { $opener->insertAfter(new Quote($this->openerCharacter)); $closer->insertBefore(new Quote($this->closerCharacter)); } /** * Create a double-quote processor * * @param string $opener * @param string $closer * * @return QuoteProcessor */ public static function createDoubleQuoteProcessor(string $opener = Quote::DOUBLE_QUOTE_OPENER, string $closer = Quote::DOUBLE_QUOTE_CLOSER): self { return new self(Quote::DOUBLE_QUOTE, $opener, $closer); } /** * Create a single-quote processor * * @param string $opener * @param string $closer * * @return QuoteProcessor */ public static function createSingleQuoteProcessor(string $opener = Quote::SINGLE_QUOTE_OPENER, string $closer = Quote::SINGLE_QUOTE_CLOSER): self { return new self(Quote::SINGLE_QUOTE, $opener, $closer); } } PK ! Ķh„ Autolink/AutolinkExtension.phpnu ÕIw¶“ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\ConfigurableEnvironmentInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ExtensionInterface; final class AutolinkExtension implements ExtensionInterface { public function register(ConfigurableEnvironmentInterface $environment) { $environment->addEventListener(DocumentParsedEvent::class, new EmailAutolinkProcessor()); $environment->addEventListener(DocumentParsedEvent::class, new UrlAutolinkProcessor()); } } PK ! #nµ µ # Autolink/EmailAutolinkProcessor.phpnu ÕIw¶“ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Inline\Element\Link; use League\CommonMark\Inline\Element\Text; final class EmailAutolinkProcessor { const REGEX = '/([A-Za-z0-9.\-_+]+@[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+)/'; /** * @param DocumentParsedEvent $e * * @return void */ public function __invoke(DocumentParsedEvent $e) { $walker = $e->getDocument()->walker(); while ($event = $walker->next()) { $node = $event->getNode(); if ($node instanceof Text && !($node->parent() instanceof Link)) { self::processAutolinks($node); } } } private static function processAutolinks(Text $node) { $contents = \preg_split(self::REGEX, $node->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); if ($contents === false || \count($contents) === 1) { return; } $leftovers = ''; foreach ($contents as $i => $content) { if ($i % 2 === 0) { $text = $leftovers . $content; if ($text !== '') { $node->insertBefore(new Text($leftovers . $content)); } $leftovers = ''; continue; } // Does the URL end with punctuation that should be stripped? if (\substr($content, -1) === '.') { // Add the punctuation later $content = \substr($content, 0, -1); $leftovers = '.'; } // The last character cannot be - or _ if (\in_array(\substr($content, -1), ['-', '_'])) { $node->insertBefore(new Text($content . $leftovers)); $leftovers = ''; continue; } $node->insertBefore(new Link('mailto:' . $content, $content)); } $node->detach(); } } PK ! ¼EéŠ Autolink/README.mdnu ÕIw¶“ # URL and email autolinking extension for `league/commonmark` This extension adds [GFM-style autolinking][link-gfm-spec-autolinking] to the [`league/commonmark` Markdown parser for PHP][link-league-commonmark]. It automatically link URLs and email addresses even when the CommonMark `<...>` autolink syntax is not used. It also provides a parser to autolink `@mentions` to Twitter, Github, or any custom service you wish, though this is disabled by default. ## Usage Configure your `Environment` as usual and simply add the `AutolinkExtension` provided by this package: ```php use League\CommonMark\CommonMarkConverter; use League\CommonMark\Environment; use League\CommonMark\Extension\Autolink\AutolinkExtension; // Obtain a pre-configured Environment with all the CommonMark parsers/renderers ready-to-go $environment = Environment::createCommonMarkEnvironment(); // Add this extension $environment->addExtension(new AutolinkExtension()); // Instantiate the converter engine and start converting some Markdown! $converter = new CommonMarkConverter([], $environment); echo $converter->convertToHtml('I successfully installed the https://github.com/thephpleague/commonmark project with the Autolink extension!'); ``` ## `@mention` Autolinking This extension also provides functionality to automatically link "mentions" like `@colinodell` to Twitter, Github, or any other site of your choice! For Twitter: ```php use League\CommonMark\Environment; use League\CommonMark\Extension\Autolink\InlineMentionParser; $environment = Environment::createCommonMarkEnvironment(); $environment->addInlineParser(InlineMentionParser::createTwitterHandleParser()); // TODO: Instantiate your converter and convert some Markdown ``` For GitHub: ```php use League\CommonMark\Environment; use League\CommonMark\Extension\Autolink\InlineMentionParser; $environment = Environment::createCommonMarkEnvironment(); $environment->addInlineParser(InlineMentionParser::createGithubHandleParser()); // TODO: Instantiate your converter and convert some Markdown ``` Or configure your own custom one: ```php use League\CommonMark\Environment; use League\CommonMark\Extension\Autolink\InlineMentionParser; $environment = Environment::createCommonMarkEnvironment(); $environment->addInlineParser(new InlineMentionParser('https://www.example.com/users/%s/profile')); // TODO: Instantiate your converter and convert some Markdown ``` When creating your own, you can provide two parameters to the constructor: - A URL template where `%s` is replaced with the username (required) - A regular expression to parse and validate the username (optional - defaults to `'/^[A-Za-z0-9_]+(?!\w)/'`) [link-league-commonmark]: https://github.com/thephpleague/commonmark [link-gfm-spec-autolinking]: https://github.github.com/gfm/#autolinks-extension- PK ! p M M ! Autolink/UrlAutolinkProcessor.phpnu ÕIw¶“ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Inline\Element\Link; use League\CommonMark\Inline\Element\Text; final class UrlAutolinkProcessor { // RegEx adapted from https://github.com/symfony/symfony/blob/4.2/src/Symfony/Component/Validator/Constraints/UrlValidator.php const REGEX = '~ (?<=^|[ \\t\\n\\x0b\\x0c\\x0d*_\\~\\(]) # Can only come at the beginning of a line, after whitespace, or certain delimiting characters ( # Must start with a supported scheme + auth, or "www" (?: (?:%s):// # protocol (?:([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth |www\.) (?: (?:[\pL\pN\pS\-\.])+(?:\.?(?:[\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name | # or \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address | # or \[ (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) \] # an IPv6 address ) (?::[0-9]+)? # a port (optional) (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path (?:\? (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) )~ixu'; private $finalRegex; public function __construct(array $allowedProtocols = ['http', 'https', 'ftp']) { $this->finalRegex = \sprintf(self::REGEX, \implode('|', $allowedProtocols)); } /** * @param DocumentParsedEvent $e * * @return void */ public function __invoke(DocumentParsedEvent $e) { $walker = $e->getDocument()->walker(); while ($event = $walker->next()) { $node = $event->getNode(); if ($node instanceof Text && !($node->parent() instanceof Link)) { self::processAutolinks($node, $this->finalRegex); } } } private static function processAutolinks(Text $node, $regex) { $contents = \preg_split($regex, $node->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE); if ($contents === false || \count($contents) === 1) { return; } $leftovers = ''; foreach ($contents as $i => $content) { // Even-indexed elements are things before/after the URLs if ($i % 2 === 0) { // Insert any left-over characters here as well $text = $leftovers . $content; if ($text !== '') { $node->insertBefore(new Text($leftovers . $content)); } $leftovers = ''; continue; } $leftovers = ''; // Does the URL end with punctuation that should be stripped? if (\preg_match('/(.+)([?!.,:*_~]+)$/', $content, $matches)) { // Add the punctuation later $content = $matches[1]; $leftovers = $matches[2]; } // Does the URL end with something that looks like an entity reference? if (\preg_match('/(.+)(&[A-Za-z0-9]+;)$/', $content, $matches)) { $content = $matches[1]; $leftovers = $matches[2] . $leftovers; } // Does the URL need its closing paren chopped off? if (\substr($content, -1) === ')' && self::hasMoreCloserParensThanOpeners($content)) { $content = \substr($content, 0, -1); $leftovers = ')' . $leftovers; } self::addLink($node, $content); } $node->detach(); } private static function addLink(Text $node, $url) { // Auto-prefix 'http://' onto 'www' URLs if (\substr($url, 0, 4) === 'www.') { $node->insertBefore(new Link('http://' . $url, $url)); return; } $node->insertBefore(new Link($url, $url)); } /** * @param string $content * * @return bool */ private static function hasMoreCloserParensThanOpeners($content) { // Scan the entire autolink for the total number of parentheses. // If there is a greater number of closing parentheses than opening ones, // we donāt consider the last character part of the autolink, in order to // facilitate including an autolink inside a parenthesis. \preg_match_all('/[()]/', $content, $matches); $charCount = ['(' => 0, ')' => 0]; foreach ($matches[0] as $char) { $charCount[$char]++; } return $charCount[')'] > $charCount['(']; } } PK ! CpŪ Ū Autolink/InlineMentionParser.phpnu ÕIw¶“ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Inline\Element\Link; use League\CommonMark\Inline\Parser\InlineParserInterface; use League\CommonMark\InlineParserContext; final class InlineMentionParser implements InlineParserInterface { /** @var string */ private $linkPattern; /** @var string */ private $handleRegex; /** * @param string $linkPattern * @param string $handleRegex */ public function __construct($linkPattern, $handleRegex = '/^[A-Za-z0-9_]+(?!\w)/') { $this->linkPattern = $linkPattern; $this->handleRegex = $handleRegex; } /** * {@inheritdoc} */ public function getCharacters(): array { return ['@']; } /** * {@inheritdoc} */ public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); // The @ symbol must not have any other characters immediately prior $previousChar = $cursor->peek(-1); if ($previousChar !== null && $previousChar !== ' ') { // peek() doesn't modify the cursor, so no need to restore state first return false; } // Save the cursor state in case we need to rewind and bail $previousState = $cursor->saveState(); // Advance past the @ symbol to keep parsing simpler $cursor->advance(); // Parse the handle $handle = $cursor->match($this->handleRegex); if (empty($handle)) { // Regex failed to match; this isn't a valid Twitter handle $cursor->restoreState($previousState); return false; } $url = \sprintf($this->linkPattern, $handle); $inlineContext->getContainer()->appendChild(new Link($url, '@' . $handle)); return true; } public static function createTwitterHandleParser() { return new self('https://twitter.com/%s', '/^[A-Za-z0-9_]{1,15}(?!\w)/'); } public static function createGithubHandleParser() { // RegEx adapted from https://github.com/shinnn/github-username-regex/blob/master/index.js return new self('https://www.github.com/%s', '/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/'); } } PK ! , ķ Table/TableExtension.phpnu ÕIw¶“ * (c) Webuni s.r.o.| th | th(center) | th(right<)/th> |
|---|---|---|
| td | td | td |