1<?php 2 3 4use ComboStrap\CallStack; 5use ComboStrap\PluginUtility; 6use ComboStrap\Prism; 7use ComboStrap\TagAttributes; 8 9require_once(__DIR__ . '/../ComboStrap/PluginUtility.php'); 10 11/** 12 * Overwrite {@link \dokuwiki\Parsing\ParserMode\Preformatted} 13 */ 14if (!defined('DOKU_INC')) die(); 15 16/** 17 * 18 * Preformatted shows a block of text as code via space at the beginning of the line 19 * 20 * It is the same as <a href="https://github.github.com/gfm/#indented-code-blocks">indented-code-blocks</a> 21 * but with 2 characters in place of 4 22 * 23 * This component is used to: 24 * * showcase preformatted as {@link \ComboStrap\Prism} component 25 * * disable preformatted mode via the function {@link syntax_plugin_combo_preformatted::disablePreformatted()} 26 * used in other HTML super set syntax component to disable this behavior 27 * 28 * It's also the original markdown specification 29 * 30 */ 31class syntax_plugin_combo_preformatted extends DokuWiki_Syntax_Plugin 32{ 33 34 const TAG = 'preformatted'; 35 36 37 const CONF_PREFORMATTED_ENABLE = "preformattedEnable"; 38 /** 39 * The content is not printed when the content of a preformatted block is empty 40 */ 41 const CONF_PREFORMATTED_EMPTY_CONTENT_NOT_PRINTED_ENABLE = "preformattedEmptyContentNotPrintedEnable"; 42 43 const HAS_EMPTY_CONTENT = "hasEmptyContent"; 44 45 /** 46 * Syntax Type. 47 * 48 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 49 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 50 * @see DokuWiki_Syntax_Plugin::getType() 51 */ 52 function getType() 53 { 54 return 'formatting'; 55 } 56 57 /** 58 * How DokuWiki will add P element 59 * 60 * * 'normal' - The plugin can be used inside paragraphs 61 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 62 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 63 * 64 * @see DokuWiki_Syntax_Plugin::getPType() 65 */ 66 function getPType() 67 { 68 return 'block'; 69 } 70 71 72 /** 73 * @return array 74 * Allow which kind of plugin inside 75 * 76 * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 77 * because we manage self the content and we call self the parser 78 * 79 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 80 */ 81 function getAllowedTypes() 82 { 83 return array(); 84 } 85 86 function getSort() 87 { 88 /** 89 * Should be less than the preformatted mode 90 * which is 20 91 * From {@link \dokuwiki\Parsing\ParserMode\Preformatted::getSort()} 92 **/ 93 return 19; 94 } 95 96 97 function connectTo($mode) 98 { 99 100 if ($this->getConf(self::CONF_PREFORMATTED_ENABLE, 1)) { 101 102 /** 103 * From {@link \dokuwiki\Parsing\ParserMode\Preformatted} 104 */ 105 $patterns = array('\n (?![\*\-])', '\n\t(?![\*\-])'); 106 foreach ($patterns as $pattern) { 107 $this->Lexer->addEntryPattern($pattern, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 108 } 109 $this->Lexer->addPattern('\n ', PluginUtility::getModeFromTag($this->getPluginComponent())); 110 $this->Lexer->addPattern('\n\t', PluginUtility::getModeFromTag($this->getPluginComponent())); 111 112 } 113 114 } 115 116 117 function postConnect() 118 { 119 /** 120 * From {@link \dokuwiki\Parsing\ParserMode\Preformatted} 121 */ 122 $this->Lexer->addExitPattern('\n', PluginUtility::getModeFromTag($this->getPluginComponent())); 123 124 } 125 126 /** 127 * 128 * The handle function goal is to parse the matched syntax through the pattern function 129 * and to return the result for use in the renderer 130 * This result is always cached until the page is modified. 131 * @param string $match 132 * @param int $state 133 * @param int $pos - byte position in the original source file 134 * @param Doku_Handler $handler 135 * @return array|bool 136 * @see DokuWiki_Syntax_Plugin::handle() 137 * 138 */ 139 function handle($match, $state, $pos, Doku_Handler $handler) 140 { 141 142 switch ($state) { 143 case DOKU_LEXER_ENTER: 144 /** 145 * used at the {@link DOKU_LEXER_EXIT} state 146 * to add the {@link syntax_plugin_combo_preformatted::HAS_EMPTY_CONTENT} 147 * flag 148 */ 149 $attributes = []; 150 return array( 151 PluginUtility::STATE => $state, 152 PluginUtility::ATTRIBUTES => $attributes 153 ); 154 case DOKU_LEXER_MATCHED: 155 return array( 156 PluginUtility::STATE => $state 157 ); 158 case DOKU_LEXER_UNMATCHED: 159 return array( 160 PluginUtility::STATE => $state, 161 PluginUtility::PAYLOAD => $match 162 ); 163 case DOKU_LEXER_EXIT: 164 $callStack = CallStack::createFromHandler($handler); 165 $openingCall = $callStack->moveToPreviousCorrespondingOpeningCall(); 166 $text = ""; 167 while ($callStack->next()) { 168 $actualCall = $callStack->getActualCall(); 169 if ($actualCall->getState() == DOKU_LEXER_UNMATCHED && $actualCall->getTagName() == self::TAG) { 170 $text .= $actualCall->getPayload() . "\n"; 171 $callStack->deleteActualCallAndPrevious(); 172 } 173 } 174 if (trim($text) == "") { 175 $openingCall->addAttribute(self::HAS_EMPTY_CONTENT, true); 176 } 177 return array( 178 PluginUtility::STATE => $state, 179 PluginUtility::PAYLOAD => $text 180 ); 181 } 182 return array(); 183 184 } 185 186 /** 187 * Render the output 188 * @param string $format 189 * @param Doku_Renderer $renderer 190 * @param array $data - what the function handle() return'ed 191 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 192 * @see DokuWiki_Syntax_Plugin::render() 193 * 194 * 195 */ 196 function render($format, Doku_Renderer $renderer, $data) 197 { 198 if ($format == "xhtml") { 199 /** 200 * @var Doku_Renderer_xhtml $renderer 201 */ 202 $state = $data[PluginUtility::STATE]; 203 $emptyContentShouldBeDeleted = $this->getConf(self::CONF_PREFORMATTED_EMPTY_CONTENT_NOT_PRINTED_ENABLE, 1); 204 switch ($state) { 205 case DOKU_LEXER_ENTER: 206 $tagAttributes = TagAttributes::createFromCallStackArray($data[PluginUtility::ATTRIBUTES], self::TAG); 207 $hasEmptyContent = $tagAttributes->getValueAndRemove(self::HAS_EMPTY_CONTENT,0); 208 if (!($hasEmptyContent && $emptyContentShouldBeDeleted)) { 209 Prism::htmlEnter($renderer, $this, $tagAttributes); 210 } 211 break; 212 case DOKU_LEXER_EXIT: 213 // Delete the eol at the beginning and end 214 // otherwise we get a big block 215 $text = trim($data[PluginUtility::PAYLOAD], "\n\r"); 216 if (!(trim($text) == "" && $emptyContentShouldBeDeleted)) { 217 218 $renderer->doc .= PluginUtility::htmlEncode($text); 219 Prism::htmlExit($renderer); 220 } 221 break; 222 } 223 } 224 return false; 225 } 226 227 /** 228 * Utility function to disable preformatted formatting 229 * in a HTML super set element 230 * 231 * @param $mode 232 * @return bool 233 */ 234 public 235 static function disablePreformatted($mode) 236 { 237 /** 238 * Disable {@link \dokuwiki\Parsing\ParserMode\Preformatted} 239 * and this syntax 240 */ 241 if ( 242 $mode == 'preformatted' 243 || 244 $mode == PluginUtility::getModeFromTag(syntax_plugin_combo_preformatted::TAG) 245 ) { 246 return false; 247 } else { 248 return true; 249 } 250 } 251 252} 253 254