xref: /dokuwiki/inc/Parsing/ParserMode/AbstractFormatting.php (revision 75364f13219a5af44f52c564ea0a62df64c3a17f)
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     * Formatting modes accept other formatting, substitutions, and disabled modes.
17     *
18     * @inheritdoc
19     */
20    protected function allowedCategories(): array
21    {
22        return [
23            ModeRegistry::CATEGORY_FORMATTING,
24            ModeRegistry::CATEGORY_SUBSTITUTION,
25            ModeRegistry::CATEGORY_DISABLED,
26        ];
27    }
28
29    /**
30     * Exclude self to prevent self-nesting (e.g. bold inside bold).
31     *
32     * @inheritdoc
33     */
34    protected function filterAllowedModes(array $modes): array
35    {
36        $self = $this->getModeName();
37        return array_values(array_filter($modes, static fn($mode) => $mode !== $self));
38    }
39
40    /** @inheritdoc */
41    public function connectTo($mode)
42    {
43        // Can't nest formatting in itself
44        if ($mode === $this->getModeName()) {
45            return;
46        }
47
48        $this->Lexer->addEntryPattern(
49            $this->getEntryPattern(),
50            $mode,
51            $this->getModeName()
52        );
53    }
54
55    /**
56     * @return string The regex pattern that starts this formatting
57     */
58    abstract protected function getEntryPattern(): string;
59
60    /**
61     * @return string The regex pattern that ends this formatting
62     */
63    abstract protected function getExitPattern(): string;
64
65    /**
66     * @return string The mode name used for lexer registration
67     */
68    abstract protected function getModeName(): string;
69
70    /**
71     * @return string The name used for emitted open/close handler instructions
72     *
73     * Defaults to the mode name. Override in subclasses where the emitted
74     * instruction should differ from the lexer mode name (e.g. Gfm modes
75     * that share instructions with a DW counterpart).
76     */
77    protected function getInstructionName(): string
78    {
79        return $this->getModeName();
80    }
81
82    /** @inheritdoc */
83    public function postConnect()
84    {
85        $this->Lexer->addExitPattern(
86            $this->getExitPattern(),
87            $this->getModeName()
88        );
89    }
90
91    /** @inheritdoc */
92    public function handle($match, $state, $pos, Handler $handler)
93    {
94        $name = $this->getInstructionName();
95        match ($state) {
96            DOKU_LEXER_ENTER => $handler->addCall($name . '_open', [], $pos),
97            DOKU_LEXER_EXIT => $handler->addCall($name . '_close', [], $pos),
98            DOKU_LEXER_UNMATCHED => $handler->addCall('cdata', [$match], $pos),
99            default => true,
100        };
101        return true;
102    }
103}
104