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\PluginUtility; 8use ComboStrap\Prism; 9use ComboStrap\Tag; 10use ComboStrap\TagAttributes; 11 12require_once(__DIR__ . '/../class/StringUtility.php'); 13require_once(__DIR__ . '/../class/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 function getSort() 82 { 83 84 return 199; 85 } 86 87 88 function connectTo($mode) 89 { 90 91 // 92 // This pattern works with mgs flag 93 // 94 // Match: 95 // * the start of a line 96 // * any consecutive blank character 97 // * 3 consecutive backtick characters (`) or tildes (~) 98 // * a word (info string - ie the language) 99 // * any consecutive blank character 100 // * the end of a line 101 // * a look ahead to see if we have the exit pattern 102 // https://github.github.com/gfm/#fenced-code-blocks 103 $gitHubMarkdownPattern = "^\s*(?:\`|~){3}\w*\s*$(?=.*?" . self::MARKDOWN_EXIT_PATTERN . ")"; 104 $this->Lexer->addEntryPattern($gitHubMarkdownPattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 105 106 107 } 108 109 110 function postConnect() 111 { 112 113 114 $this->Lexer->addExitPattern(self::MARKDOWN_EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 115 116 117 } 118 119 /** 120 * 121 * The handle function goal is to parse the matched syntax through the pattern function 122 * and to return the result for use in the renderer 123 * This result is always cached until the page is modified. 124 * @param string $match 125 * @param int $state 126 * @param int $pos - byte position in the original source file 127 * @param Doku_Handler $handler 128 * @return array|bool 129 * @see DokuWiki_Syntax_Plugin::handle() 130 * 131 */ 132 function handle($match, $state, $pos, Doku_Handler $handler) 133 { 134 135 switch ($state) { 136 137 case DOKU_LEXER_ENTER: 138 $trimmedMatch = trim($match); 139 $language = preg_replace("(`{3}|~{3})", "", $trimmedMatch); 140 141 $attributes = [TagAttributes::TYPE_KEY => $language]; 142 return array( 143 PluginUtility::STATE => $state, 144 PluginUtility::ATTRIBUTES => $attributes 145 ); 146 147 case DOKU_LEXER_UNMATCHED : 148 149 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 150 151 152 case DOKU_LEXER_EXIT : 153 154 return array(PluginUtility::STATE => $state); 155 156 157 } 158 return array(); 159 160 } 161 162 /** 163 * Render the output 164 * @param string $format 165 * @param Doku_Renderer $renderer 166 * @param array $data - what the function handle() return'ed 167 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 168 * @see DokuWiki_Syntax_Plugin::render() 169 * 170 * 171 */ 172 function render($format, Doku_Renderer $renderer, $data) 173 { 174 175 176 if ($format == 'xhtml') { 177 178 /** @var Doku_Renderer_xhtml $renderer */ 179 $state = $data [PluginUtility::STATE]; 180 switch ($state) { 181 case DOKU_LEXER_ENTER : 182 $attributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], syntax_plugin_combo_code::CODE_TAG); 183 Prism::htmlEnter($renderer, $this, $attributes); 184 break; 185 186 case DOKU_LEXER_UNMATCHED : 187 188 // Delete the eol at the beginning and end 189 // otherwise we get a big block 190 $payload = trim($data[PluginUtility::PAYLOAD], "\n\r"); 191 $renderer->doc .= PluginUtility::htmlEncode($payload); 192 break; 193 194 case DOKU_LEXER_EXIT : 195 196 Prism::htmlExit($renderer); 197 break; 198 199 } 200 return true; 201 } else if ($format == 'code') { 202 203 /** 204 * The renderer to download the code 205 * @var Doku_Renderer_code $renderer 206 */ 207 $state = $data [PluginUtility::STATE]; 208 if ($state == DOKU_LEXER_UNMATCHED) { 209 210 $attributes = $data[PluginUtility::ATTRIBUTES]; 211 $text = $data[PluginUtility::PAYLOAD]; 212 $language = strtolower($attributes["type"]); 213 $filename = $language; 214 $renderer->code($text, $language, $filename); 215 216 } 217 } 218 219 // unsupported $mode 220 return false; 221 222 } 223 224 225} 226 227