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