1<?php 2 3// implementation of 4// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code 5 6// must be run within Dokuwiki 7use ComboStrap\Html; 8use ComboStrap\PluginUtility; 9use ComboStrap\Prism; 10use ComboStrap\TagAttributes; 11 12require_once(__DIR__ . '/../ComboStrap/StringUtility.php'); 13require_once(__DIR__ . '/../ComboStrap/Prism.php'); 14 15if (!defined('DOKU_INC')) die(); 16 17/** 18 * 19 * Support <a href="https://github.github.com/gfm/#fenced-code-blocks">Github code block</a> 20 * 21 * The original code markdown code block is the {@link syntax_plugin_combo_preformatted} 22 */ 23class syntax_plugin_combo_codemarkdown extends DokuWiki_Syntax_Plugin 24{ 25 26 27 /** 28 * The exit pattern of markdown 29 * use in the enter pattern as regexp look-ahead 30 */ 31 const MARKDOWN_EXIT_PATTERN = "^\s*(?:\`|~){3}\s*$"; 32 33 /** 34 * The syntax name, not a tag 35 * This must be the same that the last part name 36 * of the class (without any space !) 37 * This is the id in the callstack 38 * and gives us just a way to get the 39 * {@link \dokuwiki\Extension\SyntaxPlugin::getPluginComponent()} 40 * value (tag = plugin component) 41 */ 42 const TAG = "codemarkdown"; 43 44 45 function getType() 46 { 47 /** 48 * You can't write in a code block 49 */ 50 return 'protected'; 51 } 52 53 /** 54 * How DokuWiki will add P element 55 * 56 * * 'normal' - The plugin can be used inside paragraphs 57 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 58 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 59 * 60 * @see DokuWiki_Syntax_Plugin::getPType() 61 */ 62 function getPType() 63 { 64 return 'block'; 65 } 66 67 /** 68 * @return array 69 * Allow which kind of plugin inside 70 * 71 * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 72 * because we manage self the content and we call self the parser 73 * 74 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 75 */ 76 function getAllowedTypes() 77 { 78 return array(); 79 } 80 81 /** 82 * Order of precedence 83 * @return int 84 */ 85 function getSort(): int 86 { 87 88 return 199; 89 } 90 91 92 function connectTo($mode) 93 { 94 95 // 96 // This pattern works with mgs flag 97 // 98 // Match: 99 // * the start of a line 100 // * any consecutive blank character 101 // * 3 consecutive backtick characters (`) or tildes (~) 102 // * a word (info string - ie the language) 103 // * any consecutive blank character 104 // * the end of a line 105 // * a look ahead to see if we have the exit pattern 106 // https://github.github.com/gfm/#fenced-code-blocks 107 $gitHubMarkdownPattern = "^\s*(?:\`|~){3}\w*\s*$(?=.*?" . self::MARKDOWN_EXIT_PATTERN . ")"; 108 $this->Lexer->addEntryPattern($gitHubMarkdownPattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 109 110 111 } 112 113 114 function postConnect() 115 { 116 117 118 $this->Lexer->addExitPattern(self::MARKDOWN_EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 119 120 121 } 122 123 /** 124 * 125 * The handle function goal is to parse the matched syntax through the pattern function 126 * and to return the result for use in the renderer 127 * This result is always cached until the page is modified. 128 * @param string $match 129 * @param int $state 130 * @param int $pos - byte position in the original source file 131 * @param Doku_Handler $handler 132 * @return array|bool 133 * @see DokuWiki_Syntax_Plugin::handle() 134 * 135 */ 136 function handle($match, $state, $pos, Doku_Handler $handler) 137 { 138 139 switch ($state) { 140 141 case DOKU_LEXER_ENTER: 142 $trimmedMatch = trim($match); 143 $language = preg_replace("(`{3}|~{3})", "", $trimmedMatch); 144 145 $attributes = [TagAttributes::TYPE_KEY => $language]; 146 return array( 147 PluginUtility::STATE => $state, 148 PluginUtility::ATTRIBUTES => $attributes 149 ); 150 151 case DOKU_LEXER_UNMATCHED : 152 153 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 154 155 156 case DOKU_LEXER_EXIT : 157 158 return array(PluginUtility::STATE => $state); 159 160 161 } 162 return array(); 163 164 } 165 166 /** 167 * Render the output 168 * @param string $format 169 * @param Doku_Renderer $renderer 170 * @param array $data - what the function handle() return'ed 171 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 172 * @see DokuWiki_Syntax_Plugin::render() 173 * 174 * 175 */ 176 function render($format, Doku_Renderer $renderer, $data) 177 { 178 179 180 if ($format == 'xhtml') { 181 182 /** @var $renderer Doku_Renderer_xhtml */ 183 $state = $data [PluginUtility::STATE]; 184 switch ($state) { 185 case DOKU_LEXER_ENTER : 186 $attributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], syntax_plugin_combo_code::CODE_TAG); 187 Prism::htmlEnter($renderer, $this, $attributes); 188 break; 189 190 case DOKU_LEXER_UNMATCHED : 191 192 // Delete the eol at the beginning and end 193 // otherwise we get a big block 194 $payload = trim($data[PluginUtility::PAYLOAD], "\n\r"); 195 $renderer->doc .= Html::encode($payload); 196 break; 197 198 case DOKU_LEXER_EXIT : 199 200 Prism::htmlExit($renderer); 201 break; 202 203 } 204 return true; 205 } else if ($format == 'code') { 206 207 /** 208 * The renderer to download the code 209 * @var Doku_Renderer_code $renderer 210 */ 211 $state = $data [PluginUtility::STATE]; 212 if ($state == DOKU_LEXER_UNMATCHED) { 213 214 $attributes = $data[PluginUtility::ATTRIBUTES]; 215 $text = $data[PluginUtility::PAYLOAD]; 216 $language = strtolower($attributes["type"]); 217 $filename = $language; 218 $renderer->code($text, $language, $filename); 219 220 } 221 } 222 223 // unsupported $mode 224 return false; 225 226 } 227 228 229} 230 231