1<?php 2 3 4require_once(__DIR__ . "/../ComboStrap/PluginUtility.php"); 5 6 7use ComboStrap\Call; 8use ComboStrap\CallStack; 9use ComboStrap\LogUtility; 10use ComboStrap\PluginUtility; 11use ComboStrap\TagAttributes; 12 13 14/** 15 * 16 * A paragraph syntax 17 * 18 * This syntax component is used dynamically while parsing (at the {@link DOKU_LEXER_END} of {@link \dokuwiki\Extension\SyntaxPlugin::handle()} 19 * with the function {@link \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack()} 20 * 21 * 22 * !!!!! 23 * 24 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol} 25 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()} 26 * 27 * Note: p_open call may appears when the {@link \ComboStrap\Syntax::getPType()} is set to `block` or `stack` 28 * and the next call is not a block or a stack 29 * 30 * !!!!! 31 * 32 * 33 * Note on Typography 34 * TODO: https://github.com/typekit/webfontloader 35 * https://www.dokuwiki.org/plugin:typography 36 * https://stackoverflow.design/email/base/typography/ 37 * http://kyleamathews.github.io/typography.js/ 38 * https://docs.gitbook.com/features/advanced-branding (Roboto, Roboto Slab, Open Sans, Source Sans Pro, Lato, Ubuntu, Raleway, Merriweather) 39 * http://themenectar.com/docs/salient/theme-options/typography/ (Doc) 40 * https://www.modularscale.com/ - see the size of font 41 * 42 * See the fonts on your computer 43 * https://wordmark.it/ 44 * 45 * What's a type ? Type terminology 46 * https://www.supremo.co.uk/typeterms/ 47 * 48 * https://theprotoolbox.com/browse/font-tools/ 49 */ 50class syntax_plugin_combo_para extends DokuWiki_Syntax_Plugin 51{ 52 53 const TAG = 'para'; 54 const COMPONENT = "combo_para"; 55 56 57 /** 58 * The component that have a `normal` {@link \dokuwiki\Extension\SyntaxPlugin::getPType()} 59 * are wrapped in a p element by {@link Block::process()} if they are not in a component with a `block` ptype 60 * 61 * This function makes it easy for the test 62 * to do it and gives a little bit of documentation 63 * on why there is a `p` in the test 64 * @param $html 65 * @return string the html wrapped in p 66 */ 67 public static function wrapInP($html) 68 { 69 return "<p>" . $html . "</p>"; 70 } 71 72 73 /** 74 * Syntax Type. 75 * 76 * Needs to return one of the mode types defined in $PARSER_MODES in parser.php 77 * @see https://www.dokuwiki.org/devel:syntax_plugins#syntax_types 78 */ 79 function getType(): string 80 { 81 /** 82 * Not `paragraphs' because we don't allow them in {@link syntax_plugin_combo_xmlblocktag} 83 */ 84 return 'formatting'; 85 } 86 87 /** 88 * How Dokuwiki will add P element 89 * 90 * * 'normal' - The plugin can be used inside paragraphs 91 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 92 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 93 * 94 * @see DokuWiki_Syntax_Plugin::getPType() 95 */ 96 function getPType() 97 { 98 /** 99 * !important! 100 * The {@link \dokuwiki\Parsing\Handler\Block::process()} 101 * will then not create an extra paragraph after it encounters a block 102 */ 103 return 'block'; 104 } 105 106 /** 107 * @return array 108 * Allow which kind of plugin inside 109 * 110 * No one of array('container', 'baseonly', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 111 * because we manage self the content and we call self the parser 112 */ 113 function getAllowedTypes() 114 { 115 /** 116 * Not needed as we don't have any {@link syntax_plugin_combo_para::connectTo()} 117 */ 118 return array(); 119 } 120 121 122 /** 123 * @see Doku_Parser_Mode::getSort() 124 * The mode with the lowest sort number will win out 125 * 126 */ 127 function getSort() 128 { 129 /** 130 * Not really needed as we don't have any {@link syntax_plugin_combo_para::connectTo()} 131 * 132 * Note: if we start to use it should be less than 370 133 * Ie Less than {@link \dokuwiki\Parsing\ParserMode\Eol::getSort()} 134 */ 135 return 369; 136 } 137 138 139 function connectTo($mode) 140 { 141 142 /** 143 * No need to connect 144 * This syntax plugin is added dynamically with the {@link CallStack::processEolToEndStack()} 145 * function 146 */ 147 148 } 149 150 151 /** 152 * The handler for an internal link 153 * based on `internallink` in {@link Doku_Handler} 154 * The handler call the good renderer in {@link Doku_Renderer_xhtml} with 155 * the parameters (ie for instance internallink) 156 * @param string $match 157 * @param int $state 158 * @param int $pos 159 * @param Doku_Handler $handler 160 * @return array|bool 161 */ 162 function handle($match, $state, $pos, Doku_Handler $handler) 163 { 164 165 /** 166 * No need to handle, 167 * there is no {@link syntax_plugin_combo_para::connectTo() connection} 168 */ 169 return true; 170 171 172 } 173 174 /** 175 * Render the output 176 * @param string $format 177 * @param Doku_Renderer $renderer 178 * @param array $data - what the function handle() return'ed 179 * @return boolean - rendered correctly? (however, returned value is not used at the moment) 180 * @see DokuWiki_Syntax_Plugin::render() 181 * 182 * 183 */ 184 function render($format, Doku_Renderer $renderer, $data): bool 185 { 186 // The data 187 switch ($format) { 188 case 'xhtml': 189 190 /** @var Doku_Renderer_xhtml $renderer */ 191 $state = $data[PluginUtility::STATE]; 192 193 switch ($state) { 194 case DOKU_LEXER_ENTER: 195 $attributes = $data[PluginUtility::ATTRIBUTES]; 196 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 197 if ($tagAttributes->hasComponentAttribute(TagAttributes::TYPE_KEY)) { 198 $class = $tagAttributes->getType(); 199 $tagAttributes->addClassName($class); 200 } 201 $renderer->doc .= $tagAttributes->toHtmlEnterTag("p"); 202 break; 203 case DOKU_LEXER_SPECIAL: 204 $attributes = $data[PluginUtility::ATTRIBUTES]; 205 $tagAttributes = TagAttributes::createFromCallStackArray($attributes); 206 $renderer->doc .= $tagAttributes->toHtmlEnterTag("p"); 207 $renderer->doc .= "</p>"; 208 break; 209 case DOKU_LEXER_EXIT: 210 $renderer->doc .= "</p>"; 211 break; 212 } 213 return true; 214 215 case 'metadata': 216 217 /** @var Doku_Renderer_metadata $renderer */ 218 219 220 return true; 221 222 223 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 224 225 /** 226 * @var renderer_plugin_combo_analytics $renderer 227 */ 228 return true; 229 230 } 231 // unsupported $mode 232 return false; 233 } 234 235 /** 236 * 237 * Transform EOL into paragraph 238 * between the {@link CallStack::getActualCall()} and the {@link CallStack::isPointerAtEnd()} 239 * of the stack 240 * 241 * Info: Basically, you get an new paragraph with a blank line or `\\` : https://www.dokuwiki.org/faq:newlines 242 * 243 * It replaces the {@link \dokuwiki\Parsing\Handler\Block::process() eol to paragraph Dokuwiki process} 244 * that takes place at the end of parsing process on the whole stack 245 * 246 * Info: The `eol` call are temporary created with {@link \dokuwiki\Parsing\ParserMode\Eol} 247 * and transformed to `p_open` and `p_close` via {@link \dokuwiki\Parsing\Handler\Block::process()} 248 * 249 * @param CallStack $callstack 250 * @param array $attributes - the attributes passed to the paragraph 251 */ 252 public static function fromEolToParagraphUntilEndOfStack(CallStack &$callstack, array $attributes) 253 { 254 255 if (!is_array($attributes)) { 256 LogUtility::msg("The passed attributes array ($attributes) for the creation of the paragraph is not an array", LogUtility::LVL_MSG_ERROR); 257 $attributes = []; 258 } 259 260 /** 261 * The syntax plugin that implements the paragraph 262 * ie {@link \syntax_plugin_combo_para} 263 * We will transform the eol with a call to this syntax plugin 264 * to create the paragraph 265 */ 266 $paragraphComponent = \syntax_plugin_combo_para::COMPONENT; 267 $paragraphTag = \syntax_plugin_combo_para::TAG; 268 269 /** 270 * The running variables 271 */ 272 $paragraphIsOpen = false; // A pointer to see if the paragraph is open 273 while ($actualCall = $callstack->next()) { 274 275 /** 276 * end of line is not always present 277 * because the pattern is eating it 278 * Example (list_open) 279 */ 280 if ($paragraphIsOpen && $actualCall->getTagName() !== "eol") { 281 if ($actualCall->getDisplay() == Call::BlOCK_DISPLAY) { 282 $paragraphIsOpen = false; 283 $callstack->insertBefore( 284 Call::createComboCall( 285 $paragraphTag, 286 DOKU_LEXER_EXIT, 287 $attributes 288 ) 289 ); 290 } 291 } 292 293 if ($actualCall->getTagName() === "eol") { 294 295 /** 296 * Next Call that is not the empty string 297 * Because Empty string would create an empty paragraph 298 * 299 * Start at 1 because we may not do 300 * a loop if we are at the end, the next call 301 * will return false 302 */ 303 $i = 1; 304 while ($nextCall = $callstack->next()) { 305 $capturedContent = $nextCall->getCapturedContent(); 306 $captureContentisEmpty = $capturedContent===null || trim($capturedContent) == ""; 307 if (!( 308 $captureContentisEmpty && 309 $nextCall->isTextCall() 310 )) { 311 break; 312 } 313 $i++; 314 } 315 while ($i > 0) { // go back 316 $i--; 317 $callstack->previous(); 318 } 319 if ($nextCall === false) { 320 $nextDisplay = "last"; 321 $nextCall = null; 322 } else { 323 $nextDisplay = $nextCall->getDisplay(); 324 } 325 326 327 /** 328 * Processing 329 */ 330 if (!$paragraphIsOpen) { 331 332 switch ($nextDisplay) { 333 case Call::BlOCK_DISPLAY: 334 case "last": 335 $callstack->deleteActualCallAndPrevious(); 336 break; 337 case Call::INLINE_DISPLAY: 338 $paragraphIsOpen = true; 339 $actualCall->updateToPluginComponent( 340 $paragraphComponent, 341 DOKU_LEXER_ENTER, 342 $attributes 343 ); 344 break; 345 case "eol": 346 /** 347 * Empty line 348 */ 349 $actualCall->updateToPluginComponent( 350 $paragraphComponent, 351 DOKU_LEXER_ENTER, 352 $attributes 353 ); 354 $nextCall->updateToPluginComponent( 355 $paragraphComponent, 356 DOKU_LEXER_EXIT 357 ); 358 $callstack->next(); 359 break; 360 default: 361 LogUtility::msg("The eol action for the combination enter / (" . $nextDisplay . ") of the call ( $nextCall ) was not implemented", LogUtility::LVL_MSG_ERROR); 362 break; 363 } 364 } else { 365 /** 366 * Paragraph is open 367 */ 368 switch ($nextDisplay) { 369 case "eol": 370 /** 371 * Empty line 372 */ 373 $actualCall->updateToPluginComponent( 374 $paragraphComponent, 375 DOKU_LEXER_EXIT 376 ); 377 $nextCall->updateToPluginComponent( 378 $paragraphComponent, 379 DOKU_LEXER_ENTER, 380 $attributes 381 ); 382 $callstack->next(); 383 break; 384 case Call::INLINE_DISPLAY: 385 // A space 386 $actualCall->updateEolToSpace(); 387 break; 388 case Call::BlOCK_DISPLAY: 389 case "last"; 390 $actualCall->updateToPluginComponent( 391 $paragraphComponent, 392 DOKU_LEXER_EXIT 393 ); 394 $paragraphIsOpen = false; 395 break; 396 default: 397 LogUtility::msg("The display for a open paragraph (" . $nextDisplay . ") is not implemented", LogUtility::LVL_MSG_ERROR); 398 break; 399 } 400 } 401 402 } 403 } 404 405 // if the paragraph is open close it 406 if ($paragraphIsOpen) { 407 $callstack->insertBefore( 408 Call::createComboCall( 409 \syntax_plugin_combo_para::TAG, 410 DOKU_LEXER_EXIT 411 ) 412 ); 413 } 414 } 415 416} 417 418