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