xref: /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php (revision 1e28e406b358f79221c515b2a56520d5dbbfb6c8)
1<?php
2
3namespace dokuwiki\Parsing\ParserMode;
4
5use dokuwiki\Parsing\Handler;
6use dokuwiki\Parsing\ModeRegistry;
7
8/**
9 * Base class for inline formatting modes (bold, italic, underline, etc.)
10 *
11 * Each concrete subclass defines its entry/exit patterns, mode name, and sort order.
12 */
13abstract class AbstractFormatting extends AbstractMode
14{
15    /**
16     * Constructor. Sets up allowed modes for this formatting type.
17     *
18     * Formatting modes accept other formatting, substitutions, and disabled modes,
19     * but exclude themselves to prevent self-nesting (e.g. bold inside bold).
20     */
21    public function __construct()
22    {
23        $self = $this->getModeName();
24        $this->allowedModes = array_filter(
25            ModeRegistry::getInstance()->getModesForCategories([
26                ModeRegistry::CATEGORY_FORMATTING,
27                ModeRegistry::CATEGORY_SUBSTITION,
28                ModeRegistry::CATEGORY_DISABLED,
29            ]),
30            static fn($mode) => $mode !== $self
31        );
32    }
33
34    /** @inheritdoc */
35    public function connectTo($mode)
36    {
37        // Can't nest formatting in itself
38        if ($mode === $this->getModeName()) {
39            return;
40        }
41
42        $this->Lexer->addEntryPattern(
43            $this->getEntryPattern(),
44            $mode,
45            $this->getModeName()
46        );
47    }
48
49    /**
50     * @return string The regex pattern that starts this formatting
51     */
52    abstract protected function getEntryPattern(): string;
53
54    /**
55     * @return string The regex pattern that ends this formatting
56     */
57    abstract protected function getExitPattern(): string;
58
59    /**
60     * @return string The mode name used for lexer registration
61     */
62    abstract protected function getModeName(): string;
63
64    /**
65     * @return string The name used for emitted open/close handler instructions
66     *
67     * Defaults to the mode name. Override in subclasses where the emitted
68     * instruction should differ from the lexer mode name (e.g. Gfm modes
69     * that share instructions with a DW counterpart).
70     */
71    protected function getInstructionName(): string
72    {
73        return $this->getModeName();
74    }
75
76    /** @inheritdoc */
77    public function postConnect()
78    {
79        $this->Lexer->addExitPattern(
80            $this->getExitPattern(),
81            $this->getModeName()
82        );
83    }
84
85    /** @inheritdoc */
86    public function handle($match, $state, $pos, Handler $handler)
87    {
88        $name = $this->getInstructionName();
89        match ($state) {
90            DOKU_LEXER_ENTER => $handler->addCall($name . '_open', [], $pos),
91            DOKU_LEXER_EXIT => $handler->addCall($name . '_close', [], $pos),
92            DOKU_LEXER_UNMATCHED => $handler->addCall('cdata', [$match], $pos),
93            default => true,
94        };
95        return true;
96    }
97}
98