137748cd8SNickeau<?php 237748cd8SNickeau/** 337748cd8SNickeau * Copyright (c) 2021. ComboStrap, Inc. and its affiliates. All Rights Reserved. 437748cd8SNickeau * 537748cd8SNickeau * This source code is licensed under the GPL license found in the 637748cd8SNickeau * COPYING file in the root directory of this source tree. 737748cd8SNickeau * 837748cd8SNickeau * @license GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html) 937748cd8SNickeau * @author ComboStrap <support@combostrap.com> 1037748cd8SNickeau * 1137748cd8SNickeau */ 1237748cd8SNickeau 1337748cd8SNickeaunamespace ComboStrap; 1437748cd8SNickeau 1537748cd8SNickeau 1637748cd8SNickeauuse Doku_Handler; 1737748cd8SNickeauuse dokuwiki\Extension\SyntaxPlugin; 1837748cd8SNickeauuse dokuwiki\Parsing\Parser; 1937748cd8SNickeauuse syntax_plugin_combo_media; 2037748cd8SNickeau 2137748cd8SNickeau/** 2237748cd8SNickeau * Class CallStack 2337748cd8SNickeau * @package ComboStrap 2437748cd8SNickeau * 2537748cd8SNickeau * This is a class that manipulate the call stack. 2637748cd8SNickeau * 2737748cd8SNickeau * A call stack is composed of call (ie array) 2837748cd8SNickeau * A tag is a call that has a state {@link DOKU_LEXER_ENTER} or {@link DOKU_LEXER_SPECIAL} 2937748cd8SNickeau * An opening call is a call with the {@link DOKU_LEXER_ENTER} 3037748cd8SNickeau * An closing call is a call with the {@link DOKU_LEXER_EXIT} 3137748cd8SNickeau * 3237748cd8SNickeau * You can move on the stack with the function: 3337748cd8SNickeau * * {@link CallStack::next()} 3437748cd8SNickeau * * {@link CallStack::previous()} 3537748cd8SNickeau * * `MoveTo`. example: {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 3637748cd8SNickeau * 3737748cd8SNickeau * 3837748cd8SNickeau */ 3937748cd8SNickeauclass CallStack 4037748cd8SNickeau{ 4137748cd8SNickeau 4237748cd8SNickeau const TAG_STATE = [DOKU_LEXER_SPECIAL, DOKU_LEXER_ENTER]; 4337748cd8SNickeau 4437748cd8SNickeau const CANONICAL = "support"; 4537748cd8SNickeau 4637748cd8SNickeau /** 4737748cd8SNickeau * The type of callstack 4837748cd8SNickeau * * main is the normal 4937748cd8SNickeau * * writer is when there is a temporary call stack from the writer 5037748cd8SNickeau */ 5137748cd8SNickeau const CALLSTACK_WRITER = "writer"; 5237748cd8SNickeau const CALLSTACK_MAIN = "main"; 5337748cd8SNickeau public const MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM = "Your DokuWiki installation is too old or a writer plugin does not conform"; 54*04fd306cSNickeau const DOCUMENT_START = "document_start"; 55*04fd306cSNickeau const DOCUMENT_END = "document_end"; 5637748cd8SNickeau 5737748cd8SNickeau private $handler; 5837748cd8SNickeau 5937748cd8SNickeau /** 6037748cd8SNickeau * @var array the call stack 6137748cd8SNickeau */ 6237748cd8SNickeau private $callStack = []; 6337748cd8SNickeau 6437748cd8SNickeau /** 6537748cd8SNickeau * A pointer to keep the information 6637748cd8SNickeau * if we have gone to far in the stack 6737748cd8SNickeau * (because you lost the fact that you are outside 6837748cd8SNickeau * the boundary, ie you can do a {@link \prev}` after that a {@link \next} return false 6937748cd8SNickeau * @var bool 7037748cd8SNickeau * If true, we are at the offset: end of th array + 1 7137748cd8SNickeau */ 7237748cd8SNickeau private $endWasReached = false; 7337748cd8SNickeau /** 7437748cd8SNickeau * If true, we are at the offset: start of th array - 1 7537748cd8SNickeau * You can use {@link CallStack::next()} 7637748cd8SNickeau * @var bool 7737748cd8SNickeau */ 7837748cd8SNickeau private $startWasReached = false; 7937748cd8SNickeau 8037748cd8SNickeau 8137748cd8SNickeau /** 8237748cd8SNickeau * A callstack is a pointer implementation to manipulate 8337748cd8SNickeau * the {@link Doku_Handler::$calls call stack of the handler} 8437748cd8SNickeau * 8537748cd8SNickeau * When you create a callstack object, the pointer 8637748cd8SNickeau * is located at the end. 8737748cd8SNickeau * 8837748cd8SNickeau * If you want to reset the pointer, you need 8937748cd8SNickeau * to call the {@link CallStack::closeAndResetPointer()} function 9037748cd8SNickeau * 9137748cd8SNickeau * @param \Doku_Handler 9237748cd8SNickeau */ 9337748cd8SNickeau public function __construct(&$handler) 9437748cd8SNickeau { 9537748cd8SNickeau $this->handler = $handler; 9637748cd8SNickeau 9737748cd8SNickeau /** 9837748cd8SNickeau * A temporary Call stack is created in the writer 9937748cd8SNickeau * for list, table, blockquote 10037748cd8SNickeau * 10137748cd8SNickeau * But third party plugin can overwrite the writer 10237748cd8SNickeau * to capture the call 10337748cd8SNickeau * 10437748cd8SNickeau * See the 10537748cd8SNickeau * https://www.dokuwiki.org/devel:parser#handler_token_methods 10637748cd8SNickeau * for an example with a list component 10737748cd8SNickeau * 10837748cd8SNickeau */ 10937748cd8SNickeau $headErrorMessage = self::MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM; 11037748cd8SNickeau if (!method_exists($handler, 'getCallWriter')) { 11137748cd8SNickeau $class = get_class($handler); 11237748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($class) provided cannot manipulate the callstack (ie the function getCallWriter does not exist).", LogUtility::LVL_MSG_ERROR); 11337748cd8SNickeau return; 11437748cd8SNickeau } 11537748cd8SNickeau $callWriter = $handler->getCallWriter(); 11637748cd8SNickeau 11737748cd8SNickeau /** 11837748cd8SNickeau * Check the calls property 11937748cd8SNickeau */ 12037748cd8SNickeau $callWriterClass = get_class($callWriter); 12137748cd8SNickeau $callsPropertyFromCallWriterExists = true; 12237748cd8SNickeau try { 12337748cd8SNickeau $rp = new \ReflectionProperty($callWriterClass, "calls"); 12437748cd8SNickeau if ($rp->isPrivate()) { 12537748cd8SNickeau 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); 12637748cd8SNickeau return; 12737748cd8SNickeau } 12837748cd8SNickeau } catch (\ReflectionException $e) { 12937748cd8SNickeau $callsPropertyFromCallWriterExists = false; 13037748cd8SNickeau } 13137748cd8SNickeau 13237748cd8SNickeau /** 13337748cd8SNickeau * The calls 13437748cd8SNickeau */ 13537748cd8SNickeau if ($callsPropertyFromCallWriterExists) { 13637748cd8SNickeau 137*04fd306cSNickeau // $this->callStackType = self::CALLSTACK_WRITER; 138*04fd306cSNickeau 13937748cd8SNickeau $writerCalls = &$callWriter->calls; 14037748cd8SNickeau $this->callStack = &$writerCalls; 141*04fd306cSNickeau 14237748cd8SNickeau 14337748cd8SNickeau } else { 14437748cd8SNickeau 145*04fd306cSNickeau // $this->callStackType = self::CALLSTACK_MAIN; 146*04fd306cSNickeau 14737748cd8SNickeau /** 14837748cd8SNickeau * Check the calls property of the handler 14937748cd8SNickeau */ 15037748cd8SNickeau $handlerClass = get_class($handler); 15137748cd8SNickeau try { 15237748cd8SNickeau $rp = new \ReflectionProperty($handlerClass, "calls"); 15337748cd8SNickeau if ($rp->isPrivate()) { 15437748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the calls of the handler are private).", LogUtility::LVL_MSG_ERROR); 15537748cd8SNickeau return; 15637748cd8SNickeau } 15737748cd8SNickeau } catch (\ReflectionException $e) { 15837748cd8SNickeau LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the handler does not have any calls property).", LogUtility::LVL_MSG_ERROR); 15937748cd8SNickeau return; 16037748cd8SNickeau } 16137748cd8SNickeau 16237748cd8SNickeau /** 16337748cd8SNickeau * Initiate the callstack 16437748cd8SNickeau */ 16537748cd8SNickeau $this->callStack = &$handler->calls; 166*04fd306cSNickeau 16737748cd8SNickeau 16837748cd8SNickeau } 16937748cd8SNickeau 17037748cd8SNickeau $this->moveToEnd(); 17137748cd8SNickeau 17237748cd8SNickeau 17337748cd8SNickeau } 17437748cd8SNickeau 17537748cd8SNickeau public 176*04fd306cSNickeau static function createFromMarkup($markup): CallStack 17737748cd8SNickeau { 17837748cd8SNickeau 179*04fd306cSNickeau $handler = \ComboStrap\Parser::parseMarkupToHandler($markup); 18037748cd8SNickeau return self::createFromHandler($handler); 18137748cd8SNickeau 18237748cd8SNickeau } 18337748cd8SNickeau 1844cadd4f8SNickeau public static function createEmpty(): CallStack 1854cadd4f8SNickeau { 1864cadd4f8SNickeau $emptyHandler = new class extends \Doku_Handler { 1874cadd4f8SNickeau public $calls = []; 1884cadd4f8SNickeau 1894cadd4f8SNickeau public function getCallWriter(): object 1904cadd4f8SNickeau { 1914cadd4f8SNickeau return new class { 1924cadd4f8SNickeau public $calls = array(); 1934cadd4f8SNickeau }; 1944cadd4f8SNickeau } 1954cadd4f8SNickeau }; 1964cadd4f8SNickeau return new CallStack($emptyHandler); 1974cadd4f8SNickeau } 1984cadd4f8SNickeau 1994cadd4f8SNickeau public static function createFromInstructions(?array $callStackArray): CallStack 2004cadd4f8SNickeau { 2014cadd4f8SNickeau return CallStack::createEmpty() 202*04fd306cSNickeau ->appendAtTheEndFromNativeArrayInstructions($callStackArray); 2034cadd4f8SNickeau 2044cadd4f8SNickeau } 2054cadd4f8SNickeau 206*04fd306cSNickeau /** 207*04fd306cSNickeau * @param CallStack $callStack 208*04fd306cSNickeau * @param int $int 209*04fd306cSNickeau * @return string - the content of the call stack as if it was in the file 210*04fd306cSNickeau */ 211*04fd306cSNickeau public static function getFileContent(CallStack $callStack, int $int): string 212*04fd306cSNickeau { 213*04fd306cSNickeau $callStack->moveToStart(); 214*04fd306cSNickeau $capturedContent = ""; 215*04fd306cSNickeau while (strlen($capturedContent) < $int && ($actualCall = $callStack->next()) != false) { 216*04fd306cSNickeau $actualCapturedContent = $actualCall->getCapturedContent(); 217*04fd306cSNickeau if ($actualCapturedContent !== null) { 218*04fd306cSNickeau $capturedContent .= $actualCapturedContent; 219*04fd306cSNickeau } 220*04fd306cSNickeau } 221*04fd306cSNickeau return $capturedContent; 222*04fd306cSNickeau } 223*04fd306cSNickeau 2244cadd4f8SNickeau 22537748cd8SNickeau /** 22637748cd8SNickeau * Reset the pointer 22737748cd8SNickeau */ 22837748cd8SNickeau public 22937748cd8SNickeau function closeAndResetPointer() 23037748cd8SNickeau { 23137748cd8SNickeau reset($this->callStack); 23237748cd8SNickeau } 23337748cd8SNickeau 23437748cd8SNickeau /** 23537748cd8SNickeau * Delete from the call stack 23637748cd8SNickeau * @param $calls 23737748cd8SNickeau * @param $start 23837748cd8SNickeau * @param $end 23937748cd8SNickeau */ 24037748cd8SNickeau public 24137748cd8SNickeau static function deleteCalls(&$calls, $start, $end) 24237748cd8SNickeau { 24337748cd8SNickeau for ($i = $start; $i <= $end; $i++) { 24437748cd8SNickeau unset($calls[$i]); 24537748cd8SNickeau } 24637748cd8SNickeau } 24737748cd8SNickeau 24837748cd8SNickeau /** 24937748cd8SNickeau * @param array $calls 25037748cd8SNickeau * @param integer $position 25137748cd8SNickeau * @param array $callStackToInsert 25237748cd8SNickeau */ 25337748cd8SNickeau public 25437748cd8SNickeau static function insertCallStackUpWards(&$calls, $position, $callStackToInsert) 25537748cd8SNickeau { 25637748cd8SNickeau 25737748cd8SNickeau array_splice($calls, $position, 0, $callStackToInsert); 25837748cd8SNickeau 25937748cd8SNickeau } 26037748cd8SNickeau 26137748cd8SNickeau /** 26237748cd8SNickeau * A callstack pointer based implementation 26337748cd8SNickeau * that starts at the end 2644cadd4f8SNickeau * @param mixed|Doku_Handler $handler - mixed because we test if the handler passed is not the good one (It can happen with third plugin) 26537748cd8SNickeau * @return CallStack 26637748cd8SNickeau */ 26737748cd8SNickeau public 2684cadd4f8SNickeau static function createFromHandler(&$handler): CallStack 26937748cd8SNickeau { 27037748cd8SNickeau return new CallStack($handler); 27137748cd8SNickeau } 27237748cd8SNickeau 27337748cd8SNickeau 27437748cd8SNickeau /** 27537748cd8SNickeau * Process the EOL call to the end of stack 27637748cd8SNickeau * replacing them with paragraph call 27737748cd8SNickeau * 27837748cd8SNickeau * A sort of {@link Block::process()} but only from a tag 27937748cd8SNickeau * to the end of the current stack 28037748cd8SNickeau * 28137748cd8SNickeau * This function is used basically in the {@link DOKU_LEXER_EXIT} 28237748cd8SNickeau * state of {@link SyntaxPlugin::handle()} to create paragraph 28337748cd8SNickeau * with the class given as parameter 28437748cd8SNickeau * 2854cadd4f8SNickeau * @param array $attributes - the attributes in an callstack array form passed to the paragraph 28637748cd8SNickeau */ 28737748cd8SNickeau public 2884cadd4f8SNickeau function processEolToEndStack(array $attributes = []) 28937748cd8SNickeau { 29037748cd8SNickeau 29137748cd8SNickeau \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack($this, $attributes); 29237748cd8SNickeau 29337748cd8SNickeau } 29437748cd8SNickeau 29537748cd8SNickeau /** 29637748cd8SNickeau * Delete the call where the pointer is 29737748cd8SNickeau * And go to the previous position 29837748cd8SNickeau * 29937748cd8SNickeau * This function can be used in a next loop 30037748cd8SNickeau * 30137748cd8SNickeau * @return Call the deleted call 30237748cd8SNickeau */ 30337748cd8SNickeau public 3041fa8c418SNickeau function deleteActualCallAndPrevious(): ?Call 30537748cd8SNickeau { 30637748cd8SNickeau 30737748cd8SNickeau $actualCall = $this->getActualCall(); 30837748cd8SNickeau 30937748cd8SNickeau $offset = $this->getActualOffset(); 31037748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 31137748cd8SNickeau 31237748cd8SNickeau /** 31337748cd8SNickeau * Move to the next element (array splice reset the pointer) 31437748cd8SNickeau * if there is a eol as, we delete it 31537748cd8SNickeau * otherwise we may end up with two eol 31637748cd8SNickeau * and this is an empty paragraph 31737748cd8SNickeau */ 31837748cd8SNickeau $this->moveToOffset($offset); 31937748cd8SNickeau if (!$this->isPointerAtEnd()) { 32037748cd8SNickeau if ($this->getActualCall()->getTagName() == 'eol') { 32137748cd8SNickeau array_splice($this->callStack, $offset, 1, []); 32237748cd8SNickeau } 32337748cd8SNickeau } 32437748cd8SNickeau 32537748cd8SNickeau /** 32637748cd8SNickeau * Move to the previous element 32737748cd8SNickeau */ 32837748cd8SNickeau $this->moveToOffset($offset - 1); 32937748cd8SNickeau 33037748cd8SNickeau return $actualCall; 33137748cd8SNickeau 33237748cd8SNickeau } 33337748cd8SNickeau 33437748cd8SNickeau /** 335*04fd306cSNickeau * @return Call|null - get a reference to the actual call 33637748cd8SNickeau * This function returns a {@link Call call} object 33737748cd8SNickeau * by reference, meaning that every update will also modify the element 33837748cd8SNickeau * in the stack 33937748cd8SNickeau */ 34037748cd8SNickeau public 3414cadd4f8SNickeau function getActualCall(): ?Call 34237748cd8SNickeau { 34337748cd8SNickeau if ($this->endWasReached) { 34437748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the end of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 34537748cd8SNickeau return null; 34637748cd8SNickeau } 34737748cd8SNickeau if ($this->startWasReached) { 34837748cd8SNickeau LogUtility::msg("The actual call cannot be ask because the start of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 34937748cd8SNickeau return null; 35037748cd8SNickeau } 35137748cd8SNickeau $actualCallKey = key($this->callStack); 35237748cd8SNickeau $actualCallArray = &$this->callStack[$actualCallKey]; 35337748cd8SNickeau return new Call($actualCallArray, $actualCallKey); 35437748cd8SNickeau 35537748cd8SNickeau } 35637748cd8SNickeau 35737748cd8SNickeau /** 35837748cd8SNickeau * put the pointer one position further 35937748cd8SNickeau * false if at the end 36037748cd8SNickeau * @return false|Call 36137748cd8SNickeau */ 36237748cd8SNickeau public 36337748cd8SNickeau function next() 36437748cd8SNickeau { 36537748cd8SNickeau if ($this->startWasReached) { 36637748cd8SNickeau $this->startWasReached = false; 36737748cd8SNickeau $result = reset($this->callStack); 36837748cd8SNickeau if ($result === false) { 36937748cd8SNickeau return false; 37037748cd8SNickeau } else { 371*04fd306cSNickeau try { 37237748cd8SNickeau return $this->getActualCall(); 373*04fd306cSNickeau } catch (ExceptionCompile $e) { 374*04fd306cSNickeau // should not happen because we check that we are not at the start/end of the stack 375*04fd306cSNickeau LogUtility::msg($e->getMessage()); 376*04fd306cSNickeau return false; 377*04fd306cSNickeau } 37837748cd8SNickeau } 37937748cd8SNickeau } else { 38037748cd8SNickeau $next = next($this->callStack); 38137748cd8SNickeau if ($next === false) { 38237748cd8SNickeau $this->endWasReached = true; 383*04fd306cSNickeau return false; 38437748cd8SNickeau } else { 385*04fd306cSNickeau try { 38637748cd8SNickeau return $this->getActualCall(); 387*04fd306cSNickeau } catch (ExceptionCompile $e) { 388*04fd306cSNickeau // should not happen because we check that we are at the start/end of the stack 389*04fd306cSNickeau LogUtility::msg($e->getMessage()); 390*04fd306cSNickeau return false; 391*04fd306cSNickeau } 39237748cd8SNickeau } 39337748cd8SNickeau } 39437748cd8SNickeau 39537748cd8SNickeau } 39637748cd8SNickeau 39737748cd8SNickeau /** 39837748cd8SNickeau * 39937748cd8SNickeau * From an exit call, move the corresponding Opening call 40037748cd8SNickeau * 40137748cd8SNickeau * This is used mostly in {@link SyntaxPlugin::handle()} from a {@link DOKU_LEXER_EXIT} 40237748cd8SNickeau * to retrieve the {@link DOKU_LEXER_ENTER} call 40337748cd8SNickeau * 40437748cd8SNickeau * @return bool|Call 40537748cd8SNickeau */ 40637748cd8SNickeau public 40737748cd8SNickeau function moveToPreviousCorrespondingOpeningCall() 40837748cd8SNickeau { 40937748cd8SNickeau 41037748cd8SNickeau /** 4114cadd4f8SNickeau * Edge case 41237748cd8SNickeau */ 41337748cd8SNickeau if (empty($this->callStack)) { 41437748cd8SNickeau return false; 41537748cd8SNickeau } 41637748cd8SNickeau 41737748cd8SNickeau if (!$this->endWasReached) { 41837748cd8SNickeau $actualCall = $this->getActualCall(); 41937748cd8SNickeau $actualState = $actualCall->getState(); 42037748cd8SNickeau if ($actualState != DOKU_LEXER_EXIT) { 42137748cd8SNickeau /** 42237748cd8SNickeau * Check if we are at the end of the stack 42337748cd8SNickeau */ 42437748cd8SNickeau 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"); 42537748cd8SNickeau return false; 42637748cd8SNickeau } 42737748cd8SNickeau } 42837748cd8SNickeau $level = 0; 42937748cd8SNickeau while ($actualCall = $this->previous()) { 43037748cd8SNickeau 43137748cd8SNickeau $state = $actualCall->getState(); 43237748cd8SNickeau switch ($state) { 43337748cd8SNickeau case DOKU_LEXER_ENTER: 43437748cd8SNickeau $level++; 43537748cd8SNickeau break; 43637748cd8SNickeau case DOKU_LEXER_EXIT: 43737748cd8SNickeau $level--; 43837748cd8SNickeau break; 43937748cd8SNickeau } 44037748cd8SNickeau if ($level > 0) { 44137748cd8SNickeau break; 44237748cd8SNickeau } 44337748cd8SNickeau 44437748cd8SNickeau } 44537748cd8SNickeau if ($level > 0) { 44637748cd8SNickeau return $actualCall; 44737748cd8SNickeau } else { 44837748cd8SNickeau return false; 44937748cd8SNickeau } 45037748cd8SNickeau } 45137748cd8SNickeau 45237748cd8SNickeau 45337748cd8SNickeau /** 45437748cd8SNickeau * @return Call|false the previous call or false if there is no more previous call 45537748cd8SNickeau */ 45637748cd8SNickeau public 45737748cd8SNickeau function previous() 45837748cd8SNickeau { 45937748cd8SNickeau if ($this->endWasReached) { 46037748cd8SNickeau $this->endWasReached = false; 46137748cd8SNickeau $return = end($this->callStack); 46237748cd8SNickeau if ($return == false) { 46337748cd8SNickeau // empty array (first call on the stack) 46437748cd8SNickeau return false; 46537748cd8SNickeau } else { 46637748cd8SNickeau return $this->getActualCall(); 46737748cd8SNickeau } 46837748cd8SNickeau } else { 46937748cd8SNickeau $prev = prev($this->callStack); 47037748cd8SNickeau if ($prev === false) { 47137748cd8SNickeau $this->startWasReached = true; 47237748cd8SNickeau return $prev; 47337748cd8SNickeau } else { 47437748cd8SNickeau return $this->getActualCall(); 47537748cd8SNickeau } 47637748cd8SNickeau } 47737748cd8SNickeau 47837748cd8SNickeau } 47937748cd8SNickeau 48037748cd8SNickeau /** 48137748cd8SNickeau * Return the first enter or special child call (ie a tag) 48237748cd8SNickeau * @return Call|false 48337748cd8SNickeau */ 48437748cd8SNickeau public 48537748cd8SNickeau function moveToFirstChildTag() 48637748cd8SNickeau { 48737748cd8SNickeau $found = false; 48837748cd8SNickeau while ($this->next()) { 48937748cd8SNickeau 49037748cd8SNickeau $actualCall = $this->getActualCall(); 49137748cd8SNickeau $state = $actualCall->getState(); 49237748cd8SNickeau switch ($state) { 49337748cd8SNickeau case DOKU_LEXER_ENTER: 49437748cd8SNickeau case DOKU_LEXER_SPECIAL: 49537748cd8SNickeau $found = true; 49637748cd8SNickeau break 2; 49737748cd8SNickeau case DOKU_LEXER_EXIT: 49837748cd8SNickeau break 2; 49937748cd8SNickeau } 50037748cd8SNickeau 50137748cd8SNickeau } 50237748cd8SNickeau if ($found) { 50337748cd8SNickeau return $this->getActualCall(); 50437748cd8SNickeau } else { 50537748cd8SNickeau return false; 50637748cd8SNickeau } 50737748cd8SNickeau 50837748cd8SNickeau 50937748cd8SNickeau } 51037748cd8SNickeau 51137748cd8SNickeau /** 51237748cd8SNickeau * The end is the one after the last element 51337748cd8SNickeau */ 51437748cd8SNickeau public 51537748cd8SNickeau function moveToEnd() 51637748cd8SNickeau { 51737748cd8SNickeau if ($this->startWasReached) { 51837748cd8SNickeau $this->startWasReached = false; 51937748cd8SNickeau } 52037748cd8SNickeau end($this->callStack); 5214cadd4f8SNickeau return $this->next(); 52237748cd8SNickeau } 52337748cd8SNickeau 52437748cd8SNickeau /** 52537748cd8SNickeau * On the same level 52637748cd8SNickeau */ 52737748cd8SNickeau public 52837748cd8SNickeau function moveToNextSiblingTag() 52937748cd8SNickeau { 53037748cd8SNickeau 53137748cd8SNickeau /** 532*04fd306cSNickeau * Edge case 53337748cd8SNickeau */ 53437748cd8SNickeau if (empty($this->callStack)) { 53537748cd8SNickeau return false; 53637748cd8SNickeau } 53737748cd8SNickeau 538*04fd306cSNickeau if($this->endWasReached){ 539*04fd306cSNickeau return false; 540*04fd306cSNickeau } 541*04fd306cSNickeau 54237748cd8SNickeau $actualCall = $this->getActualCall(); 5434cadd4f8SNickeau $enterState = $actualCall->getState(); 5444cadd4f8SNickeau if (!in_array($enterState, CallStack::TAG_STATE)) { 545*04fd306cSNickeau LogUtility::msg("A next sibling can be asked only from a tag call. The state is $enterState", LogUtility::LVL_MSG_ERROR, "support"); 54637748cd8SNickeau return false; 54737748cd8SNickeau } 54837748cd8SNickeau $level = 0; 54937748cd8SNickeau while ($this->next()) { 55037748cd8SNickeau 55137748cd8SNickeau $actualCall = $this->getActualCall(); 55237748cd8SNickeau $state = $actualCall->getState(); 55337748cd8SNickeau switch ($state) { 55437748cd8SNickeau case DOKU_LEXER_ENTER: 55537748cd8SNickeau $level++; 55637748cd8SNickeau break; 5574cadd4f8SNickeau case DOKU_LEXER_SPECIAL: 5584cadd4f8SNickeau if ($enterState === DOKU_LEXER_SPECIAL) { 5594cadd4f8SNickeau break; 5604cadd4f8SNickeau } else { 5614cadd4f8SNickeau // ENTER TAG 5624cadd4f8SNickeau continue 2; 5634cadd4f8SNickeau } 56437748cd8SNickeau case DOKU_LEXER_EXIT: 56537748cd8SNickeau $level--; 56637748cd8SNickeau break; 56737748cd8SNickeau } 56837748cd8SNickeau 56937748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 57037748cd8SNickeau break; 57137748cd8SNickeau } 57237748cd8SNickeau } 57337748cd8SNickeau if ($level == 0 && !$this->endWasReached) { 57437748cd8SNickeau return $this->getActualCall(); 57537748cd8SNickeau } else { 57637748cd8SNickeau return false; 57737748cd8SNickeau } 57837748cd8SNickeau } 57937748cd8SNickeau 58037748cd8SNickeau /** 58137748cd8SNickeau * @param Call $call 58237748cd8SNickeau * @return Call the inserted call 58337748cd8SNickeau */ 58437748cd8SNickeau public 5851fa8c418SNickeau function insertBefore(Call $call): Call 58637748cd8SNickeau { 58737748cd8SNickeau if ($this->endWasReached) { 58837748cd8SNickeau 58937748cd8SNickeau $this->callStack[] = $call->toCallArray(); 59037748cd8SNickeau 59137748cd8SNickeau } else { 59237748cd8SNickeau 59337748cd8SNickeau $offset = $this->getActualOffset(); 59437748cd8SNickeau array_splice($this->callStack, $offset, 0, [$call->toCallArray()]); 59537748cd8SNickeau // array splice reset the pointer 59637748cd8SNickeau // we move it to the actual element (ie the key is offset +1) 597*04fd306cSNickeau try { 598*04fd306cSNickeau $targetOffset = $offset + 1; 599*04fd306cSNickeau $this->moveToOffset($targetOffset); 600*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 601*04fd306cSNickeau /** 602*04fd306cSNickeau * We don't throw because we should be able to add before at any index 603*04fd306cSNickeau */ 604*04fd306cSNickeau if (PluginUtility::isDevOrTest()) { 605*04fd306cSNickeau LogUtility::error("Unable to move the callback pointer to the offset ($targetOffset)", self::CANONICAL); 606*04fd306cSNickeau } 607*04fd306cSNickeau } 60837748cd8SNickeau 60937748cd8SNickeau } 61037748cd8SNickeau return $call; 61137748cd8SNickeau } 61237748cd8SNickeau 61337748cd8SNickeau /** 61437748cd8SNickeau * Move pointer by offset 61537748cd8SNickeau * @param $offset 616*04fd306cSNickeau * @throws ExceptionBadArgument 61737748cd8SNickeau */ 61837748cd8SNickeau private 61937748cd8SNickeau function moveToOffset($offset) 62037748cd8SNickeau { 621*04fd306cSNickeau if ($offset < 0) { 622*04fd306cSNickeau if ($offset === -1) { 623*04fd306cSNickeau $this->moveToStart(); 624*04fd306cSNickeau return; 625*04fd306cSNickeau } 626*04fd306cSNickeau throw new ExceptionBadArgument("The offset value of ($offset) is off limit"); 627*04fd306cSNickeau } 62837748cd8SNickeau $this->resetPointer(); 62937748cd8SNickeau for ($i = 0; $i < $offset; $i++) { 63037748cd8SNickeau $result = $this->next(); 63137748cd8SNickeau if ($result === false) { 63237748cd8SNickeau break; 63337748cd8SNickeau } 63437748cd8SNickeau } 63537748cd8SNickeau } 63637748cd8SNickeau 63737748cd8SNickeau /** 63837748cd8SNickeau * Move pointer by key 63937748cd8SNickeau * @param $targetKey 64037748cd8SNickeau */ 641*04fd306cSNickeau public function moveToKey($targetKey) 64237748cd8SNickeau { 64337748cd8SNickeau $this->resetPointer(); 64437748cd8SNickeau for ($i = 0; $i < $targetKey; $i++) { 64537748cd8SNickeau next($this->callStack); 64637748cd8SNickeau } 64737748cd8SNickeau $actualKey = key($this->callStack); 64837748cd8SNickeau if ($actualKey != $targetKey) { 64937748cd8SNickeau LogUtility::msg("The target key ($targetKey) is not equal to the actual key ($actualKey). The moveToKey was not successful"); 65037748cd8SNickeau } 65137748cd8SNickeau } 65237748cd8SNickeau 65337748cd8SNickeau /** 654*04fd306cSNickeau * Insert After. The pointer stays at the current location. 655*04fd306cSNickeau * If you need to process the call that you just 65637748cd8SNickeau * inserted, you may want to call {@link CallStack::next()} 65737748cd8SNickeau * @param Call $call 658*04fd306cSNickeau * @return void - next to go the inserted element 65937748cd8SNickeau */ 66037748cd8SNickeau public 661*04fd306cSNickeau function insertAfter(Call $call): void 66237748cd8SNickeau { 66337748cd8SNickeau $actualKey = key($this->callStack); 664*04fd306cSNickeau if ($actualKey !== null) { 66537748cd8SNickeau $offset = array_search($actualKey, array_keys($this->callStack), true); 66637748cd8SNickeau array_splice($this->callStack, $offset + 1, 0, [$call->toCallArray()]); 66737748cd8SNickeau // array splice reset the pointer 66837748cd8SNickeau // we move it to the actual element 66937748cd8SNickeau $this->moveToKey($actualKey); 670*04fd306cSNickeau return; 67137748cd8SNickeau } 672*04fd306cSNickeau 673*04fd306cSNickeau if ($this->endWasReached === true) { 674*04fd306cSNickeau $this->callStack[] = $call->toCallArray(); 675*04fd306cSNickeau return; 676*04fd306cSNickeau } 677*04fd306cSNickeau if ($this->startWasReached === true) { 678*04fd306cSNickeau // since 4+ 679*04fd306cSNickeau array_unshift($this->callStack, $call->toCallArray()); 680*04fd306cSNickeau $this->previous(); 681*04fd306cSNickeau return; 682*04fd306cSNickeau } 683*04fd306cSNickeau LogUtility::msg("Callstack: Actual key is null, we can't insert after null"); 684*04fd306cSNickeau 685*04fd306cSNickeau 68637748cd8SNickeau } 68737748cd8SNickeau 68837748cd8SNickeau public 68937748cd8SNickeau function getActualKey() 69037748cd8SNickeau { 69137748cd8SNickeau return key($this->callStack); 69237748cd8SNickeau } 69337748cd8SNickeau 69437748cd8SNickeau /** 69537748cd8SNickeau * Insert an EOL call if the next call is not an EOL 69637748cd8SNickeau * This is to enforce an new paragraph 69737748cd8SNickeau */ 69837748cd8SNickeau public 69937748cd8SNickeau function insertEolIfNextCallIsNotEolOrBlock() 70037748cd8SNickeau { 70137748cd8SNickeau if (!$this->isPointerAtEnd()) { 70237748cd8SNickeau $nextCall = $this->next(); 70337748cd8SNickeau if ($nextCall != false) { 70437748cd8SNickeau if ($nextCall->getTagName() != "eol" && $nextCall->getDisplay() != "block") { 70537748cd8SNickeau $this->insertBefore( 70637748cd8SNickeau Call::createNativeCall("eol") 70737748cd8SNickeau ); 70837748cd8SNickeau // move on the eol 70937748cd8SNickeau $this->previous(); 71037748cd8SNickeau } 71137748cd8SNickeau // move back 71237748cd8SNickeau $this->previous(); 71337748cd8SNickeau } 71437748cd8SNickeau } 71537748cd8SNickeau } 71637748cd8SNickeau 71737748cd8SNickeau private 71837748cd8SNickeau function isPointerAtEnd() 71937748cd8SNickeau { 72037748cd8SNickeau return $this->endWasReached; 72137748cd8SNickeau } 72237748cd8SNickeau 72337748cd8SNickeau public 72437748cd8SNickeau function &getHandler() 72537748cd8SNickeau { 72637748cd8SNickeau return $this->handler; 72737748cd8SNickeau } 72837748cd8SNickeau 72937748cd8SNickeau /** 73037748cd8SNickeau * Return The offset (not the key): 73137748cd8SNickeau * * starting at 0 for the first element 73237748cd8SNickeau * * 1 for the second ... 73337748cd8SNickeau * 73437748cd8SNickeau * @return false|int|string 73537748cd8SNickeau */ 73637748cd8SNickeau private 73737748cd8SNickeau function getActualOffset() 73837748cd8SNickeau { 73937748cd8SNickeau $actualKey = key($this->callStack); 74037748cd8SNickeau return array_search($actualKey, array_keys($this->callStack), true); 74137748cd8SNickeau } 74237748cd8SNickeau 74337748cd8SNickeau private 74437748cd8SNickeau function resetPointer() 74537748cd8SNickeau { 74637748cd8SNickeau reset($this->callStack); 74737748cd8SNickeau $this->endWasReached = false; 74837748cd8SNickeau } 74937748cd8SNickeau 75037748cd8SNickeau public 75137748cd8SNickeau function moveToStart() 75237748cd8SNickeau { 75337748cd8SNickeau $this->resetPointer(); 7544cadd4f8SNickeau return $this->previous(); 75537748cd8SNickeau } 75637748cd8SNickeau 75737748cd8SNickeau /** 75837748cd8SNickeau * @return Call|false the parent call or false if there is no parent 75937748cd8SNickeau * If you are on an {@link DOKU_LEXER_EXIT} state, you should 76037748cd8SNickeau * call first the {@link CallStack::moveToPreviousCorrespondingOpeningCall()} 76137748cd8SNickeau */ 76237748cd8SNickeau public function moveToParent() 76337748cd8SNickeau { 76437748cd8SNickeau 76537748cd8SNickeau /** 76637748cd8SNickeau * Case when we start from the exit state element 76737748cd8SNickeau * We go first to the opening tag 76837748cd8SNickeau * because the algorithm is level based. 76937748cd8SNickeau * 77037748cd8SNickeau * When the end is reached, there is no call 77137748cd8SNickeau * (this not the {@link end php end} but one further 77237748cd8SNickeau */ 77337748cd8SNickeau if (!$this->endWasReached && !$this->startWasReached && $this->getActualCall()->getState() == DOKU_LEXER_EXIT) { 77437748cd8SNickeau 77537748cd8SNickeau $this->moveToPreviousCorrespondingOpeningCall(); 77637748cd8SNickeau 77737748cd8SNickeau } 77837748cd8SNickeau 77937748cd8SNickeau 78037748cd8SNickeau /** 78137748cd8SNickeau * We are in a parent when the tree level is negative 78237748cd8SNickeau */ 78337748cd8SNickeau $treeLevel = 0; 78437748cd8SNickeau while ($actualCall = $this->previous()) { 78537748cd8SNickeau 78637748cd8SNickeau /** 78737748cd8SNickeau * Add 78837748cd8SNickeau * would become a parent on its enter state 78937748cd8SNickeau */ 79037748cd8SNickeau $actualCallState = $actualCall->getState(); 79137748cd8SNickeau switch ($actualCallState) { 79237748cd8SNickeau case DOKU_LEXER_ENTER: 79337748cd8SNickeau $treeLevel = $treeLevel - 1; 79437748cd8SNickeau break; 79537748cd8SNickeau case DOKU_LEXER_EXIT: 79637748cd8SNickeau /** 79737748cd8SNickeau * When the tag has a sibling with an exit tag 79837748cd8SNickeau */ 79937748cd8SNickeau $treeLevel = $treeLevel + 1; 80037748cd8SNickeau break; 80137748cd8SNickeau } 80237748cd8SNickeau 80337748cd8SNickeau /** 80437748cd8SNickeau * The breaking statement 80537748cd8SNickeau */ 80637748cd8SNickeau if ($treeLevel < 0) { 80737748cd8SNickeau break; 80837748cd8SNickeau } 80937748cd8SNickeau 81037748cd8SNickeau } 81137748cd8SNickeau return $actualCall; 81237748cd8SNickeau 81337748cd8SNickeau 81437748cd8SNickeau } 81537748cd8SNickeau 81637748cd8SNickeau /** 81737748cd8SNickeau * Delete the anchor link to the image (ie the lightbox) 81837748cd8SNickeau * 81937748cd8SNickeau * This is used in navigation and for instance 82037748cd8SNickeau * in heading 82137748cd8SNickeau */ 82237748cd8SNickeau public function processNoLinkOnImageToEndStack() 82337748cd8SNickeau { 82437748cd8SNickeau while ($this->next()) { 82537748cd8SNickeau $actualCall = $this->getActualCall(); 82637748cd8SNickeau if ($actualCall->getTagName() == syntax_plugin_combo_media::TAG) { 827*04fd306cSNickeau $actualCall->addAttribute(MediaMarkup::LINKING_KEY, MediaMarkup::LINKING_NOLINK_VALUE); 82837748cd8SNickeau } 82937748cd8SNickeau } 83037748cd8SNickeau } 83137748cd8SNickeau 83237748cd8SNickeau /** 83337748cd8SNickeau * Append instructions to the callstack (ie at the end) 83437748cd8SNickeau * @param array $instructions 8354cadd4f8SNickeau * @return CallStack 83637748cd8SNickeau */ 837*04fd306cSNickeau public function appendAtTheEndFromNativeArrayInstructions(array $instructions): CallStack 83837748cd8SNickeau { 83937748cd8SNickeau array_splice($this->callStack, count($this->callStack), 0, $instructions); 8404cadd4f8SNickeau return $this; 84137748cd8SNickeau } 84237748cd8SNickeau 84337748cd8SNickeau /** 844*04fd306cSNickeau * @param array $instructions 845*04fd306cSNickeau * @return $this 846*04fd306cSNickeau * The key is the actual 847*04fd306cSNickeau */ 848*04fd306cSNickeau public function insertAfterFromNativeArrayInstructions(array $instructions): CallStack 849*04fd306cSNickeau { 850*04fd306cSNickeau $offset = null; 851*04fd306cSNickeau $actualKey = $this->getActualKey(); 852*04fd306cSNickeau if ($actualKey !== null) { 853*04fd306cSNickeau $offset = $actualKey + 1; 854*04fd306cSNickeau } 855*04fd306cSNickeau array_splice($this->callStack, $offset, 0, $instructions); 856*04fd306cSNickeau if ($actualKey !== null) { 857*04fd306cSNickeau $this->moveToKey($actualKey); 858*04fd306cSNickeau } 859*04fd306cSNickeau return $this; 860*04fd306cSNickeau } 861*04fd306cSNickeau 862*04fd306cSNickeau /** 86337748cd8SNickeau * @param Call $call 86437748cd8SNickeau */ 865*04fd306cSNickeau public function appendCallAtTheEnd(Call $call) 86637748cd8SNickeau { 86737748cd8SNickeau $this->callStack[] = $call->toCallArray(); 86837748cd8SNickeau } 86937748cd8SNickeau 87037748cd8SNickeau public function moveToPreviousSiblingTag() 87137748cd8SNickeau { 87237748cd8SNickeau /** 87337748cd8SNickeau * Edge case 87437748cd8SNickeau */ 87537748cd8SNickeau if (empty($this->callStack)) { 87637748cd8SNickeau return false; 87737748cd8SNickeau } 87837748cd8SNickeau 8794cadd4f8SNickeau $enterState = null; 88037748cd8SNickeau if (!$this->endWasReached) { 88137748cd8SNickeau $actualCall = $this->getActualCall(); 8824cadd4f8SNickeau $enterState = $actualCall->getState(); 8834cadd4f8SNickeau if (!in_array($enterState, CallStack::TAG_STATE)) { 884*04fd306cSNickeau LogUtility::msg("A previous sibling can be asked only from a tag call. The state is $enterState", LogUtility::LVL_MSG_ERROR, "support"); 88537748cd8SNickeau return false; 88637748cd8SNickeau } 88737748cd8SNickeau } 88837748cd8SNickeau $level = 0; 8894cadd4f8SNickeau while ($actualCall = $this->previous()) { 89037748cd8SNickeau 89137748cd8SNickeau $state = $actualCall->getState(); 89237748cd8SNickeau switch ($state) { 89337748cd8SNickeau case DOKU_LEXER_ENTER: 89437748cd8SNickeau $level++; 89537748cd8SNickeau break; 8964cadd4f8SNickeau case DOKU_LEXER_SPECIAL: 8974cadd4f8SNickeau if ($enterState === DOKU_LEXER_SPECIAL) { 8984cadd4f8SNickeau break; 8994cadd4f8SNickeau } else { 9004cadd4f8SNickeau continue 2; 9014cadd4f8SNickeau } 90237748cd8SNickeau case DOKU_LEXER_EXIT: 90337748cd8SNickeau $level--; 90437748cd8SNickeau break; 9054cadd4f8SNickeau default: 9064cadd4f8SNickeau // cdata 9074cadd4f8SNickeau continue 2; 90837748cd8SNickeau } 90937748cd8SNickeau 91037748cd8SNickeau if ($level == 0 && in_array($state, self::TAG_STATE)) { 91137748cd8SNickeau break; 91237748cd8SNickeau } 91337748cd8SNickeau } 91437748cd8SNickeau if ($level == 0 && !$this->startWasReached) { 91537748cd8SNickeau return $this->getActualCall(); 91637748cd8SNickeau } else { 91737748cd8SNickeau return false; 91837748cd8SNickeau } 91937748cd8SNickeau } 92037748cd8SNickeau 92137748cd8SNickeau /** 92237748cd8SNickeau * Delete all calls after the passed call 92337748cd8SNickeau * 92437748cd8SNickeau * It's used in syntax generator that: 92537748cd8SNickeau * * capture the children callstack at the end, 92637748cd8SNickeau * * delete it 92737748cd8SNickeau * * and use it to generate more calls. 92837748cd8SNickeau * 92937748cd8SNickeau * @param Call $call 93037748cd8SNickeau */ 93137748cd8SNickeau public function deleteAllCallsAfter(Call $call) 93237748cd8SNickeau { 93337748cd8SNickeau $key = $call->getKey(); 93437748cd8SNickeau $offset = array_search($key, array_keys($this->callStack), true); 93537748cd8SNickeau if ($offset !== false) { 93637748cd8SNickeau /** 93737748cd8SNickeau * We delete from the next 93837748cd8SNickeau * {@link array_splice()} delete also the given offset 93937748cd8SNickeau */ 94037748cd8SNickeau array_splice($this->callStack, $offset + 1); 94137748cd8SNickeau } else { 94237748cd8SNickeau LogUtility::msg("The call ($call) could not be found in the callStack. We couldn't therefore delete the calls after"); 94337748cd8SNickeau } 94437748cd8SNickeau 94537748cd8SNickeau } 94637748cd8SNickeau 94737748cd8SNickeau /** 94837748cd8SNickeau * @param Call[] $calls 94937748cd8SNickeau */ 95037748cd8SNickeau public function appendInstructionsFromCallObjects($calls) 95137748cd8SNickeau { 95237748cd8SNickeau foreach ($calls as $call) { 95337748cd8SNickeau $this->appendCallAtTheEnd($call); 95437748cd8SNickeau } 955c3437056SNickeau 956c3437056SNickeau } 957c3437056SNickeau 958c3437056SNickeau /** 959c3437056SNickeau * 960c3437056SNickeau * @return int|mixed - the last position on the callstack 961c3437056SNickeau * If you are at the end of the callstack after a full parsing, 962c3437056SNickeau * this should be the length of the string of the page 963c3437056SNickeau */ 964c3437056SNickeau public function getLastCharacterPosition() 965c3437056SNickeau { 966c3437056SNickeau $offset = $this->getActualOffset(); 967c3437056SNickeau 968c3437056SNickeau $lastEndPosition = 0; 969c3437056SNickeau $this->moveToEnd(); 970c3437056SNickeau while ($actualCall = $this->previous()) { 971c3437056SNickeau // p_open and p_close have always a position value of 0 972c3437056SNickeau $lastEndPosition = $actualCall->getLastMatchedCharacterPosition(); 973c3437056SNickeau if ($lastEndPosition !== 0) { 974c3437056SNickeau break; 975c3437056SNickeau } 976c3437056SNickeau } 977c3437056SNickeau if ($offset == null) { 978c3437056SNickeau $this->moveToEnd(); 979c3437056SNickeau } else { 980c3437056SNickeau $this->moveToOffset($offset); 981c3437056SNickeau } 982c3437056SNickeau return $lastEndPosition; 98337748cd8SNickeau 98437748cd8SNickeau } 98537748cd8SNickeau 9864cadd4f8SNickeau public function getStack(): array 9874cadd4f8SNickeau { 9884cadd4f8SNickeau return $this->callStack; 9894cadd4f8SNickeau } 9904cadd4f8SNickeau 9914cadd4f8SNickeau public function moveToFirstEnterTag() 9924cadd4f8SNickeau { 9934cadd4f8SNickeau 9944cadd4f8SNickeau while ($actualCall = $this->next()) { 9954cadd4f8SNickeau 9964cadd4f8SNickeau if ($actualCall->getState() === DOKU_LEXER_ENTER) { 9974cadd4f8SNickeau return $this->getActualCall(); 9984cadd4f8SNickeau } 9994cadd4f8SNickeau } 10004cadd4f8SNickeau return false; 10014cadd4f8SNickeau 10024cadd4f8SNickeau } 10034cadd4f8SNickeau 10044cadd4f8SNickeau /** 10054cadd4f8SNickeau * Move the pointer to the corresponding exit call 10064cadd4f8SNickeau * and return it or false if not found 10074cadd4f8SNickeau * @return Call|false 10084cadd4f8SNickeau */ 10094cadd4f8SNickeau public function moveToNextCorrespondingExitTag() 10104cadd4f8SNickeau { 10114cadd4f8SNickeau /** 10124cadd4f8SNickeau * Edge case 10134cadd4f8SNickeau */ 10144cadd4f8SNickeau if (empty($this->callStack)) { 10154cadd4f8SNickeau return false; 10164cadd4f8SNickeau } 10174cadd4f8SNickeau 10184cadd4f8SNickeau /** 10194cadd4f8SNickeau * Check if we are on an enter tag 10204cadd4f8SNickeau */ 10214cadd4f8SNickeau $actualCall = $this->getActualCall(); 10224cadd4f8SNickeau if ($actualCall === null) { 10234cadd4f8SNickeau LogUtility::msg("You are not on the stack (start or end), you can't ask for the corresponding exit call", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 10244cadd4f8SNickeau return false; 10254cadd4f8SNickeau } 10264cadd4f8SNickeau $actualState = $actualCall->getState(); 10274cadd4f8SNickeau if ($actualState != DOKU_LEXER_ENTER) { 10284cadd4f8SNickeau LogUtility::msg("You are not on an enter tag ($actualState). You can't ask for the corresponding exit call .", LogUtility::LVL_MSG_ERROR, self::CANONICAL); 10294cadd4f8SNickeau return false; 10304cadd4f8SNickeau } 10314cadd4f8SNickeau 10324cadd4f8SNickeau $level = 0; 10334cadd4f8SNickeau while ($actualCall = $this->next()) { 10344cadd4f8SNickeau 10354cadd4f8SNickeau $state = $actualCall->getState(); 10364cadd4f8SNickeau switch ($state) { 10374cadd4f8SNickeau case DOKU_LEXER_ENTER: 10384cadd4f8SNickeau $level++; 10394cadd4f8SNickeau break; 10404cadd4f8SNickeau case DOKU_LEXER_EXIT: 10414cadd4f8SNickeau $level--; 10424cadd4f8SNickeau break; 10434cadd4f8SNickeau } 10444cadd4f8SNickeau if ($level < 0) { 10454cadd4f8SNickeau break; 10464cadd4f8SNickeau } 10474cadd4f8SNickeau 10484cadd4f8SNickeau } 10494cadd4f8SNickeau if ($level < 0) { 10504cadd4f8SNickeau return $actualCall; 10514cadd4f8SNickeau } else { 10524cadd4f8SNickeau return false; 10534cadd4f8SNickeau } 10544cadd4f8SNickeau 10554cadd4f8SNickeau } 10564cadd4f8SNickeau 10574cadd4f8SNickeau public function moveToCall(Call $call): ?Call 10584cadd4f8SNickeau { 10594cadd4f8SNickeau $targetKey = $call->getKey(); 10604cadd4f8SNickeau $actualKey = $this->getActualKey(); 1061*04fd306cSNickeau if ($actualKey === null) { 1062*04fd306cSNickeau if ($this->endWasReached) { 1063*04fd306cSNickeau $actualKey = sizeof($this->callStack); 1064*04fd306cSNickeau } 1065*04fd306cSNickeau if ($this->startWasReached) { 1066*04fd306cSNickeau $actualKey = -1; 1067*04fd306cSNickeau } 1068*04fd306cSNickeau } 10694cadd4f8SNickeau $diff = $targetKey - $actualKey; 10704cadd4f8SNickeau for ($i = 0; $i < abs($diff); $i++) { 10714cadd4f8SNickeau if ($diff > 0) { 10724cadd4f8SNickeau $this->next(); 10734cadd4f8SNickeau } else { 10744cadd4f8SNickeau $this->previous(); 10754cadd4f8SNickeau } 10764cadd4f8SNickeau } 1077*04fd306cSNickeau if ($this->endWasReached) { 1078*04fd306cSNickeau return null; 1079*04fd306cSNickeau } 1080*04fd306cSNickeau if ($this->startWasReached) { 1081*04fd306cSNickeau return null; 1082*04fd306cSNickeau } 10834cadd4f8SNickeau return $this->getActualCall(); 10844cadd4f8SNickeau } 10854cadd4f8SNickeau 108637748cd8SNickeau 1087*04fd306cSNickeau /** 1088*04fd306cSNickeau * Delete all call before (Don't delete the passed call) 1089*04fd306cSNickeau * @param Call $call 1090*04fd306cSNickeau * @return void 1091*04fd306cSNickeau */ 1092*04fd306cSNickeau public function deleteAllCallsBefore(Call $call) 1093*04fd306cSNickeau { 1094*04fd306cSNickeau $key = $call->getKey(); 1095*04fd306cSNickeau $offset = array_search($key, array_keys($this->callStack), true); 1096*04fd306cSNickeau if ($offset !== false) { 1097*04fd306cSNickeau /** 1098*04fd306cSNickeau * We delete from the next 1099*04fd306cSNickeau * {@link array_splice()} delete also the given offset 1100*04fd306cSNickeau */ 1101*04fd306cSNickeau array_splice($this->callStack, 0, $offset); 1102*04fd306cSNickeau } else { 1103*04fd306cSNickeau LogUtility::msg("The call ($call) could not be found in the callStack. We couldn't therefore delete the before"); 1104*04fd306cSNickeau } 1105*04fd306cSNickeau 1106*04fd306cSNickeau } 1107*04fd306cSNickeau 1108*04fd306cSNickeau public function isAtEnd(): bool 1109*04fd306cSNickeau { 1110*04fd306cSNickeau return $this->endWasReached; 1111*04fd306cSNickeau } 1112*04fd306cSNickeau 1113*04fd306cSNickeau public function empty() 1114*04fd306cSNickeau { 1115*04fd306cSNickeau $this->callStack = []; 1116*04fd306cSNickeau } 1117*04fd306cSNickeau 1118*04fd306cSNickeau /** 1119*04fd306cSNickeau * @return Call[] 1120*04fd306cSNickeau */ 1121*04fd306cSNickeau public function getChildren(): array 1122*04fd306cSNickeau { 1123*04fd306cSNickeau $children = []; 1124*04fd306cSNickeau $firstChildTag = $this->moveToFirstChildTag(); 1125*04fd306cSNickeau if ($firstChildTag == false) { 1126*04fd306cSNickeau return $children; 1127*04fd306cSNickeau } 1128*04fd306cSNickeau $children[] = $firstChildTag; 1129*04fd306cSNickeau while ($actualCall = $this->moveToNextSiblingTag()) { 1130*04fd306cSNickeau $children[] = $actualCall; 1131*04fd306cSNickeau } 1132*04fd306cSNickeau return $children; 1133*04fd306cSNickeau } 1134*04fd306cSNickeau 1135*04fd306cSNickeau public function appendCallsAtTheEnd(array $calls) 1136*04fd306cSNickeau { 1137*04fd306cSNickeau foreach($calls as $call){ 1138*04fd306cSNickeau $this->appendCallAtTheEnd($call); 1139*04fd306cSNickeau } 1140*04fd306cSNickeau } 1141*04fd306cSNickeau 1142*04fd306cSNickeau 114337748cd8SNickeau} 1144