1*37748cd8SNickeau<?php 2*37748cd8SNickeau/** 3*37748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 4*37748cd8SNickeau * 5*37748cd8SNickeau * This source code is licensed under the GPL license found in the 6*37748cd8SNickeau * COPYING file in the root directory of this source tree. 7*37748cd8SNickeau * 8*37748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 9*37748cd8SNickeau * @author ComboStrap <support@combostrap.com> 10*37748cd8SNickeau * 11*37748cd8SNickeau */ 12*37748cd8SNickeau 13*37748cd8SNickeaunamespace ComboStrap; 14*37748cd8SNickeau 15*37748cd8SNickeau 16*37748cd8SNickeauuse Doku_Handler; 17*37748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin; 18*37748cd8SNickeauuse dokuwiki\Parsing\Parser; 19*37748cd8SNickeauuse syntax_plugin_combo_media; 20*37748cd8SNickeau 21*37748cd8SNickeau/** 22*37748cd8SNickeau * Class CallStack 23*37748cd8SNickeau * @package ComboStrap 24*37748cd8SNickeau * 25*37748cd8SNickeau * This is a class that manipulate the call stack. 26*37748cd8SNickeau * 27*37748cd8SNickeau * A call stack is composed of call (ie array) 28*37748cd8SNickeau * A tag is a call that has a state {@link DOKU_LEXER_ENTER} or {@link DOKU_LEXER_SPECIAL} 29*37748cd8SNickeau * An opening call is a call with the {@link DOKU_LEXER_ENTER} 30*37748cd8SNickeau * An closing call is a call with the {@link DOKU_LEXER_EXIT} 31*37748cd8SNickeau * 32*37748cd8SNickeau * You can move on the stack with the function: 33*37748cd8SNickeau * * {@link CallStack::next()} 34*37748cd8SNickeau * * {@link CallStack::previous()} 35*37748cd8SNickeau * * `MoveTo`. example: {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 36*37748cd8SNickeau * 37*37748cd8SNickeau * 38*37748cd8SNickeau */ 39*37748cd8SNickeauclass CallStack 40*37748cd8SNickeau{ 41*37748cd8SNickeau 42*37748cd8SNickeau const TAG_STATE = [DOKU_LEXER_SPECIAL, DOKU_LEXER_ENTER]; 43*37748cd8SNickeau 44*37748cd8SNickeau const CANONICAL = "support"; 45*37748cd8SNickeau 46*37748cd8SNickeau /** 47*37748cd8SNickeau * The type of callstack 48*37748cd8SNickeau * * main is the normal 49*37748cd8SNickeau * * writer is when there is a temporary call stack from the writer 50*37748cd8SNickeau */ 51*37748cd8SNickeau const CALLSTACK_WRITER = "writer"; 52*37748cd8SNickeau const CALLSTACK_MAIN = "main"; 53*37748cd8SNickeau public const MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM = "Your DokuWiki installation is too old or a writer plugin does not conform"; 54*37748cd8SNickeau 55*37748cd8SNickeau private $handler; 56*37748cd8SNickeau 57*37748cd8SNickeau /** 58*37748cd8SNickeau * The max key of the calls 59*37748cd8SNickeau * @var int|null 60*37748cd8SNickeau */ 61*37748cd8SNickeau private $maxIndex = 0; 62*37748cd8SNickeau 63*37748cd8SNickeau /** 64*37748cd8SNickeau * @var array the call stack 65*37748cd8SNickeau */ 66*37748cd8SNickeau private $callStack = []; 67*37748cd8SNickeau 68*37748cd8SNickeau /** 69*37748cd8SNickeau * A pointer to keep the information 70*37748cd8SNickeau * if we have gone to far in the stack 71*37748cd8SNickeau * (because you lost the fact that you are outside 72*37748cd8SNickeau * the boundary, ie you can do a {@link \prev}` after that a {@link \next} return false 73*37748cd8SNickeau * @var bool 74*37748cd8SNickeau * If true, we are at the offset: end of th array + 1 75*37748cd8SNickeau */ 76*37748cd8SNickeau private $endWasReached = false; 77*37748cd8SNickeau /** 78*37748cd8SNickeau * If true, we are at the offset: start of th array - 1 79*37748cd8SNickeau * You can use {@link CallStack::next()} 80*37748cd8SNickeau * @var bool 81*37748cd8SNickeau */ 82*37748cd8SNickeau private $startWasReached = false; 83*37748cd8SNickeau 84*37748cd8SNickeau /** 85*37748cd8SNickeau * @var string the type of callstack 86*37748cd8SNickeau */ 87*37748cd8SNickeau private $callStackType = "unknown"; 88*37748cd8SNickeau 89*37748cd8SNickeau /** 90*37748cd8SNickeau * A callstack is a pointer implementation to manipulate 91*37748cd8SNickeau * the {@link Doku_Handler::$calls call stack of the handler} 92*37748cd8SNickeau * 93*37748cd8SNickeau * When you create a callstack object, the pointer 94*37748cd8SNickeau * is located at the end. 95*37748cd8SNickeau * 96*37748cd8SNickeau * If you want to reset the pointer, you need 97*37748cd8SNickeau * to call the {@link CallStack::closeAndResetPointer()} function 98*37748cd8SNickeau * 99*37748cd8SNickeau * @param \Doku_Handler 100*37748cd8SNickeau */ 101*37748cd8SNickeau public function __construct(&$handler) 102*37748cd8SNickeau { 103*37748cd8SNickeau $this->handler = $handler; 104*37748cd8SNickeau 105*37748cd8SNickeau /** 106*37748cd8SNickeau * A temporary Call stack is created in the writer 107*37748cd8SNickeau * for list, table, blockquote 108*37748cd8SNickeau * 109*37748cd8SNickeau * But third party plugin can overwrite the writer 110*37748cd8SNickeau * to capture the call 111*37748cd8SNickeau * 112*37748cd8SNickeau * See the 113*37748cd8SNickeau * https://www.dokuwiki.org/devel:parser#handler_token_methods 114*37748cd8SNickeau * for an example with a list component 115*37748cd8SNickeau * 116*37748cd8SNickeau */ 117*37748cd8SNickeau $headErrorMessage = self::MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM; 118*37748cd8SNickeau if (!method_exists($handler, 'getCallWriter')) { 119*37748cd8SNickeau $class = get_class($handler); 120*37748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($class) provided cannot manipulate the callstack (ie the function getCallWriter does not exist).", LogUtility::LVL_MSG_ERROR); 121*37748cd8SNickeau return; 122*37748cd8SNickeau } 123*37748cd8SNickeau $callWriter = $handler->getCallWriter(); 124*37748cd8SNickeau 125*37748cd8SNickeau /** 126*37748cd8SNickeau * Check the calls property 127*37748cd8SNickeau */ 128*37748cd8SNickeau $callWriterClass = get_class($callWriter); 129*37748cd8SNickeau $callsPropertyFromCallWriterExists = true; 130*37748cd8SNickeau try { 131*37748cd8SNickeau $rp = new \ReflectionProperty($callWriterClass, "calls"); 132*37748cd8SNickeau if ($rp->isPrivate()) { 133*37748cd8SNickeau LogUtility::msg("$headErrorMessage. The call writer ($callWriterClass) provided cannot manipulate the callstack (ie the calls of the call writer are private).", LogUtility::LVL_MSG_ERROR); 134*37748cd8SNickeau return; 135*37748cd8SNickeau } 136*37748cd8SNickeau } catch (\ReflectionException $e) { 137*37748cd8SNickeau $callsPropertyFromCallWriterExists = false; 138*37748cd8SNickeau } 139*37748cd8SNickeau 140*37748cd8SNickeau /** 141*37748cd8SNickeau * The calls 142*37748cd8SNickeau */ 143*37748cd8SNickeau if ($callsPropertyFromCallWriterExists) { 144*37748cd8SNickeau 145*37748cd8SNickeau $writerCalls = &$callWriter->calls; 146*37748cd8SNickeau $this->callStack = &$writerCalls; 147*37748cd8SNickeau $this->callStackType = self::CALLSTACK_WRITER; 148*37748cd8SNickeau 149*37748cd8SNickeau } else { 150*37748cd8SNickeau 151*37748cd8SNickeau /** 152*37748cd8SNickeau * Check the calls property of the handler 153*37748cd8SNickeau */ 154*37748cd8SNickeau $handlerClass = get_class($handler); 155*37748cd8SNickeau try { 156*37748cd8SNickeau $rp = new \ReflectionProperty($handlerClass, "calls"); 157*37748cd8SNickeau if ($rp->isPrivate()) { 158*37748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the calls of the handler are private).", LogUtility::LVL_MSG_ERROR); 159*37748cd8SNickeau return; 160*37748cd8SNickeau } 161*37748cd8SNickeau } catch (\ReflectionException $e) { 162*37748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the handler does not have any calls property).", LogUtility::LVL_MSG_ERROR); 163*37748cd8SNickeau return; 164*37748cd8SNickeau } 165*37748cd8SNickeau 166*37748cd8SNickeau /** 167*37748cd8SNickeau * Initiate the callstack 168*37748cd8SNickeau */ 169*37748cd8SNickeau $this->callStack = &$handler->calls; 170*37748cd8SNickeau $this->callStackType = self::CALLSTACK_MAIN; 171*37748cd8SNickeau 172*37748cd8SNickeau } 173*37748cd8SNickeau 174*37748cd8SNickeau $this->maxIndex = ArrayUtility::array_key_last($this->callStack); 175*37748cd8SNickeau $this->moveToEnd(); 176*37748cd8SNickeau 177*37748cd8SNickeau 178*37748cd8SNickeau } 179*37748cd8SNickeau 180*37748cd8SNickeau public 181*37748cd8SNickeau static function createFromMarkup($marki) 182*37748cd8SNickeau { 183*37748cd8SNickeau 184*37748cd8SNickeau $modes = p_get_parsermodes(); 185*37748cd8SNickeau $handler = new Doku_Handler(); 186*37748cd8SNickeau $parser = new Parser($handler); 187*37748cd8SNickeau 188*37748cd8SNickeau //add modes to parser 189*37748cd8SNickeau foreach ($modes as $mode) { 190*37748cd8SNickeau $parser->addMode($mode['mode'], $mode['obj']); 191*37748cd8SNickeau } 192*37748cd8SNickeau $parser->parse($marki); 193*37748cd8SNickeau return self::createFromHandler($handler); 194*37748cd8SNickeau 195*37748cd8SNickeau } 196*37748cd8SNickeau 197*37748cd8SNickeau /** 198*37748cd8SNickeau * Reset the pointer 199*37748cd8SNickeau */ 200*37748cd8SNickeau public 201*37748cd8SNickeau function closeAndResetPointer() 202*37748cd8SNickeau { 203*37748cd8SNickeau reset($this->callStack); 204*37748cd8SNickeau } 205*37748cd8SNickeau 206*37748cd8SNickeau /** 207*37748cd8SNickeau * Delete from the call stack 208*37748cd8SNickeau * @param $calls 209*37748cd8SNickeau * @param $start 210*37748cd8SNickeau * @param $end 211*37748cd8SNickeau */ 212*37748cd8SNickeau public 213*37748cd8SNickeau static function deleteCalls(&$calls, $start, $end) 214*37748cd8SNickeau { 215*37748cd8SNickeau for ($i = $start; $i <= $end; $i++) { 216*37748cd8SNickeau unset($calls[$i]); 217*37748cd8SNickeau } 218*37748cd8SNickeau } 219*37748cd8SNickeau 220*37748cd8SNickeau /** 221*37748cd8SNickeau * @param array $calls 222*37748cd8SNickeau * @param integer $position 223*37748cd8SNickeau * @param array $callStackToInsert 224*37748cd8SNickeau */ 225*37748cd8SNickeau public 226*37748cd8SNickeau static function insertCallStackUpWards(&$calls, $position, $callStackToInsert) 227*37748cd8SNickeau { 228*37748cd8SNickeau 229*37748cd8SNickeau array_splice($calls, $position, 0, $callStackToInsert); 230*37748cd8SNickeau 231*37748cd8SNickeau } 232*37748cd8SNickeau 233*37748cd8SNickeau /** 234*37748cd8SNickeau * A callstack pointer based implementation 235*37748cd8SNickeau * that starts at the end 236*37748cd8SNickeau * @param Doku_Handler $handler 237*37748cd8SNickeau * @return CallStack 238*37748cd8SNickeau */ 239*37748cd8SNickeau public 240*37748cd8SNickeau static function createFromHandler(&$handler) 241*37748cd8SNickeau { 242*37748cd8SNickeau return new CallStack($handler); 243*37748cd8SNickeau } 244*37748cd8SNickeau 245*37748cd8SNickeau 246*37748cd8SNickeau /** 247*37748cd8SNickeau * Process the EOL call to the end of stack 248*37748cd8SNickeau * replacing them with paragraph call 249*37748cd8SNickeau * 250*37748cd8SNickeau * A sort of {@link Block::process()} but only from a tag 251*37748cd8SNickeau * to the end of the current stack 252*37748cd8SNickeau * 253*37748cd8SNickeau * This function is used basically in the {@link DOKU_LEXER_EXIT} 254*37748cd8SNickeau * state of {@link SyntaxPlugin::handle()} to create paragraph 255*37748cd8SNickeau * with the class given as parameter 256*37748cd8SNickeau * 257*37748cd8SNickeau * @param $attributes - the attributes in an callstack array form passed to the paragraph 258*37748cd8SNickeau */ 259*37748cd8SNickeau public 260*37748cd8SNickeau function processEolToEndStack($attributes = []) 261*37748cd8SNickeau { 262*37748cd8SNickeau 263*37748cd8SNickeau \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack($this, $attributes); 264*37748cd8SNickeau 265*37748cd8SNickeau } 266*37748cd8SNickeau 267*37748cd8SNickeau /** 268*37748cd8SNickeau * Delete the call where the pointer is 269*37748cd8SNickeau * And go to the previous position 270*37748cd8SNickeau * 271*37748cd8SNickeau * This function can be used in a next loop 272*37748cd8SNickeau * 273*37748cd8SNickeau * @return Call the deleted call 274*37748cd8SNickeau */ 275*37748cd8SNickeau public 276*37748cd8SNickeau function deleteActualCallAndPrevious() 277*37748cd8SNickeau { 278*37748cd8SNickeau 279*37748cd8SNickeau $actualCall = $this->getActualCall(); 280*37748cd8SNickeau 281*37748cd8SNickeau $offset = $this->getActualOffset(); 282*37748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 283*37748cd8SNickeau 284*37748cd8SNickeau /** 285*37748cd8SNickeau * Move to the next element (array splice reset the pointer) 286*37748cd8SNickeau * if there is a eol as, we delete it 287*37748cd8SNickeau * otherwise we may end up with two eol 288*37748cd8SNickeau * and this is an empty paragraph 289*37748cd8SNickeau */ 290*37748cd8SNickeau $this->moveToOffset($offset); 291*37748cd8SNickeau if (!$this->isPointerAtEnd()) { 292*37748cd8SNickeau if ($this->getActualCall()->getTagName() == 'eol') { 293*37748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 294*37748cd8SNickeau } 295*37748cd8SNickeau } 296*37748cd8SNickeau 297*37748cd8SNickeau /** 298*37748cd8SNickeau * Move to the previous element 299*37748cd8SNickeau */ 300*37748cd8SNickeau $this->moveToOffset($offset - 1); 301*37748cd8SNickeau 302*37748cd8SNickeau return $actualCall; 303*37748cd8SNickeau 304*37748cd8SNickeau } 305*37748cd8SNickeau 306*37748cd8SNickeau /** 307*37748cd8SNickeau * @return Call - get a reference to the actual call 308*37748cd8SNickeau * This function returns a {@link Call call} object 309*37748cd8SNickeau * by reference, meaning that every update will also modify the element 310*37748cd8SNickeau * in the stack 311*37748cd8SNickeau */ 312*37748cd8SNickeau public 313*37748cd8SNickeau function getActualCall() 314*37748cd8SNickeau { 315*37748cd8SNickeau if ($this->endWasReached) { 316*37748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the end of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 317*37748cd8SNickeau return null; 318*37748cd8SNickeau } 319*37748cd8SNickeau if ($this->startWasReached) { 320*37748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the start of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 321*37748cd8SNickeau return null; 322*37748cd8SNickeau } 323*37748cd8SNickeau $actualCallKey = key($this->callStack); 324*37748cd8SNickeau $actualCallArray = &$this->callStack[$actualCallKey]; 325*37748cd8SNickeau return new Call($actualCallArray, $actualCallKey); 326*37748cd8SNickeau 327*37748cd8SNickeau } 328*37748cd8SNickeau 329*37748cd8SNickeau /** 330*37748cd8SNickeau * put the pointer one position further 331*37748cd8SNickeau * false if at the end 332*37748cd8SNickeau * @return false|Call 333*37748cd8SNickeau */ 334*37748cd8SNickeau public 335*37748cd8SNickeau function next() 336*37748cd8SNickeau { 337*37748cd8SNickeau if ($this->startWasReached) { 338*37748cd8SNickeau $this->startWasReached = false; 339*37748cd8SNickeau $result = reset($this->callStack); 340*37748cd8SNickeau if ($result === false) { 341*37748cd8SNickeau return false; 342*37748cd8SNickeau } else { 343*37748cd8SNickeau return $this->getActualCall(); 344*37748cd8SNickeau } 345*37748cd8SNickeau } else { 346*37748cd8SNickeau $next = next($this->callStack); 347*37748cd8SNickeau if ($next === false) { 348*37748cd8SNickeau $this->endWasReached = true; 349*37748cd8SNickeau return $next; 350*37748cd8SNickeau } else { 351*37748cd8SNickeau return $this->getActualCall(); 352*37748cd8SNickeau } 353*37748cd8SNickeau } 354*37748cd8SNickeau 355*37748cd8SNickeau } 356*37748cd8SNickeau 357*37748cd8SNickeau /** 358*37748cd8SNickeau * 359*37748cd8SNickeau * From an exit call, move the corresponding Opening call 360*37748cd8SNickeau * 361*37748cd8SNickeau * This is used mostly in {@link SyntaxPlugin::handle()} from a {@link DOKU_LEXER_EXIT} 362*37748cd8SNickeau * to retrieve the {@link DOKU_LEXER_ENTER} call 363*37748cd8SNickeau * 364*37748cd8SNickeau * @return bool|Call 365*37748cd8SNickeau */ 366*37748cd8SNickeau public 367*37748cd8SNickeau function moveToPreviousCorrespondingOpeningCall() 368*37748cd8SNickeau { 369*37748cd8SNickeau 370*37748cd8SNickeau /** 371*37748cd8SNickeau * Edgde case 372*37748cd8SNickeau */ 373*37748cd8SNickeau if(empty($this->callStack)){ 374*37748cd8SNickeau return false; 375*37748cd8SNickeau } 376*37748cd8SNickeau 377*37748cd8SNickeau if (!$this->endWasReached) { 378*37748cd8SNickeau $actualCall = $this->getActualCall(); 379*37748cd8SNickeau $actualState = $actualCall->getState(); 380*37748cd8SNickeau if ($actualState != DOKU_LEXER_EXIT) { 381*37748cd8SNickeau /** 382*37748cd8SNickeau * Check if we are at the end of the stack 383*37748cd8SNickeau */ 384*37748cd8SNickeau LogUtility::msg("You are not at the end of stack and you are not on a opening tag, you can't ask for the opening tag." . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 385*37748cd8SNickeau return false; 386*37748cd8SNickeau } 387*37748cd8SNickeau } 388*37748cd8SNickeau $level = 0; 389*37748cd8SNickeau while ($actualCall = $this->previous()) { 390*37748cd8SNickeau 391*37748cd8SNickeau $state = $actualCall->getState(); 392*37748cd8SNickeau switch ($state) { 393*37748cd8SNickeau case DOKU_LEXER_ENTER: 394*37748cd8SNickeau $level++; 395*37748cd8SNickeau break; 396*37748cd8SNickeau case DOKU_LEXER_EXIT: 397*37748cd8SNickeau $level--; 398*37748cd8SNickeau break; 399*37748cd8SNickeau } 400*37748cd8SNickeau if ($level > 0) { 401*37748cd8SNickeau break; 402*37748cd8SNickeau } 403*37748cd8SNickeau 404*37748cd8SNickeau } 405*37748cd8SNickeau if ($level > 0) { 406*37748cd8SNickeau return $actualCall; 407*37748cd8SNickeau } else { 408*37748cd8SNickeau return false; 409*37748cd8SNickeau } 410*37748cd8SNickeau } 411*37748cd8SNickeau 412*37748cd8SNickeau 413*37748cd8SNickeau /** 414*37748cd8SNickeau * @return Call|false the previous call or false if there is no more previous call 415*37748cd8SNickeau */ 416*37748cd8SNickeau public 417*37748cd8SNickeau function previous() 418*37748cd8SNickeau { 419*37748cd8SNickeau if ($this->endWasReached) { 420*37748cd8SNickeau $this->endWasReached = false; 421*37748cd8SNickeau $return = end($this->callStack); 422*37748cd8SNickeau if ($return == false) { 423*37748cd8SNickeau // empty array (first call on the stack) 424*37748cd8SNickeau return false; 425*37748cd8SNickeau } else { 426*37748cd8SNickeau return $this->getActualCall(); 427*37748cd8SNickeau } 428*37748cd8SNickeau } else { 429*37748cd8SNickeau $prev = prev($this->callStack); 430*37748cd8SNickeau if ($prev === false) { 431*37748cd8SNickeau $this->startWasReached = true; 432*37748cd8SNickeau return $prev; 433*37748cd8SNickeau } else { 434*37748cd8SNickeau return $this->getActualCall(); 435*37748cd8SNickeau } 436*37748cd8SNickeau } 437*37748cd8SNickeau 438*37748cd8SNickeau } 439*37748cd8SNickeau 440*37748cd8SNickeau /** 441*37748cd8SNickeau * Return the first enter or special child call (ie a tag) 442*37748cd8SNickeau * @return Call|false 443*37748cd8SNickeau */ 444*37748cd8SNickeau public 445*37748cd8SNickeau function moveToFirstChildTag() 446*37748cd8SNickeau { 447*37748cd8SNickeau $found = false; 448*37748cd8SNickeau while ($this->next()) { 449*37748cd8SNickeau 450*37748cd8SNickeau $actualCall = $this->getActualCall(); 451*37748cd8SNickeau $state = $actualCall->getState(); 452*37748cd8SNickeau switch ($state) { 453*37748cd8SNickeau case DOKU_LEXER_ENTER: 454*37748cd8SNickeau case DOKU_LEXER_SPECIAL: 455*37748cd8SNickeau $found = true; 456*37748cd8SNickeau break 2; 457*37748cd8SNickeau case DOKU_LEXER_EXIT: 458*37748cd8SNickeau break 2; 459*37748cd8SNickeau } 460*37748cd8SNickeau 461*37748cd8SNickeau } 462*37748cd8SNickeau if ($found) { 463*37748cd8SNickeau return $this->getActualCall(); 464*37748cd8SNickeau } else { 465*37748cd8SNickeau return false; 466*37748cd8SNickeau } 467*37748cd8SNickeau 468*37748cd8SNickeau 469*37748cd8SNickeau } 470*37748cd8SNickeau 471*37748cd8SNickeau /** 472*37748cd8SNickeau * The end is the one after the last element 473*37748cd8SNickeau */ 474*37748cd8SNickeau public 475*37748cd8SNickeau function moveToEnd() 476*37748cd8SNickeau { 477*37748cd8SNickeau if ($this->startWasReached) { 478*37748cd8SNickeau $this->startWasReached = false; 479*37748cd8SNickeau } 480*37748cd8SNickeau end($this->callStack); 481*37748cd8SNickeau $this->next(); 482*37748cd8SNickeau } 483*37748cd8SNickeau 484*37748cd8SNickeau /** 485*37748cd8SNickeau * On the same level 486*37748cd8SNickeau */ 487*37748cd8SNickeau public 488*37748cd8SNickeau function moveToNextSiblingTag() 489*37748cd8SNickeau { 490*37748cd8SNickeau 491*37748cd8SNickeau /** 492*37748cd8SNickeau * Edgde case 493*37748cd8SNickeau */ 494*37748cd8SNickeau if(empty($this->callStack)){ 495*37748cd8SNickeau return false; 496*37748cd8SNickeau } 497*37748cd8SNickeau 498*37748cd8SNickeau $actualCall = $this->getActualCall(); 499*37748cd8SNickeau $actualState = $actualCall->getState(); 500*37748cd8SNickeau if (!in_array($actualState, CallStack::TAG_STATE)) { 501*37748cd8SNickeau LogUtility::msg("A next sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 502*37748cd8SNickeau return false; 503*37748cd8SNickeau } 504*37748cd8SNickeau $level = 0; 505*37748cd8SNickeau while ($this->next()) { 506*37748cd8SNickeau 507*37748cd8SNickeau $actualCall = $this->getActualCall(); 508*37748cd8SNickeau $state = $actualCall->getState(); 509*37748cd8SNickeau switch ($state) { 510*37748cd8SNickeau case DOKU_LEXER_ENTER: 511*37748cd8SNickeau case DOKU_LEXER_SPECIAL: 512*37748cd8SNickeau $level++; 513*37748cd8SNickeau break; 514*37748cd8SNickeau case DOKU_LEXER_EXIT: 515*37748cd8SNickeau $level--; 516*37748cd8SNickeau break; 517*37748cd8SNickeau } 518*37748cd8SNickeau 519*37748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 520*37748cd8SNickeau break; 521*37748cd8SNickeau } 522*37748cd8SNickeau } 523*37748cd8SNickeau if ($level == 0 && !$this->endWasReached) { 524*37748cd8SNickeau return $this->getActualCall(); 525*37748cd8SNickeau } else { 526*37748cd8SNickeau return false; 527*37748cd8SNickeau } 528*37748cd8SNickeau } 529*37748cd8SNickeau 530*37748cd8SNickeau /** 531*37748cd8SNickeau * @param Call $call 532*37748cd8SNickeau * @return Call the inserted call 533*37748cd8SNickeau */ 534*37748cd8SNickeau public 535*37748cd8SNickeau function insertBefore($call) 536*37748cd8SNickeau { 537*37748cd8SNickeau if ($this->endWasReached) { 538*37748cd8SNickeau 539*37748cd8SNickeau $this->callStack[] = $call->toCallArray(); 540*37748cd8SNickeau 541*37748cd8SNickeau } else { 542*37748cd8SNickeau 543*37748cd8SNickeau $offset = $this->getActualOffset(); 544*37748cd8SNickeau array_splice($this->callStack, $offset, 0, [$call->toCallArray()]); 545*37748cd8SNickeau // array splice reset the pointer 546*37748cd8SNickeau // we move it to the actual element (ie the key is offset +1) 547*37748cd8SNickeau $this->moveToOffset($offset + 1); 548*37748cd8SNickeau 549*37748cd8SNickeau } 550*37748cd8SNickeau return $call; 551*37748cd8SNickeau } 552*37748cd8SNickeau 553*37748cd8SNickeau /** 554*37748cd8SNickeau * Move pointer by offset 555*37748cd8SNickeau * @param $offset 556*37748cd8SNickeau */ 557*37748cd8SNickeau private 558*37748cd8SNickeau function moveToOffset($offset) 559*37748cd8SNickeau { 560*37748cd8SNickeau $this->resetPointer(); 561*37748cd8SNickeau for ($i = 0; $i < $offset; $i++) { 562*37748cd8SNickeau $result = $this->next(); 563*37748cd8SNickeau if ($result === false) { 564*37748cd8SNickeau break; 565*37748cd8SNickeau } 566*37748cd8SNickeau } 567*37748cd8SNickeau } 568*37748cd8SNickeau 569*37748cd8SNickeau /** 570*37748cd8SNickeau * Move pointer by key 571*37748cd8SNickeau * @param $targetKey 572*37748cd8SNickeau */ 573*37748cd8SNickeau private 574*37748cd8SNickeau function moveToKey($targetKey) 575*37748cd8SNickeau { 576*37748cd8SNickeau $this->resetPointer(); 577*37748cd8SNickeau for ($i = 0; $i < $targetKey; $i++) { 578*37748cd8SNickeau next($this->callStack); 579*37748cd8SNickeau } 580*37748cd8SNickeau $actualKey = key($this->callStack); 581*37748cd8SNickeau if ($actualKey != $targetKey) { 582*37748cd8SNickeau LogUtility::msg("The target key ($targetKey) is not equal to the actual key ($actualKey). The moveToKey was not successful"); 583*37748cd8SNickeau } 584*37748cd8SNickeau } 585*37748cd8SNickeau 586*37748cd8SNickeau /** 587*37748cd8SNickeau * Insert After. The pointer stays at the current state. 588*37748cd8SNickeau * If you don't need to process the call that you just 589*37748cd8SNickeau * inserted, you may want to call {@link CallStack::next()} 590*37748cd8SNickeau * @param Call $call 591*37748cd8SNickeau */ 592*37748cd8SNickeau public 593*37748cd8SNickeau function insertAfter($call) 594*37748cd8SNickeau { 595*37748cd8SNickeau $actualKey = key($this->callStack); 596*37748cd8SNickeau if ($actualKey == null) { 597*37748cd8SNickeau if ($this->endWasReached == true) { 598*37748cd8SNickeau $this->callStack[] = $call->toCallArray(); 599*37748cd8SNickeau } else { 600*37748cd8SNickeau LogUtility::msg("Callstack: Actual key is null, we can't insert after null"); 601*37748cd8SNickeau } 602*37748cd8SNickeau } else { 603*37748cd8SNickeau $offset = array_search($actualKey, array_keys($this->callStack), true); 604*37748cd8SNickeau array_splice($this->callStack, $offset + 1, 0, [$call->toCallArray()]); 605*37748cd8SNickeau // array splice reset the pointer 606*37748cd8SNickeau // we move it to the actual element 607*37748cd8SNickeau $this->moveToKey($actualKey); 608*37748cd8SNickeau } 609*37748cd8SNickeau } 610*37748cd8SNickeau 611*37748cd8SNickeau public 612*37748cd8SNickeau function getActualKey() 613*37748cd8SNickeau { 614*37748cd8SNickeau return key($this->callStack); 615*37748cd8SNickeau } 616*37748cd8SNickeau 617*37748cd8SNickeau /** 618*37748cd8SNickeau * Insert an EOL call if the next call is not an EOL 619*37748cd8SNickeau * This is to enforce an new paragraph 620*37748cd8SNickeau */ 621*37748cd8SNickeau public 622*37748cd8SNickeau function insertEolIfNextCallIsNotEolOrBlock() 623*37748cd8SNickeau { 624*37748cd8SNickeau if (!$this->isPointerAtEnd()) { 625*37748cd8SNickeau $nextCall = $this->next(); 626*37748cd8SNickeau if ($nextCall != false) { 627*37748cd8SNickeau if ($nextCall->getTagName() != "eol" && $nextCall->getDisplay() != "block") { 628*37748cd8SNickeau $this->insertBefore( 629*37748cd8SNickeau Call::createNativeCall("eol") 630*37748cd8SNickeau ); 631*37748cd8SNickeau // move on the eol 632*37748cd8SNickeau $this->previous(); 633*37748cd8SNickeau } 634*37748cd8SNickeau // move back 635*37748cd8SNickeau $this->previous(); 636*37748cd8SNickeau } 637*37748cd8SNickeau } 638*37748cd8SNickeau } 639*37748cd8SNickeau 640*37748cd8SNickeau private 641*37748cd8SNickeau function isPointerAtEnd() 642*37748cd8SNickeau { 643*37748cd8SNickeau return $this->endWasReached; 644*37748cd8SNickeau } 645*37748cd8SNickeau 646*37748cd8SNickeau public 647*37748cd8SNickeau function &getHandler() 648*37748cd8SNickeau { 649*37748cd8SNickeau return $this->handler; 650*37748cd8SNickeau } 651*37748cd8SNickeau 652*37748cd8SNickeau /** 653*37748cd8SNickeau * Return The offset (not the key): 654*37748cd8SNickeau * * starting at 0 for the first element 655*37748cd8SNickeau * * 1 for the second ... 656*37748cd8SNickeau * 657*37748cd8SNickeau * @return false|int|string 658*37748cd8SNickeau */ 659*37748cd8SNickeau private 660*37748cd8SNickeau function getActualOffset() 661*37748cd8SNickeau { 662*37748cd8SNickeau $actualKey = key($this->callStack); 663*37748cd8SNickeau return array_search($actualKey, array_keys($this->callStack), true); 664*37748cd8SNickeau } 665*37748cd8SNickeau 666*37748cd8SNickeau private 667*37748cd8SNickeau function resetPointer() 668*37748cd8SNickeau { 669*37748cd8SNickeau reset($this->callStack); 670*37748cd8SNickeau $this->endWasReached = false; 671*37748cd8SNickeau } 672*37748cd8SNickeau 673*37748cd8SNickeau public 674*37748cd8SNickeau function moveToStart() 675*37748cd8SNickeau { 676*37748cd8SNickeau $this->resetPointer(); 677*37748cd8SNickeau $this->previous(); 678*37748cd8SNickeau } 679*37748cd8SNickeau 680*37748cd8SNickeau /** 681*37748cd8SNickeau * @return Call|false the parent call or false if there is no parent 682*37748cd8SNickeau * If you are on an {@link DOKU_LEXER_EXIT} state, you should 683*37748cd8SNickeau * call first the {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 684*37748cd8SNickeau */ 685*37748cd8SNickeau public function moveToParent() 686*37748cd8SNickeau { 687*37748cd8SNickeau 688*37748cd8SNickeau /** 689*37748cd8SNickeau * Case when we start from the exit state element 690*37748cd8SNickeau * We go first to the opening tag 691*37748cd8SNickeau * because the algorithm is level based. 692*37748cd8SNickeau * 693*37748cd8SNickeau * When the end is reached, there is no call 694*37748cd8SNickeau * (this not the {@link end php end} but one further 695*37748cd8SNickeau */ 696*37748cd8SNickeau if (!$this->endWasReached && !$this->startWasReached && $this->getActualCall()->getState() == DOKU_LEXER_EXIT) { 697*37748cd8SNickeau 698*37748cd8SNickeau $this->moveToPreviousCorrespondingOpeningCall(); 699*37748cd8SNickeau 700*37748cd8SNickeau } 701*37748cd8SNickeau 702*37748cd8SNickeau 703*37748cd8SNickeau /** 704*37748cd8SNickeau * We are in a parent when the tree level is negative 705*37748cd8SNickeau */ 706*37748cd8SNickeau $treeLevel = 0; 707*37748cd8SNickeau while ($actualCall = $this->previous()) { 708*37748cd8SNickeau 709*37748cd8SNickeau /** 710*37748cd8SNickeau * Add 711*37748cd8SNickeau * would become a parent on its enter state 712*37748cd8SNickeau */ 713*37748cd8SNickeau $actualCallState = $actualCall->getState(); 714*37748cd8SNickeau switch ($actualCallState) { 715*37748cd8SNickeau case DOKU_LEXER_ENTER: 716*37748cd8SNickeau $treeLevel = $treeLevel - 1; 717*37748cd8SNickeau break; 718*37748cd8SNickeau case DOKU_LEXER_EXIT: 719*37748cd8SNickeau /** 720*37748cd8SNickeau * When the tag has a sibling with an exit tag 721*37748cd8SNickeau */ 722*37748cd8SNickeau $treeLevel = $treeLevel + 1; 723*37748cd8SNickeau break; 724*37748cd8SNickeau } 725*37748cd8SNickeau 726*37748cd8SNickeau /** 727*37748cd8SNickeau * The breaking statement 728*37748cd8SNickeau */ 729*37748cd8SNickeau if ($treeLevel < 0) { 730*37748cd8SNickeau break; 731*37748cd8SNickeau } 732*37748cd8SNickeau 733*37748cd8SNickeau } 734*37748cd8SNickeau return $actualCall; 735*37748cd8SNickeau 736*37748cd8SNickeau 737*37748cd8SNickeau } 738*37748cd8SNickeau 739*37748cd8SNickeau /** 740*37748cd8SNickeau * Delete the anchor link to the image (ie the lightbox) 741*37748cd8SNickeau * 742*37748cd8SNickeau * This is used in navigation and for instance 743*37748cd8SNickeau * in heading 744*37748cd8SNickeau */ 745*37748cd8SNickeau public function processNoLinkOnImageToEndStack() 746*37748cd8SNickeau { 747*37748cd8SNickeau while ($this->next()) { 748*37748cd8SNickeau $actualCall = $this->getActualCall(); 749*37748cd8SNickeau if ($actualCall->getTagName() == syntax_plugin_combo_media::TAG) { 750*37748cd8SNickeau $actualCall->addAttribute(MediaLink::LINKING_KEY, MediaLink::LINKING_NOLINK_VALUE); 751*37748cd8SNickeau } 752*37748cd8SNickeau } 753*37748cd8SNickeau } 754*37748cd8SNickeau 755*37748cd8SNickeau /** 756*37748cd8SNickeau * Append instructions to the callstack (ie at the end) 757*37748cd8SNickeau * @param array $instructions 758*37748cd8SNickeau */ 759*37748cd8SNickeau public function appendInstructionsFromNativeArray($instructions) 760*37748cd8SNickeau { 761*37748cd8SNickeau array_splice($this->callStack, count($this->callStack), 0, $instructions); 762*37748cd8SNickeau } 763*37748cd8SNickeau 764*37748cd8SNickeau /** 765*37748cd8SNickeau * @param Call $call 766*37748cd8SNickeau */ 767*37748cd8SNickeau public function appendCallAtTheEnd($call) 768*37748cd8SNickeau { 769*37748cd8SNickeau $this->callStack[] = $call->toCallArray(); 770*37748cd8SNickeau } 771*37748cd8SNickeau 772*37748cd8SNickeau public function moveToPreviousSiblingTag() 773*37748cd8SNickeau { 774*37748cd8SNickeau /** 775*37748cd8SNickeau * Edge case 776*37748cd8SNickeau */ 777*37748cd8SNickeau if(empty($this->callStack)){ 778*37748cd8SNickeau return false; 779*37748cd8SNickeau } 780*37748cd8SNickeau 781*37748cd8SNickeau if (!$this->endWasReached) { 782*37748cd8SNickeau $actualCall = $this->getActualCall(); 783*37748cd8SNickeau $actualState = $actualCall->getState(); 784*37748cd8SNickeau if (!in_array($actualState, CallStack::TAG_STATE)) { 785*37748cd8SNickeau LogUtility::msg("A previous sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support"); 786*37748cd8SNickeau return false; 787*37748cd8SNickeau } 788*37748cd8SNickeau } 789*37748cd8SNickeau $level = 0; 790*37748cd8SNickeau while ($this->previous()) { 791*37748cd8SNickeau 792*37748cd8SNickeau $actualCall = $this->getActualCall(); 793*37748cd8SNickeau $state = $actualCall->getState(); 794*37748cd8SNickeau switch ($state) { 795*37748cd8SNickeau case DOKU_LEXER_ENTER: 796*37748cd8SNickeau case DOKU_LEXER_SPECIAL: 797*37748cd8SNickeau $level++; 798*37748cd8SNickeau break; 799*37748cd8SNickeau case DOKU_LEXER_EXIT: 800*37748cd8SNickeau $level--; 801*37748cd8SNickeau break; 802*37748cd8SNickeau } 803*37748cd8SNickeau 804*37748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 805*37748cd8SNickeau break; 806*37748cd8SNickeau } 807*37748cd8SNickeau } 808*37748cd8SNickeau if ($level == 0 && !$this->startWasReached) { 809*37748cd8SNickeau return $this->getActualCall(); 810*37748cd8SNickeau } else { 811*37748cd8SNickeau return false; 812*37748cd8SNickeau } 813*37748cd8SNickeau } 814*37748cd8SNickeau 815*37748cd8SNickeau /** 816*37748cd8SNickeau * Delete all calls after the passed call 817*37748cd8SNickeau * 818*37748cd8SNickeau * It's used in syntax generator that: 819*37748cd8SNickeau * * capture the children callstack at the end, 820*37748cd8SNickeau * * delete it 821*37748cd8SNickeau * * and use it to generate more calls. 822*37748cd8SNickeau * 823*37748cd8SNickeau * @param Call $call 824*37748cd8SNickeau */ 825*37748cd8SNickeau public function deleteAllCallsAfter(Call $call) 826*37748cd8SNickeau { 827*37748cd8SNickeau $key = $call->getKey(); 828*37748cd8SNickeau $offset = array_search($key, array_keys($this->callStack), true); 829*37748cd8SNickeau if ($offset !== false) { 830*37748cd8SNickeau /** 831*37748cd8SNickeau * We delete from the next 832*37748cd8SNickeau * {@link array_splice()} delete also the given offset 833*37748cd8SNickeau */ 834*37748cd8SNickeau array_splice($this->callStack, $offset + 1); 835*37748cd8SNickeau } else { 836*37748cd8SNickeau LogUtility::msg("The call ($call) could not be found in the callStack. We couldn't therefore delete the calls after"); 837*37748cd8SNickeau } 838*37748cd8SNickeau 839*37748cd8SNickeau } 840*37748cd8SNickeau 841*37748cd8SNickeau /** 842*37748cd8SNickeau * @param Call[] $calls 843*37748cd8SNickeau */ 844*37748cd8SNickeau public function appendInstructionsFromCallObjects($calls) 845*37748cd8SNickeau { 846*37748cd8SNickeau foreach($calls as $call){ 847*37748cd8SNickeau $this->appendCallAtTheEnd($call); 848*37748cd8SNickeau } 849*37748cd8SNickeau 850*37748cd8SNickeau } 851*37748cd8SNickeau 852*37748cd8SNickeau 853*37748cd8SNickeau} 854