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