* * usage: ex. ... */ class syntax_plugin_codeprettify_code extends DokuWiki_Syntax_Plugin { public function getType() { // Syntax Type return 'protected'; } public function getPType() { // Paragraph Type return 'block'; } /** * Connect pattern to lexer */ protected $mode, $pattern; public function getSort() { // sort number used to determine priority of this mode return 199; // < native 'code' mode (=200) } public function preConnect() { // syntax mode, drop 'syntax_' from class name $this->mode = substr(__CLASS__, 7); // allowing nested "" in title using regex atomic grouping $n = 3; $param = str_repeat('(?>[^<>\n]+|<', $n).str_repeat('>)*', $n); // syntax patterns $this->pattern[1] = ''.'(?=.*?)'; $this->pattern[4] = ''; // DokuWiki original syntax patterns $this->pattern[11] = '(?=.*?)'; $this->pattern[14] = ''; } public function connectTo($mode) { $this->Lexer->addEntryPattern($this->pattern[1], $mode, $this->mode); if ($this->getConf('override')) { $this->Lexer->addEntryPattern($this->pattern[11], $mode, $this->mode); } } public function postConnect() { $this->Lexer->addExitPattern($this->pattern[4], $this->mode); if ($this->getConf('override')) { $this->Lexer->addExitPattern($this->pattern[14], $this->mode); } } /** * GeSHi Options Parser * * DokuWiki release 2018-04-22 "Greebo" supports some GeSHi options * for syntax highlighting * alternative of parse_highlight_options() in inc/parser/handler.php * * @param string $params space separated list of key-value pairs * @return array * @see also https://www.dokuwiki.org/syntax_highlighting */ private function getGeshiOption($params) { $opts = []; // remove enclosing brackets and double-quotes $params = str_replace('"', '', trim($params, '[]')); if (preg_match_all('/(\w+)=?(\w+)?/', $params, $matches)) { // make keys lowercase $keys = array_map('strtolower', $matches[1]); // interpret boolian string values $values = array_map( function($value) { if (is_numeric($value)) { return $value; } else { $s = strtolower($value); if ($s == 'true') $value = 1; if ($s == 'false') $value = 0; return $value; } }, $matches[2] ); // Note: last one prevails if same keys have appeared $opts = array_combine($keys, $values); } return $opts; } /** * Convert/interpret GeSHi Options to correspondent Prettifier options * - enable_line_numbers=0 -> nolinenums * - start_line_numbers_at=1 -> linenums:1 * * @param array $opts GeSHi options * @return string Prettifier linenums parameter * @see also https://www.dokuwiki.org/syntax_highlighting */ private function strGeshiOptions(array $opts=[]) { $option = 'linenums'; if (isset($opts['enable_line_numbers']) && $opts['enable_line_numbers'] == 0) { $option = 'no'.$option; } if (isset($opts['start_line_numbers_at']) && $opts['start_line_numbers_at'] > 0) { $option = $option.':'.$opts['start_line_numbers_at']; } return $option; } /** * Prettifier Options Parser * * @param string $params * @return array */ private function getPrettifierOptions($params) { $opts = []; // offset holds the position of the matched string // if offset become 0, the first token of given params is NOT language $offset = 1; if (preg_match('/\b(no)?linenums(:\d+)?/', $params, $m, PREG_OFFSET_CAPTURE)) { $offset = ($offset > 0) ? $m[0][1] : 1; $opts['linenums'] = ('no' == $m[1][0] ?? '') ? 'linenums:0' : $m[0][0]; } else { $opts['linenums'] = $this->getConf('linenums') ? 'linenums' : ''; } if (preg_match('/\blang-\w+/', $params, $m, PREG_OFFSET_CAPTURE)) { $offset = ($offset > 0) ? $m[0][1] : 1; $opts['language'] = $m[0][0]; } elseif ($offset) { // assume the first token is language; ex. C, php, css list ($lang, ) = explode(' ', $params, 2); $opts['language'] = $lang ? 'lang-'.$lang : ''; } return $opts; } /** * Handle the match */ public function handle($match, $state, $pos, Doku_Handler $handler) { switch ($state) { case DOKU_LEXER_ENTER: list($params, $title) = array_pad(explode('|', substr($match, 5, -1), 2), 2, ''); // title parameter if ($title) { // remove first "document_start" and last "document_end" instructions $calls = array_slice(p_get_instructions($title), 1, -1); } else { $calls = null; } // prettifier parameters $params = trim($params, ' :'); if ( preg_match('/\[.*\]/', $params, $matches) ) { // replace GeSHi parameters $params = str_replace( $matches[0], $this->strGeshiOptions( $this->getGeshiOption($matches[0]) ), $params ); } $opts['prettify'] = 'prettyprint'; $opts += $this->getPrettifierOptions($params); $params= implode(' ', $opts); return $data = [$state, $params, $calls]; case DOKU_LEXER_UNMATCHED: return $data = [$state, $match]; case DOKU_LEXER_EXIT: return $data = [$state, '']; } return false; } /** * Create output */ function render($format, Doku_Renderer $renderer, $data) { if ($format == 'metadata') return false; if (empty($data)) return false; $state = $data[0]; switch ($state) { case DOKU_LEXER_ENTER: list($args, $calls) = array($data[1], $data[2]); if (isset($calls)) { // title of code box $renderer->doc .= '
'; $renderer->nest($calls); $renderer->doc .= '
'; } $renderer->doc .= '
';
                break;
            case DOKU_LEXER_UNMATCHED:
                $match = $data[1];
                $renderer->doc .= $renderer->_xmlEntities($match);
                break;
            case DOKU_LEXER_EXIT:
                $renderer->doc .= '
'; break; } return true; } }