1<?php 2 3use ComboStrap\CallStack; 4use ComboStrap\LogUtility; 5use ComboStrap\PluginUtility; 6use ComboStrap\TagAttributes; 7 8 9/** 10 * Class headingwiki 11 * Taking over {@link \dokuwiki\Parsing\ParserMode\Header} 12 */ 13class syntax_plugin_combo_headingwiki extends DokuWiki_Syntax_Plugin 14{ 15 16 /** 17 * Header pattern 18 * * Dokuwiki does not made a space mandatory after and before the opening an closing `=` character 19 * * No line break in the look ahead 20 * * The capture of the first spaces should be optional otherwise the {@link \dokuwiki\Parsing\ParserMode\Header} is taking over 21 * 22 * See also for information, 23 * the original heading pattern of Dokuwiki {@link \dokuwiki\Parsing\ParserMode\Header} 24 */ 25 const ENTRY_PATTERN = '[ \t]*={1,6}(?=[^\n]*={1,6}\s*\r??\n)'; 26 const EXIT_PATTERN = '={1,6}\s*(?=\r??\n)'; 27 const TAG = "headingwiki"; 28 29 const CONF_WIKI_HEADING_ENABLE = "headingWikiEnable"; 30 const CONF_DEFAULT_WIKI_ENABLE_VALUE = 1; 31 32 public function getSort(): int 33 { 34 /** 35 * It's 49 (on less than the original heading) 36 * {@link \dokuwiki\Parsing\ParserMode\Header::getSort()} 37 */ 38 return 49; 39 } 40 41 public function getType(): string 42 { 43 return syntax_plugin_combo_heading::SYNTAX_TYPE; 44 } 45 46 47 /** 48 * 49 * How Dokuwiki will add P element 50 * 51 * * 'normal' - The plugin can be used inside paragraphs (inline) 52 * * 'block' - Open paragraphs need to be closed before plugin output - block should not be inside paragraphs 53 * * 'stack' - Special case. Plugin wraps other paragraphs. - Stacks can contain paragraphs 54 * 55 * @see DokuWiki_Syntax_Plugin::getPType() 56 * 57 * This is the equivalent of inline or block for css 58 */ 59 public function getPType(): string 60 { 61 return syntax_plugin_combo_heading::SYNTAX_PTYPE; 62 } 63 64 /** 65 * @return array 66 * Allow which kind of plugin inside 67 * 68 * No one of array('baseonly','container', 'formatting', 'substition', 'protected', 'disabled', 'paragraphs') 69 * because we manage self the content and we call self the parser 70 * 71 * Return an array of one or more of the mode types {@link $PARSER_MODES} in Parser.php 72 */ 73 function getAllowedTypes(): array 74 { 75 return array('formatting', 'substition', 'protected', 'disabled'); 76 } 77 78 79 public function connectTo($mode) 80 { 81 if ($this->enableWikiHeading($mode)) { 82 $this->Lexer->addEntryPattern(self::ENTRY_PATTERN, $mode, PluginUtility::getModeFromTag($this->getPluginComponent())); 83 } 84 } 85 86 public function postConnect() 87 { 88 89 $this->Lexer->addExitPattern(self::EXIT_PATTERN, PluginUtility::getModeFromTag($this->getPluginComponent())); 90 91 } 92 93 94 /** 95 * Handle the syntax 96 * 97 * At the end of the parser, the `section_open` and `section_close` calls 98 * are created in {@link action_plugin_combo_headingpostprocessing} 99 * and the text inside for the toc is captured 100 * 101 * @param string $match 102 * @param int $state 103 * @param int $pos 104 * @param Doku_Handler $handler 105 * @return array 106 */ 107 public function handle($match, $state, $pos, Doku_Handler $handler): array 108 { 109 switch ($state) { 110 111 case DOKU_LEXER_ENTER: 112 /** 113 * Title regexp 114 */ 115 $attributes[syntax_plugin_combo_heading::LEVEL] = $this->getLevelFromMatch($match); 116 $callStack = CallStack::createFromHandler($handler); 117 118 $context = syntax_plugin_combo_heading::getContext($callStack); 119 120 return array( 121 PluginUtility::STATE => $state, 122 PluginUtility::ATTRIBUTES => $attributes, 123 PluginUtility::CONTEXT => $context, 124 PluginUtility::POSITION => $pos 125 ); 126 case DOKU_LEXER_UNMATCHED : 127 128 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 129 130 case DOKU_LEXER_EXIT : 131 132 $callStack = CallStack::createFromHandler($handler); 133 134 $returnedData = syntax_plugin_combo_heading::handleExit($callStack); 135 136 137 /** 138 * Control of the Number of `=` before and after 139 */ 140 $callStack->moveToEnd(); 141 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 142 $levelFromMatch = $this->getLevelFromMatch($match); 143 $levelFromStartTag = $openingTag->getAttribute(syntax_plugin_combo_heading::LEVEL); 144 if ($levelFromMatch != $levelFromStartTag) { 145 $content = ""; 146 while ($actualCall = $callStack->next()) { 147 $content .= $actualCall->getCapturedContent(); 148 } 149 LogUtility::msg("The number of `=` character for a wiki heading is not the same before ($levelFromStartTag) and after ($levelFromMatch) the content ($content).", LogUtility::LVL_MSG_INFO, syntax_plugin_combo_heading::CANONICAL); 150 } 151 152 return $returnedData; 153 154 } 155 return array(); 156 } 157 158 public function render($format, Doku_Renderer $renderer, $data): bool 159 { 160 161 if ($format == "xhtml") { 162 /** 163 * @var Doku_Renderer_xhtml $renderer 164 */ 165 $state = $data[PluginUtility::STATE]; 166 switch ($state) { 167 168 case DOKU_LEXER_ENTER: 169 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 170 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray, syntax_plugin_combo_heading::TAG); 171 $context = $data[PluginUtility::CONTEXT]; 172 $pos = $data[PluginUtility::POSITION]; 173 syntax_plugin_combo_heading::renderOpeningTag($context, $tagAttributes, $renderer, $pos); 174 return true; 175 case DOKU_LEXER_UNMATCHED: 176 $renderer->doc .= PluginUtility::renderUnmatched($data); 177 return true; 178 case DOKU_LEXER_EXIT: 179 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 180 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 181 $renderer->doc .= syntax_plugin_combo_heading::renderClosingTag($tagAttributes); 182 return true; 183 184 } 185 } else if ($format == renderer_plugin_combo_analytics::RENDERER_FORMAT) { 186 187 /** 188 * @var renderer_plugin_combo_analytics $renderer 189 */ 190 syntax_plugin_combo_heading::processMetadataAnalytics($data, $renderer); 191 192 } else if ($format == "metadata") { 193 194 /** 195 * @var Doku_Renderer_metadata $renderer 196 */ 197 syntax_plugin_combo_heading::processHeadingMetadata($data, $renderer); 198 199 } else if ($format == renderer_plugin_combo_xml::FORMAT) { 200 $state = $data[PluginUtility::STATE]; 201 switch ($state) { 202 case DOKU_LEXER_ENTER: 203 $level = $data[PluginUtility::ATTRIBUTES][syntax_plugin_combo_heading::LEVEL]; 204 $renderer->doc .= "<h$level>"; 205 break; 206 case DOKU_LEXER_UNMATCHED: 207 $renderer->doc .= PluginUtility::renderUnmatchedXml($data); 208 break; 209 case DOKU_LEXER_EXIT: 210 $level = $data[PluginUtility::ATTRIBUTES][syntax_plugin_combo_heading::LEVEL]; 211 $renderer->doc .= "</h$level>"; 212 break; 213 214 } 215 216 } 217 218 return false; 219 } 220 221 /** 222 * @param $match 223 * @return int 224 */ 225 public 226 function getLevelFromMatch($match) 227 { 228 return 7 - strlen(trim($match)); 229 } 230 231 232 private 233 function enableWikiHeading($mode) 234 { 235 236 237 /** 238 * Basically all mode that are not `base` 239 * To not take the dokuwiki heading 240 */ 241 if (!(in_array($mode, ['base', 'header', 'table']))) { 242 return true; 243 } else { 244 return PluginUtility::getConfValue(self::CONF_WIKI_HEADING_ENABLE, self::CONF_DEFAULT_WIKI_ENABLE_VALUE); 245 } 246 247 248 } 249 250 251} 252