1<?php
2
3/*
4 * This file is part of the league/commonmark package.
5 *
6 * (c) Colin O'Dell <colinodell@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace League\CommonMark\Extension\Mention;
13
14use League\CommonMark\Extension\Mention\Generator\CallbackGenerator;
15use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface;
16use League\CommonMark\Extension\Mention\Generator\StringTemplateLinkGenerator;
17use League\CommonMark\Inline\Parser\InlineParserInterface;
18use League\CommonMark\InlineParserContext;
19
20final class MentionParser implements InlineParserInterface
21{
22    /** @var string */
23    private $symbol;
24
25    /** @var string */
26    private $mentionRegex;
27
28    /** @var MentionGeneratorInterface */
29    private $mentionGenerator;
30
31    public function __construct(string $symbol, string $mentionRegex, MentionGeneratorInterface $mentionGenerator)
32    {
33        $this->symbol = $symbol;
34        $this->mentionRegex = $mentionRegex;
35        $this->mentionGenerator = $mentionGenerator;
36    }
37
38    public function getCharacters(): array
39    {
40        return [$this->symbol];
41    }
42
43    public function parse(InlineParserContext $inlineContext): bool
44    {
45        $cursor = $inlineContext->getCursor();
46
47        // The symbol must not have any other characters immediately prior
48        $previousChar = $cursor->peek(-1);
49        if ($previousChar !== null && \preg_match('/\w/', $previousChar)) {
50            // peek() doesn't modify the cursor, so no need to restore state first
51            return false;
52        }
53
54        // Save the cursor state in case we need to rewind and bail
55        $previousState = $cursor->saveState();
56
57        // Advance past the symbol to keep parsing simpler
58        $cursor->advance();
59
60        // Parse the mention match value
61        $identifier = $cursor->match($this->mentionRegex);
62        if ($identifier === null) {
63            // Regex failed to match; this isn't a valid mention
64            $cursor->restoreState($previousState);
65
66            return false;
67        }
68
69        $mention = $this->mentionGenerator->generateMention(new Mention($this->symbol, $identifier));
70
71        if ($mention === null) {
72            $cursor->restoreState($previousState);
73
74            return false;
75        }
76
77        $inlineContext->getContainer()->appendChild($mention);
78
79        return true;
80    }
81
82    public static function createWithStringTemplate(string $symbol, string $mentionRegex, string $urlTemplate): MentionParser
83    {
84        return new self($symbol, $mentionRegex, new StringTemplateLinkGenerator($urlTemplate));
85    }
86
87    public static function createWithCallback(string $symbol, string $mentionRegex, callable $callback): MentionParser
88    {
89        return new self($symbol, $mentionRegex, new CallbackGenerator($callback));
90    }
91}
92