Skip to content

Commit 0dd4015

Browse files
authored
Merge pull request #480 from phpDocumentor/reference-resolving
Reference resolving
2 parents 140e3e3 + 78ce30e commit 0dd4015

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+695
-603
lines changed

packages/guides-restructured-text/src/RestructuredText/Parser/InlineLexer.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@
55
namespace phpDocumentor\Guides\RestructuredText\Parser;
66

77
use Doctrine\Common\Lexer\AbstractLexer;
8+
use phpDocumentor\Guides\ReferenceResolvers\ExternalReferenceResolver;
89
use ReflectionClass;
910

1011
use function array_column;
1112
use function array_flip;
13+
use function parse_url;
1214
use function preg_match;
1315

16+
use const PHP_URL_SCHEME;
17+
1418
/** @extends AbstractLexer<int, string> */
1519
final class InlineLexer extends AbstractLexer
1620
{
@@ -55,7 +59,7 @@ protected function getCatchablePatterns(): array
5559
return [
5660
'\\\\``', // must be a separate case, as the next pattern would split in "\`" + "`", causing it to become a intepreted text
5761
'\\\\[\s\S]', // Escaping hell... needs escaped slash in regex, but also in php.
58-
'https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)',
62+
ExternalReferenceResolver::SUPPORTED_SCHEMAS . ':[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*[-a-zA-Z0-9()@%_\\+~#&\\/=]', // standalone hyperlinks
5963
'\\S+@\\S+\\.\\S+',
6064
'[a-z0-9-]+_{2}', //Inline href.
6165
'[a-z0-9-]+_{1}(?=[\s\.+]|$)', //Inline href.
@@ -114,7 +118,7 @@ protected function getType(string &$value)
114118
return self::LITERAL;
115119
}
116120

117-
if (preg_match('/https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)/i', $value)) {
121+
if (preg_match('/' . ExternalReferenceResolver::SUPPORTED_SCHEMAS . ':[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*[-a-zA-Z0-9()@%_\\+~#&\\/=]/', $value) && parse_url($value, PHP_URL_SCHEME) !== null) {
118122
return self::HYPERLINK;
119123
}
120124

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/AnonymousPhraseRule.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
1010

1111
/**
12-
* Rule to parse for simple anonymous references, such as `myref__`
12+
* Rule to parse for anonymous references
13+
*
14+
* Syntax example:
15+
*
16+
* `Example anonymous reference`__
17+
* `Example reference <http://phpdoc.org>`__
18+
*
19+
* @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#anonymous-hyperlinks
1320
*/
1421
class AnonymousPhraseRule extends ReferenceRule
1522
{
@@ -21,19 +28,19 @@ public function applies(InlineLexer $lexer): bool
2128
public function apply(ParserContext $parserContext, InlineLexer $lexer): HyperLinkNode|null
2229
{
2330
$text = '';
24-
$url = null;
31+
$embeddedUrl = null;
2532
$initialPosition = $lexer->token?->position;
2633
$lexer->moveNext();
2734
while ($lexer->token !== null) {
2835
switch ($lexer->token->type) {
2936
case InlineLexer::PHRASE_ANONYMOUS_END:
3037
$lexer->moveNext();
3138

32-
return $this->createReference($parserContext, $text, $url, false);
39+
return $this->createAnonymousReference($parserContext, $text, $embeddedUrl);
3340

3441
case InlineLexer::EMBEDED_URL_START:
35-
$url = $this->parseEmbeddedUrl($lexer);
36-
if ($url === null) {
42+
$embeddedUrl = $this->parseEmbeddedUrl($lexer);
43+
if ($embeddedUrl === null) {
3744
$text .= '<';
3845
}
3946

@@ -50,6 +57,15 @@ public function apply(ParserContext $parserContext, InlineLexer $lexer): HyperLi
5057
return null;
5158
}
5259

60+
private function createAnonymousReference(ParserContext $parserContext, string $link, string|null $embeddedUrl): HyperLinkNode
61+
{
62+
$parserContext->resetAnonymousStack();
63+
$node = $this->createReference($parserContext, $link, $embeddedUrl, false);
64+
$parserContext->pushAnonymous($link);
65+
66+
return $node;
67+
}
68+
5369
public function getPriority(): int
5470
{
5571
return 1000;

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/AnonymousReferenceRule.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,13 @@
1111
use function trim;
1212

1313
/**
14-
* Rule to parse for simple anonymous references, such as `myref__`
14+
* Rule to parse for simple anonymous references
15+
*
16+
* Syntax example:
17+
*
18+
* Example reference__
19+
*
20+
* @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#anonymous-hyperlinks
1521
*/
1622
class AnonymousReferenceRule extends ReferenceRule
1723
{
@@ -34,7 +40,7 @@ public function apply(ParserContext $parserContext, InlineLexer $lexer): HyperLi
3440
private function createAnonymousReference(ParserContext $parserContext, string $link): HyperLinkNode
3541
{
3642
$parserContext->resetAnonymousStack();
37-
$node = $this->createReference($parserContext, $link);
43+
$node = $this->createReference($parserContext, $link, null, false);
3844
$parserContext->pushAnonymous($link);
3945

4046
return $node;

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/NamedPhraseRule.php

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,14 @@
99
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
1010

1111
/**
12-
* Rule to parse for simple named references, such as `myref_`
12+
* Rule to parse for named references
13+
*
14+
* Syntax examples:
15+
*
16+
* `Sample reference`_
17+
* `Another example <https://phpdoc.org>`_
18+
*
19+
* @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#hyperlink-references
1320
*/
1421
class NamedPhraseRule extends ReferenceRule
1522
{
@@ -21,25 +28,29 @@ public function applies(InlineLexer $lexer): bool
2128
public function apply(ParserContext $parserContext, InlineLexer $lexer): InlineNode|null
2229
{
2330
$text = '';
24-
$url = null;
31+
$embeddedUrl = null;
2532
$initialPosition = $lexer->token?->position;
2633
$lexer->moveNext();
2734
while ($lexer->token !== null) {
2835
switch ($lexer->token->type) {
2936
case InlineLexer::NAMED_REFERENCE_END:
3037
$lexer->moveNext();
3138
if ($text === '') {
32-
$text = $url ?? '';
39+
$text = $embeddedUrl ?? '';
3340
}
3441

35-
return $this->createReference($parserContext, $text, $url);
42+
return $this->createReference($parserContext, $text, $embeddedUrl);
3643

3744
case InlineLexer::EMBEDED_URL_START:
38-
$url = $this->parseEmbeddedUrl($lexer);
39-
if ($url === null) {
45+
$embeddedUrl = $this->parseEmbeddedUrl($lexer);
46+
if ($embeddedUrl === null) {
4047
$text .= '<';
4148
}
4249

50+
break;
51+
case InlineLexer::WHITESPACE:
52+
$text .= ' ';
53+
4354
break;
4455
default:
4556
$text .= $lexer->token->value;

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/NamedReferenceRule.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@
88
use phpDocumentor\Guides\ParserContext;
99
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
1010

11-
use function trim;
11+
use function rtrim;
1212

1313
/**
14-
* Rule to parse for simple named references, such as `myref_`
14+
* Rule to parse for simple named references
15+
*
16+
* Syntax examples:
17+
*
18+
* Sample reference_
19+
*
20+
* @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#hyperlink-references
1521
*/
1622
class NamedReferenceRule extends ReferenceRule
1723
{
@@ -22,7 +28,9 @@ public function applies(InlineLexer $lexer): bool
2228

2329
public function apply(ParserContext $parserContext, InlineLexer $lexer): InlineNode|null
2430
{
25-
$node = $this->createReference($parserContext, trim($lexer->token?->value ?? '', '_'));
31+
$value = rtrim($lexer->token?->value ?? '', '_');
32+
$node = $this->createReference($parserContext, $value);
33+
2634
$lexer->moveNext();
2735

2836
return $node;

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/ReferenceRule.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@
1414

1515
abstract class ReferenceRule extends AbstractInlineRule
1616
{
17-
protected function createReference(ParserContext $parserContext, string $link, string|null $url = null, bool $registerLink = true): HyperLinkNode
17+
protected function createReference(ParserContext $parserContext, string $link, string|null $embeddedUrl = null, bool $registerLink = true): HyperLinkNode
1818
{
1919
// the link may have a new line in it, so we need to strip it
2020
// before setting the link and adding a token to be replaced
2121
$link = str_replace("\n", ' ', $link);
2222
$link = trim(preg_replace('/\s+/', ' ', $link) ?? '');
2323

24-
if ($registerLink && $url !== null) {
25-
$parserContext->setLink($link, $url);
24+
if ($registerLink && $embeddedUrl !== null) {
25+
$parserContext->setLink($link, $embeddedUrl);
2626
}
2727

28-
return new HyperLinkNode($link, $url);
28+
return new HyperLinkNode($link, $embeddedUrl ?? $link);
2929
}
3030

3131
protected function parseEmbeddedUrl(InlineLexer $lexer): string|null

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/StandaloneEmailRule.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
1010

1111
/**
12-
* Rule to parse for simple anonymous references, such as `myref__`
12+
* Rule for standalone hyperlinks
13+
*
14+
* Syntax example:
15+
*
16+
17+
*
18+
* @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#standalone-hyperlinks
1319
*/
1420
class StandaloneEmailRule extends ReferenceRule
1521
{
@@ -20,12 +26,9 @@ public function applies(InlineLexer $lexer): bool
2026

2127
public function apply(ParserContext $parserContext, InlineLexer $lexer): HyperLinkNode|null
2228
{
23-
$node = $this->createReference(
24-
$parserContext,
25-
$lexer->token?->value ?? '',
26-
'mailto:' . $lexer->token?->value,
27-
false,
28-
);
29+
$value = $lexer->token?->value ?? '';
30+
$node = $this->createReference($parserContext, $value, $value, false);
31+
2932
$lexer->moveNext();
3033

3134
return $node;

packages/guides-restructured-text/src/RestructuredText/Parser/Productions/InlineRules/StandaloneHyperlinkRule.php

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
1010

1111
/**
12-
* Rule to parse for simple anonymous references, such as `myref__`
12+
* Rule for standalone hyperlinks
13+
*
14+
* Syntax example:
15+
*
16+
* https://phpdoc.org/
17+
*
18+
* @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#standalone-hyperlinks
1319
*/
1420
class StandaloneHyperlinkRule extends ReferenceRule
1521
{
@@ -20,12 +26,9 @@ public function applies(InlineLexer $lexer): bool
2026

2127
public function apply(ParserContext $parserContext, InlineLexer $lexer): HyperLinkNode|null
2228
{
23-
$node = $this->createReference(
24-
$parserContext,
25-
$lexer->token?->value ?? '',
26-
null,
27-
false,
28-
);
29+
$value = $lexer->token?->value ?? '';
30+
$node = $this->createReference($parserContext, $value, $value, false);
31+
2932
$lexer->moveNext();
3033

3134
return $node;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpDocumentor\Guides\RestructuredText\TextRoles;
6+
7+
use phpDocumentor\Guides\Nodes\Inline\AbstractLinkInlineNode;
8+
use phpDocumentor\Guides\ParserContext;
9+
use phpDocumentor\Guides\RestructuredText\Parser\InlineLexer;
10+
use Psr\Log\LoggerInterface;
11+
12+
use function sprintf;
13+
use function trim;
14+
15+
/** @see https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#embedded-uris-and-aliases */
16+
abstract class AbstractReferenceTextRole implements TextRole
17+
{
18+
private InlineLexer $lexer;
19+
20+
public function __construct(
21+
private readonly LoggerInterface $logger,
22+
) {
23+
// Do not inject the $lexer. It contains a state.
24+
$this->lexer = new InlineLexer();
25+
}
26+
27+
public function processNode(
28+
ParserContext $parserContext,
29+
string $role,
30+
string $content,
31+
string $rawContent,
32+
): AbstractLinkInlineNode {
33+
$referenceTarget = null;
34+
$value = null;
35+
36+
$part = '';
37+
$this->lexer->setInput($content);
38+
$this->lexer->moveNext();
39+
$this->lexer->moveNext();
40+
while ($this->lexer->token !== null) {
41+
$token = $this->lexer->token;
42+
switch ($token->type) {
43+
case InlineLexer::EMBEDED_URL_START:
44+
$value = trim($part);
45+
$part = '';
46+
47+
break;
48+
case InlineLexer::EMBEDED_URL_END:
49+
if ($value === null) {
50+
// not inside the embedded URL
51+
$part .= $token->value;
52+
break;
53+
}
54+
55+
if ($this->lexer->peek() !== null) {
56+
$this->logger->warning(
57+
sprintf(
58+
'Reference contains unexpected content after closing `>`: "%s"',
59+
$content,
60+
),
61+
$parserContext->getLoggerInformation(),
62+
);
63+
}
64+
65+
$referenceTarget = $part;
66+
$part = '';
67+
68+
break 2;
69+
default:
70+
$part .= $token->value;
71+
}
72+
73+
$this->lexer->moveNext();
74+
}
75+
76+
$value .= trim($part);
77+
78+
if ($referenceTarget === null) {
79+
$referenceTarget = $value;
80+
$value = null;
81+
}
82+
83+
return $this->createNode($referenceTarget, $value);
84+
}
85+
86+
abstract protected function createNode(string $referenceTarget, string|null $referenceName): AbstractLinkInlineNode;
87+
}

0 commit comments

Comments
 (0)