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