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