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