1<?php 2 3use ComboStrap\CallStack; 4use ComboStrap\HeadingTag; 5use ComboStrap\LogUtility; 6use ComboStrap\PluginUtility; 7use ComboStrap\Site; 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 $attributes = TagAttributes::createEmpty(self::TAG) 133 ->addComponentAttributeValue(HeadingTag::LEVEL,$level) 134 ->toCallStackArray(); 135 136 $callStack = CallStack::createFromHandler($handler); 137 $context = HeadingTag::getContext($callStack); 138 139 return array( 140 PluginUtility::STATE => $state, 141 PluginUtility::ATTRIBUTES => $attributes, 142 PluginUtility::CONTEXT => $context, 143 PluginUtility::POSITION => $pos 144 ); 145 case DOKU_LEXER_UNMATCHED : 146 147 return PluginUtility::handleAndReturnUnmatchedData(self::TAG, $match, $handler); 148 149 case DOKU_LEXER_EXIT : 150 151 $returnedData = HeadingTag::handleExit($handler); 152 153 /** 154 * Control of the Number of `=` before and after 155 */ 156 $callStack = CallStack::createFromHandler($handler); 157 $callStack->moveToEnd(); 158 $openingTag = $callStack->moveToPreviousCorrespondingOpeningCall(); 159 $levelFromMatch = $this->getLevelFromMatch($match); 160 $levelFromStartTag = $openingTag->getAttribute(HeadingTag::LEVEL); 161 if ($levelFromMatch != $levelFromStartTag) { 162 $content = ""; 163 while ($actualCall = $callStack->next()) { 164 $content .= $actualCall->getCapturedContent(); 165 } 166 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); 167 } 168 169 return $returnedData; 170 171 } 172 return array(); 173 } 174 175 public function render($format, Doku_Renderer $renderer, $data): bool 176 { 177 178 switch ($format) { 179 case "xhtml": 180 /** 181 * @var Doku_Renderer_xhtml $renderer 182 */ 183 $state = $data[PluginUtility::STATE]; 184 $context = $data[PluginUtility::CONTEXT]; 185 switch ($state) { 186 187 case DOKU_LEXER_ENTER: 188 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 189 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray, HeadingTag::HEADING_TAG); 190 $pos = $data[PluginUtility::POSITION]; 191 HeadingTag::processRenderEnterXhtml($context, $tagAttributes, $renderer, $pos); 192 return true; 193 case DOKU_LEXER_UNMATCHED: 194 $renderer->doc .= PluginUtility::renderUnmatched($data); 195 return true; 196 case DOKU_LEXER_EXIT: 197 $callStackArray = $data[PluginUtility::ATTRIBUTES]; 198 $tagAttributes = TagAttributes::createFromCallStackArray($callStackArray); 199 $renderer->doc .= HeadingTag::renderClosingTag($tagAttributes, $context); 200 return true; 201 202 } 203 return false; 204 case renderer_plugin_combo_analytics::RENDERER_FORMAT: 205 206 /** 207 * @var renderer_plugin_combo_analytics $renderer 208 */ 209 HeadingTag::processMetadataAnalytics($data, $renderer); 210 return true; 211 212 case "metadata": 213 214 /** 215 * @var Doku_Renderer_metadata $renderer 216 */ 217 HeadingTag::processHeadingEnterMetadata($data, $renderer); 218 return true; 219 220 case renderer_plugin_combo_xml::FORMAT: 221 $state = $data[PluginUtility::STATE]; 222 switch ($state) { 223 case DOKU_LEXER_ENTER: 224 $level = $data[PluginUtility::ATTRIBUTES][HeadingTag::LEVEL]; 225 $renderer->doc .= "<h$level>"; 226 return true; 227 case DOKU_LEXER_UNMATCHED: 228 $renderer->doc .= PluginUtility::renderUnmatchedXml($data); 229 return true; 230 case DOKU_LEXER_EXIT: 231 $level = $data[PluginUtility::ATTRIBUTES][HeadingTag::LEVEL]; 232 $renderer->doc .= "</h$level>"; 233 return true; 234 235 } 236 return false; 237 default: 238 return false; 239 } 240 241 } 242 243 /** 244 * @param $match 245 * @return int 246 */ 247 public 248 function getLevelFromMatch($match): int 249 { 250 return 7 - strlen(trim($match)); 251 } 252 253 254 private 255 function enableWikiHeading($mode) 256 { 257 258 259 /** 260 * Basically all mode that are not `base` 261 * To not take the dokuwiki heading 262 */ 263 if (!(in_array($mode, ['base', 'header', 'table']))) { 264 return true; 265 } else { 266 return SiteConfig::getConfValue(self::CONF_WIKI_HEADING_ENABLE, self::CONF_DEFAULT_WIKI_ENABLE_VALUE); 267 } 268 269 270 } 271 272 273} 274