xref: /dokuwiki/inc/Parsing/Helpers/Code.php (revision 685560eb3044321b3bdd0be40985871ced5f1d05)
1<?php
2
3namespace dokuwiki\Parsing\Helpers;
4
5/**
6 * Pure helpers for parsing code / file attribute blocks.
7 *
8 * Side-effect-free: returns data and leaves handler emission to the
9 * caller. Shared by DokuWiki's Code / File modes and GfmCode / GfmFile.
10 */
11class Code
12{
13    /**
14     * Parse the attribute block of a code / file tag or fence opener.
15     *
16     * Accepts the text between <code and > (DokuWiki) or the info
17     * string after a fence opener (GFM). The grammar is the same in both
18     * places: an optional [key=value,...] bracket block appears
19     * anywhere in the string and contains highlight options; what
20     * remains, whitespace-split, is language then filename.
21     *
22     * Conventions carried over from DokuWiki's Code mode:
23     *   - "-" as the language means "no language" (returned as null);
24     *   - "html" is aliased to GeSHi's "html4strict" identifier.
25     *
26     * @param string $attr raw attribute text (no <code/> or fence chars)
27     * @return array{0: ?string, 1: ?string, 2: ?array} [language, filename, options]
28     */
29    public static function parseAttributes(string $attr): array
30    {
31        $options = null;
32        if (preg_match('/\[.*\]/', $attr, $optMatch)) {
33            $attr = str_replace($optMatch[0], '', $attr);
34            $options = self::parseHighlightOptions($optMatch[0]);
35        }
36
37        $parts = preg_split('/\s+/', trim($attr), 2, PREG_SPLIT_NO_EMPTY);
38        $language = $parts[0] ?? null;
39        $filename = $parts[1] ?? null;
40
41        if ($language === 'html') $language = 'html4strict';
42        if ($language === '-') $language = null;
43
44        return [$language, $filename, $options];
45    }
46
47    /**
48     * Parse a [key=value,...] block of highlight options.
49     *
50     * Keys without a value are treated as booleans (1). Values may be
51     * bare or "quoted"; quoted values may contain commas. Only a
52     * fixed whitelist of keys is retained (see below); unknown keys are
53     * silently dropped.
54     *
55     * @param string $options the [...] string including the brackets
56     * @return array|null key/value map, or null if nothing recognised
57     */
58    public static function parseHighlightOptions(string $options): ?array
59    {
60        $result = [];
61        preg_match_all('/(\w+(?:="[^"]*"))|(\w+(?:=[^\s]*))|(\w+[^=\s\]])(?:\s*)/', $options, $matches, PREG_SET_ORDER);
62        foreach ($matches as $match) {
63            $equal_sign = strpos($match[0], '=');
64            if ($equal_sign === false) {
65                $key = trim($match[0]);
66                $result[$key] = 1;
67            } else {
68                $key = substr($match[0], 0, $equal_sign);
69                $value = substr($match[0], $equal_sign + 1);
70                $value = trim($value, '"');
71                if ($value !== '') {
72                    $result[$key] = $value;
73                } else {
74                    $result[$key] = 1;
75                }
76            }
77        }
78
79        $result = array_intersect_key(
80            $result,
81            array_flip([
82                'enable_line_numbers',
83                'start_line_numbers_at',
84                'highlight_lines_extra',
85                'enable_keyword_links'
86            ])
87        );
88
89        if (isset($result['enable_line_numbers'])) {
90            if ($result['enable_line_numbers'] === 'false') {
91                $result['enable_line_numbers'] = false;
92            }
93            $result['enable_line_numbers'] = (bool)$result['enable_line_numbers'];
94        }
95        if (isset($result['highlight_lines_extra'])) {
96            $result['highlight_lines_extra'] = array_map(intval(...), explode(',', $result['highlight_lines_extra']));
97            $result['highlight_lines_extra'] = array_filter($result['highlight_lines_extra']);
98            $result['highlight_lines_extra'] = array_unique($result['highlight_lines_extra']);
99        }
100        if (isset($result['start_line_numbers_at'])) {
101            $result['start_line_numbers_at'] = (int)$result['start_line_numbers_at'];
102        }
103        if (isset($result['enable_keyword_links'])) {
104            if ($result['enable_keyword_links'] === 'false') {
105                $result['enable_keyword_links'] = false;
106            }
107            $result['enable_keyword_links'] = (bool)$result['enable_keyword_links'];
108        }
109        if (count($result) == 0) {
110            return null;
111        }
112
113        return $result;
114    }
115}
116