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