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 * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
9 *  - (c) John MacFarlane
10 *
11 * For the full copyright and license information, please view the LICENSE
12 * file that was distributed with this source code.
13 */
14
15namespace League\CommonMark\Reference;
16
17use League\CommonMark\Cursor;
18use League\CommonMark\Util\LinkParserHelper;
19
20final class ReferenceParser
21{
22    /**
23     * @var ReferenceMapInterface
24     */
25    private $referenceMap;
26
27    public function __construct(ReferenceMapInterface $referenceMap)
28    {
29        $this->referenceMap = $referenceMap;
30    }
31
32    /**
33     * Attempt to parse a link reference, modifying the refmap.
34     *
35     * @param Cursor $cursor
36     *
37     * @return bool
38     */
39    public function parse(Cursor $cursor)
40    {
41        if ($cursor->isAtEnd()) {
42            return false;
43        }
44
45        $initialState = $cursor->saveState();
46
47        $matchChars = LinkParserHelper::parseLinkLabel($cursor);
48        if ($matchChars === 0) {
49            $cursor->restoreState($initialState);
50
51            return false;
52        }
53
54        // We need to trim the opening and closing brackets from the previously-matched text
55        $label = \substr($cursor->getPreviousText(), 1, -1);
56
57        if (\preg_match('/[^\s]/', $label) === 0) {
58            $cursor->restoreState($initialState);
59
60            return false;
61        }
62
63        if ($cursor->getCharacter() !== ':') {
64            $cursor->restoreState($initialState);
65
66            return false;
67        }
68
69        // Advance past the colon
70        $cursor->advanceBy(1);
71
72        // Link URL
73        $cursor->advanceToNextNonSpaceOrNewline();
74
75        $destination = LinkParserHelper::parseLinkDestination($cursor);
76        if ($destination === null) {
77            $cursor->restoreState($initialState);
78
79            return false;
80        }
81
82        $previousState = $cursor->saveState();
83
84        if ($cursor->advanceToNextNonSpaceOrNewline() > 0) {
85            $title = LinkParserHelper::parseLinkTitle($cursor);
86        }
87
88        if (!isset($title)) {
89            $title = '';
90            $cursor->restoreState($previousState);
91        }
92
93        // Make sure we're at line end:
94        $atLineEnd = true;
95        if ($cursor->match('/^ *(?:\n|$)/') === null) {
96            if ($title === '') {
97                $atLineEnd = false;
98            } else {
99                // The potential title we found is not at the line end,
100                // but it could still be a legal link reference if we
101                // discard the title
102                $title = '';
103                // rewind before spaces
104                $cursor->restoreState($previousState);
105                // and instead check if the link URL is at the line end
106                $atLineEnd = $cursor->match('/^ *(?:\n|$)/') !== null;
107            }
108        }
109
110        if (!$atLineEnd) {
111            $cursor->restoreState($initialState);
112
113            return false;
114        }
115
116        if (!$this->referenceMap->contains($label)) {
117            $reference = new Reference($label, $destination, $title);
118            $this->referenceMap->addReference($reference);
119        }
120
121        return true;
122    }
123}
124