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