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