xref: /dokuwiki/inc/Parsing/ParserMode/Code.php (revision 71096e46fcbfaeaa808667aba794e77fe2780169)
1<?php
2
3namespace dokuwiki\Parsing\ParserMode;
4
5use dokuwiki\Parsing\Handler;
6
7class Code extends AbstractMode
8{
9    /** @var string The call type used in addCall ('code' or 'file') */
10    protected $type = 'code';
11
12    /** @inheritdoc */
13    public function getSort()
14    {
15        return 200;
16    }
17
18    /** @inheritdoc */
19    public function connectTo($mode)
20    {
21        $this->Lexer->addEntryPattern('<code\b(?=.*</code>)', $mode, 'code');
22    }
23
24    /** @inheritdoc */
25    public function postConnect()
26    {
27        $this->Lexer->addExitPattern('</code>', 'code');
28    }
29
30    /** @inheritdoc */
31    public function handle($match, $state, $pos, Handler $handler)
32    {
33        if ($state !== DOKU_LEXER_UNMATCHED) return true;
34
35        // split "language filename [options]>content" at the first >
36        [$attr, $content] = sexplode('>', $match, 2, '');
37
38        // extract highlight options from [...]
39        $hasOptions = preg_match('/\[.*\]/', $attr, $optMatch);
40        if ($hasOptions) {
41            $attr = str_replace($optMatch[0], '', $attr);
42        }
43
44        // split remaining attributes into language and filename
45        $parts = preg_split('/\s+/', $attr, 2, PREG_SPLIT_NO_EMPTY);
46        $language = $parts[0] ?? null;
47        $filename = $parts[1] ?? null;
48
49        // normalize language
50        if ($language === 'html') $language = 'html4strict';
51        if ($language === '-') $language = null;
52
53        $param = [$content, $language, $filename];
54        if ($hasOptions) {
55            $param[] = $this->parseHighlightOptions($optMatch[0]);
56        }
57        $handler->addCall($this->type, $param, $pos);
58
59        return true;
60    }
61
62    /**
63     * Internal function for parsing highlight options.
64     * $options is parsed for key value pairs separated by commas.
65     * A value might also be missing in which case the value will simply
66     * be set to true. Commas in strings are ignored, e.g. option="4,56"
67     * will work as expected and will only create one entry.
68     *
69     * @param string $options space separated list of key-value pairs
70     * @return array|null Array of key-value pairs or null if no entries found
71     */
72    protected function parseHighlightOptions($options)
73    {
74        $result = [];
75        preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER);
76        foreach ($matches as $match) {
77            $equal_sign = strpos($match[0], '=');
78            if ($equal_sign === false) {
79                $key = trim($match[0]);
80                $result[$key] = 1;
81            } else {
82                $key = substr($match[0], 0, $equal_sign);
83                $value = substr($match[0], $equal_sign + 1);
84                $value = trim($value, '"');
85                if ($value !== '') {
86                    $result[$key] = $value;
87                } else {
88                    $result[$key] = 1;
89                }
90            }
91        }
92
93        // Check for supported options
94        $result = array_intersect_key(
95            $result,
96            array_flip([
97                'enable_line_numbers',
98                'start_line_numbers_at',
99                'highlight_lines_extra',
100                'enable_keyword_links'
101            ])
102        );
103
104        // Sanitize values
105        if (isset($result['enable_line_numbers'])) {
106            if ($result['enable_line_numbers'] === 'false') {
107                $result['enable_line_numbers'] = false;
108            }
109            $result['enable_line_numbers'] = (bool)$result['enable_line_numbers'];
110        }
111        if (isset($result['highlight_lines_extra'])) {
112            $result['highlight_lines_extra'] = array_map(intval(...), explode(',', $result['highlight_lines_extra']));
113            $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']);
114            $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']);
115        }
116        if (isset($result['start_line_numbers_at'])) {
117            $result['start_line_numbers_at'] = (int)$result['start_line_numbers_at'];
118        }
119        if (isset($result['enable_keyword_links'])) {
120            if ($result['enable_keyword_links'] === 'false') {
121                $result['enable_keyword_links'] = false;
122            }
123            $result['enable_keyword_links'] = (bool)$result['enable_keyword_links'];
124        }
125        if (count($result) == 0) {
126            return null;
127        }
128
129        return $result;
130    }
131}
132