xref: /dokuwiki/inc/Parsing/ParserMode/GfmLink.php (revision e89aeebd5989e476b6a69236d9aabf72a9a01f14)
1<?php
2
3namespace dokuwiki\Parsing\ParserMode;
4
5use dokuwiki\Parsing\Handler;
6
7/**
8 * GFM inline link [text](url) with optional title [text](url "title").
9 *
10 * Deliberately not supported (see skip.php for the affected spec examples):
11 *
12 *   - Reference links [text][id] / [text][] / [foo] — the single-pass
13 *     lexer cannot resolve forward references to [foo]: url definitions.
14 *   - Pointy-bracket destinations [link](<foo bar>) — rarely used,
15 *     regex cost outweighs the benefit.
16 *   - Balanced-parens inside URLs [link](foo(bar)) — uncommon, complex.
17 *   - Image-in-link [![alt](img)](url) — requires GfmMedia plus nested
18 *     recursion across modes.
19 *   - Title HTML attribute — DokuWiki link handler instructions have no
20 *     title-attribute slot, and plumbing one through every renderer just
21 *     for this is out of scope. The title parses cleanly but is discarded.
22 */
23class GfmLink extends AbstractMode
24{
25    use LinkDispatch;
26
27    /** @inheritdoc */
28    public function getSort()
29    {
30        return 300;
31    }
32
33    /** @inheritdoc */
34    public function connectTo($mode)
35    {
36        // Pattern breakdown:
37        //   \[(?!\[)               — single `[`, not part of DW `[[`
38        //   [^\[\]\n]+              — text: no nested brackets, single line
39        //   \]\(                   — `]` immediately followed by `(` (GFM
40        //                             forbids whitespace between them)
41        //   \s*                    — optional whitespace around the URL
42        //   [^\s()\n]+             — URL: no whitespace, no parens, single line
43        //   (?:\s+(?:"[^"\n]*"
44        //          |'[^'\n]*'))?   — optional title in "..." or '...'
45        //   \s*\)                  — optional trailing whitespace, close paren
46        $pattern = '\[(?!\[)[^\[\]\n]+\]\('
47            . '\s*[^\s()\n]+'
48            . '(?:\s+(?:"[^"\n]*"|\'[^\'\n]*\'))?'
49            . '\s*\)';
50        $this->Lexer->addSpecialPattern($pattern, $mode, 'gfm_link');
51    }
52
53    /** @inheritdoc */
54    public function handle($match, $state, $pos, Handler $handler)
55    {
56        // The entry pattern has already validated the `[text](url)`
57        // shape, so we can destructure with plain string ops. Split on
58        // `](` to separate text from "url and optional title"; the URL
59        // is the first whitespace-delimited token of the remainder, and
60        // anything after it is the title — discarded, since DokuWiki
61        // link instructions have no title-attribute slot.
62        $sep    = strpos($match, '](');
63        $text   = substr($match, 1, $sep - 1);
64        $inside = trim(substr($match, $sep + 2, -1));
65        $url    = substr($inside, 0, strcspn($inside, " \t\n"));
66
67        $this->dispatchLink($url, $text, $pos, $handler);
68        return true;
69    }
70}
71