xref: /dokuwiki/inc/Parsing/ParserMode/GfmEmphasisStrong.php (revision 2bb62bca317961d66fa2908b40f183af01402a91)
1<?php
2
3namespace dokuwiki\Parsing\ParserMode;
4
5use dokuwiki\Parsing\Handler;
6
7/**
8 * GFM / CommonMark em-wrapping-strong via triple asterisks: `***text***`.
9 *
10 * Renders as <em><strong>text</strong></em>. Only the exact 3+3 symmetric
11 * variant is supported. Longer symmetric runs (`****foo****`,
12 * `******foo******`) or asymmetric runs (`***foo**`) require CommonMark's
13 * full delimiter-pairing algorithm and are out of scope.
14 *
15 * Sort 65 is below Strong (70) so this mode wins the lexer race for
16 * `***...***` patterns.
17 */
18class GfmEmphasisStrong extends AbstractFormatting
19{
20    /** @inheritdoc */
21    public function getSort()
22    {
23        return 65;
24    }
25
26    /** @inheritdoc */
27    protected function getModeName(): string
28    {
29        return 'gfm_emphasis_strong';
30    }
31
32    /** @inheritdoc */
33    protected function getEntryPattern(): string
34    {
35        // Broken down:
36        //   (?<!\*)                  — opener not preceded by `*` (so we
37        //                              don't match inside `****...` runs)
38        //   \*\*\*                   — exactly three opening `*`
39        //   (?=[^\s*])               — next body char: not whitespace, not `*`
40        //                              (flanking-opener rule)
41        //   (?=                      — lookahead: a valid closer must exist
42        //     CONTENT_UNTIL_PARA     —   body that doesn't cross a paragraph
43        //     [^\s]                  —   last body char: not whitespace
44        //                              (flanking-closer rule)
45        //     \*\*\*                 —   closing `***`
46        //     (?!\*)                 —   and not followed by another `*`
47        //                              (exactly 3, not 4+)
48        //   )
49        return '(?<!\*)\*\*\*(?=[^\s*])'
50            . '(?=' . self::CONTENT_UNTIL_PARA . '[^\s]\*\*\*(?!\*))';
51    }
52
53    /** @inheritdoc */
54    protected function getExitPattern(): string
55    {
56        return '(?<=[^\s])\*\*\*(?!\*)';
57    }
58
59    /**
60     * Emit em wrapping strong (and their closers in reverse order).
61     * Overridden because AbstractFormatting's default emits a single
62     * open/close pair — we need two each.
63     *
64     * @inheritdoc
65     */
66    public function handle($match, $state, $pos, Handler $handler)
67    {
68        switch ($state) {
69            case DOKU_LEXER_ENTER:
70                $handler->addCall('emphasis_open', [], $pos);
71                $handler->addCall('strong_open', [], $pos);
72                break;
73            case DOKU_LEXER_EXIT:
74                $handler->addCall('strong_close', [], $pos);
75                $handler->addCall('emphasis_close', [], $pos);
76                break;
77            case DOKU_LEXER_UNMATCHED:
78                $handler->addCall('cdata', [$match], $pos);
79                break;
80        }
81        return true;
82    }
83}
84