xref: /template/strap/ComboStrap/CallStack.php (revision 4cadd4f8c541149bdda95f080e38a6d4e3a640ca)
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";
5437748cd8SNickeau
5537748cd8SNickeau    private $handler;
5637748cd8SNickeau
5737748cd8SNickeau    /**
5837748cd8SNickeau     * The max key of the calls
5937748cd8SNickeau     * @var int|null
6037748cd8SNickeau     */
6137748cd8SNickeau    private $maxIndex = 0;
6237748cd8SNickeau
6337748cd8SNickeau    /**
6437748cd8SNickeau     * @var array the call stack
6537748cd8SNickeau     */
6637748cd8SNickeau    private $callStack = [];
6737748cd8SNickeau
6837748cd8SNickeau    /**
6937748cd8SNickeau     * A pointer to keep the information
7037748cd8SNickeau     * if we have gone to far in the stack
7137748cd8SNickeau     * (because you lost the fact that you are outside
7237748cd8SNickeau     * the boundary, ie you can do a {@link \prev}` after that a {@link \next} return false
7337748cd8SNickeau     * @var bool
7437748cd8SNickeau     * If true, we are at the offset: end of th array + 1
7537748cd8SNickeau     */
7637748cd8SNickeau    private $endWasReached = false;
7737748cd8SNickeau    /**
7837748cd8SNickeau     * If true, we are at the offset: start of th array - 1
7937748cd8SNickeau     * You can use {@link CallStack::next()}
8037748cd8SNickeau     * @var bool
8137748cd8SNickeau     */
8237748cd8SNickeau    private $startWasReached = false;
8337748cd8SNickeau
8437748cd8SNickeau    /**
8537748cd8SNickeau     * @var string the type of callstack
8637748cd8SNickeau     */
8737748cd8SNickeau    private $callStackType = "unknown";
8837748cd8SNickeau
8937748cd8SNickeau    /**
9037748cd8SNickeau     * A callstack is a pointer implementation to manipulate
9137748cd8SNickeau     * the {@link Doku_Handler::$calls call stack of the handler}
9237748cd8SNickeau     *
9337748cd8SNickeau     * When you create a callstack object, the pointer
9437748cd8SNickeau     * is located at the end.
9537748cd8SNickeau     *
9637748cd8SNickeau     * If you want to reset the pointer, you need
9737748cd8SNickeau     * to call the {@link CallStack::closeAndResetPointer()} function
9837748cd8SNickeau     *
9937748cd8SNickeau     * @param \Doku_Handler
10037748cd8SNickeau     */
10137748cd8SNickeau    public function __construct(&$handler)
10237748cd8SNickeau    {
10337748cd8SNickeau        $this->handler = $handler;
10437748cd8SNickeau
10537748cd8SNickeau        /**
10637748cd8SNickeau         * A temporary Call stack is created in the writer
10737748cd8SNickeau         * for list, table, blockquote
10837748cd8SNickeau         *
10937748cd8SNickeau         * But third party plugin can overwrite the writer
11037748cd8SNickeau         * to capture the call
11137748cd8SNickeau         *
11237748cd8SNickeau         * See the
11337748cd8SNickeau         * https://www.dokuwiki.org/devel:parser#handler_token_methods
11437748cd8SNickeau         * for an example with a list component
11537748cd8SNickeau         *
11637748cd8SNickeau         */
11737748cd8SNickeau        $headErrorMessage = self::MESSAGE_PREFIX_CALLSTACK_NOT_CONFORM;
11837748cd8SNickeau        if (!method_exists($handler, 'getCallWriter')) {
11937748cd8SNickeau            $class = get_class($handler);
12037748cd8SNickeau            LogUtility::msg("$headErrorMessage. The handler ($class) provided cannot manipulate the callstack (ie the function getCallWriter does not exist).", LogUtility::LVL_MSG_ERROR);
12137748cd8SNickeau            return;
12237748cd8SNickeau        }
12337748cd8SNickeau        $callWriter = $handler->getCallWriter();
12437748cd8SNickeau
12537748cd8SNickeau        /**
12637748cd8SNickeau         * Check the calls property
12737748cd8SNickeau         */
12837748cd8SNickeau        $callWriterClass = get_class($callWriter);
12937748cd8SNickeau        $callsPropertyFromCallWriterExists = true;
13037748cd8SNickeau        try {
13137748cd8SNickeau            $rp = new \ReflectionProperty($callWriterClass, "calls");
13237748cd8SNickeau            if ($rp->isPrivate()) {
13337748cd8SNickeau                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);
13437748cd8SNickeau                return;
13537748cd8SNickeau            }
13637748cd8SNickeau        } catch (\ReflectionException $e) {
13737748cd8SNickeau            $callsPropertyFromCallWriterExists = false;
13837748cd8SNickeau        }
13937748cd8SNickeau
14037748cd8SNickeau        /**
14137748cd8SNickeau         * The calls
14237748cd8SNickeau         */
14337748cd8SNickeau        if ($callsPropertyFromCallWriterExists) {
14437748cd8SNickeau
14537748cd8SNickeau            $writerCalls = &$callWriter->calls;
14637748cd8SNickeau            $this->callStack = &$writerCalls;
14737748cd8SNickeau            $this->callStackType = self::CALLSTACK_WRITER;
14837748cd8SNickeau
14937748cd8SNickeau        } else {
15037748cd8SNickeau
15137748cd8SNickeau            /**
15237748cd8SNickeau             * Check the calls property of the handler
15337748cd8SNickeau             */
15437748cd8SNickeau            $handlerClass = get_class($handler);
15537748cd8SNickeau            try {
15637748cd8SNickeau                $rp = new \ReflectionProperty($handlerClass, "calls");
15737748cd8SNickeau                if ($rp->isPrivate()) {
15837748cd8SNickeau                    LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the calls of the handler are private).", LogUtility::LVL_MSG_ERROR);
15937748cd8SNickeau                    return;
16037748cd8SNickeau                }
16137748cd8SNickeau            } catch (\ReflectionException $e) {
16237748cd8SNickeau                LogUtility::msg("$headErrorMessage. The handler ($handlerClass) provided cannot manipulate the callstack (ie the handler does not have any calls property).", LogUtility::LVL_MSG_ERROR);
16337748cd8SNickeau                return;
16437748cd8SNickeau            }
16537748cd8SNickeau
16637748cd8SNickeau            /**
16737748cd8SNickeau             * Initiate the callstack
16837748cd8SNickeau             */
16937748cd8SNickeau            $this->callStack = &$handler->calls;
17037748cd8SNickeau            $this->callStackType = self::CALLSTACK_MAIN;
17137748cd8SNickeau
17237748cd8SNickeau        }
17337748cd8SNickeau
17437748cd8SNickeau        $this->maxIndex = ArrayUtility::array_key_last($this->callStack);
17537748cd8SNickeau        $this->moveToEnd();
17637748cd8SNickeau
17737748cd8SNickeau
17837748cd8SNickeau    }
17937748cd8SNickeau
18037748cd8SNickeau    public
181*4cadd4f8SNickeau    static function createFromMarkup($marki): CallStack
18237748cd8SNickeau    {
18337748cd8SNickeau
18437748cd8SNickeau        $modes = p_get_parsermodes();
18537748cd8SNickeau        $handler = new Doku_Handler();
18637748cd8SNickeau        $parser = new Parser($handler);
18737748cd8SNickeau
18837748cd8SNickeau        //add modes to parser
18937748cd8SNickeau        foreach ($modes as $mode) {
19037748cd8SNickeau            $parser->addMode($mode['mode'], $mode['obj']);
19137748cd8SNickeau        }
19237748cd8SNickeau        $parser->parse($marki);
19337748cd8SNickeau        return self::createFromHandler($handler);
19437748cd8SNickeau
19537748cd8SNickeau    }
19637748cd8SNickeau
197*4cadd4f8SNickeau    public static function createEmpty(): CallStack
198*4cadd4f8SNickeau    {
199*4cadd4f8SNickeau        $emptyHandler = new class extends \Doku_Handler {
200*4cadd4f8SNickeau            public $calls = [];
201*4cadd4f8SNickeau
202*4cadd4f8SNickeau            public function getCallWriter(): object
203*4cadd4f8SNickeau            {
204*4cadd4f8SNickeau                return new class {
205*4cadd4f8SNickeau                    public $calls = array();
206*4cadd4f8SNickeau                };
207*4cadd4f8SNickeau            }
208*4cadd4f8SNickeau        };
209*4cadd4f8SNickeau        return new CallStack($emptyHandler);
210*4cadd4f8SNickeau    }
211*4cadd4f8SNickeau
212*4cadd4f8SNickeau    public static function createFromInstructions(?array $callStackArray): CallStack
213*4cadd4f8SNickeau    {
214*4cadd4f8SNickeau        return CallStack::createEmpty()
215*4cadd4f8SNickeau            ->appendInstructionsFromNativeArray($callStackArray);
216*4cadd4f8SNickeau
217*4cadd4f8SNickeau    }
218*4cadd4f8SNickeau
219*4cadd4f8SNickeau
22037748cd8SNickeau    /**
22137748cd8SNickeau     * Reset the pointer
22237748cd8SNickeau     */
22337748cd8SNickeau    public
22437748cd8SNickeau    function closeAndResetPointer()
22537748cd8SNickeau    {
22637748cd8SNickeau        reset($this->callStack);
22737748cd8SNickeau    }
22837748cd8SNickeau
22937748cd8SNickeau    /**
23037748cd8SNickeau     * Delete from the call stack
23137748cd8SNickeau     * @param $calls
23237748cd8SNickeau     * @param $start
23337748cd8SNickeau     * @param $end
23437748cd8SNickeau     */
23537748cd8SNickeau    public
23637748cd8SNickeau    static function deleteCalls(&$calls, $start, $end)
23737748cd8SNickeau    {
23837748cd8SNickeau        for ($i = $start; $i <= $end; $i++) {
23937748cd8SNickeau            unset($calls[$i]);
24037748cd8SNickeau        }
24137748cd8SNickeau    }
24237748cd8SNickeau
24337748cd8SNickeau    /**
24437748cd8SNickeau     * @param array $calls
24537748cd8SNickeau     * @param integer $position
24637748cd8SNickeau     * @param array $callStackToInsert
24737748cd8SNickeau     */
24837748cd8SNickeau    public
24937748cd8SNickeau    static function insertCallStackUpWards(&$calls, $position, $callStackToInsert)
25037748cd8SNickeau    {
25137748cd8SNickeau
25237748cd8SNickeau        array_splice($calls, $position, 0, $callStackToInsert);
25337748cd8SNickeau
25437748cd8SNickeau    }
25537748cd8SNickeau
25637748cd8SNickeau    /**
25737748cd8SNickeau     * A callstack pointer based implementation
25837748cd8SNickeau     * that starts at the end
259*4cadd4f8SNickeau     * @param mixed|Doku_Handler $handler - mixed because we test if the handler passed is not the good one (It can happen with third plugin)
26037748cd8SNickeau     * @return CallStack
26137748cd8SNickeau     */
26237748cd8SNickeau    public
263*4cadd4f8SNickeau    static function createFromHandler(&$handler): CallStack
26437748cd8SNickeau    {
26537748cd8SNickeau        return new CallStack($handler);
26637748cd8SNickeau    }
26737748cd8SNickeau
26837748cd8SNickeau
26937748cd8SNickeau    /**
27037748cd8SNickeau     * Process the EOL call to the end of stack
27137748cd8SNickeau     * replacing them with paragraph call
27237748cd8SNickeau     *
27337748cd8SNickeau     * A sort of {@link Block::process()} but only from a tag
27437748cd8SNickeau     * to the end of the current stack
27537748cd8SNickeau     *
27637748cd8SNickeau     * This function is used basically in the {@link DOKU_LEXER_EXIT}
27737748cd8SNickeau     * state of {@link SyntaxPlugin::handle()} to create paragraph
27837748cd8SNickeau     * with the class given as parameter
27937748cd8SNickeau     *
280*4cadd4f8SNickeau     * @param array $attributes - the attributes in an callstack array form passed to the paragraph
28137748cd8SNickeau     */
28237748cd8SNickeau    public
283*4cadd4f8SNickeau    function processEolToEndStack(array $attributes = [])
28437748cd8SNickeau    {
28537748cd8SNickeau
28637748cd8SNickeau        \syntax_plugin_combo_para::fromEolToParagraphUntilEndOfStack($this, $attributes);
28737748cd8SNickeau
28837748cd8SNickeau    }
28937748cd8SNickeau
29037748cd8SNickeau    /**
29137748cd8SNickeau     * Delete the call where the pointer is
29237748cd8SNickeau     * And go to the previous position
29337748cd8SNickeau     *
29437748cd8SNickeau     * This function can be used in a next loop
29537748cd8SNickeau     *
29637748cd8SNickeau     * @return Call the deleted call
29737748cd8SNickeau     */
29837748cd8SNickeau    public
2991fa8c418SNickeau    function deleteActualCallAndPrevious(): ?Call
30037748cd8SNickeau    {
30137748cd8SNickeau
30237748cd8SNickeau        $actualCall = $this->getActualCall();
30337748cd8SNickeau
30437748cd8SNickeau        $offset = $this->getActualOffset();
30537748cd8SNickeau        array_splice($this->callStack, $offset, 1, []);
30637748cd8SNickeau
30737748cd8SNickeau        /**
30837748cd8SNickeau         * Move to the next element (array splice reset the pointer)
30937748cd8SNickeau         * if there is a eol as, we delete it
31037748cd8SNickeau         * otherwise we may end up with two eol
31137748cd8SNickeau         * and this is an empty paragraph
31237748cd8SNickeau         */
31337748cd8SNickeau        $this->moveToOffset($offset);
31437748cd8SNickeau        if (!$this->isPointerAtEnd()) {
31537748cd8SNickeau            if ($this->getActualCall()->getTagName() == 'eol') {
31637748cd8SNickeau                array_splice($this->callStack, $offset, 1, []);
31737748cd8SNickeau            }
31837748cd8SNickeau        }
31937748cd8SNickeau
32037748cd8SNickeau        /**
32137748cd8SNickeau         * Move to the previous element
32237748cd8SNickeau         */
32337748cd8SNickeau        $this->moveToOffset($offset - 1);
32437748cd8SNickeau
32537748cd8SNickeau        return $actualCall;
32637748cd8SNickeau
32737748cd8SNickeau    }
32837748cd8SNickeau
32937748cd8SNickeau    /**
33037748cd8SNickeau     * @return Call - get a reference to the actual call
33137748cd8SNickeau     * This function returns a {@link Call call} object
33237748cd8SNickeau     * by reference, meaning that every update will also modify the element
33337748cd8SNickeau     * in the stack
33437748cd8SNickeau     */
33537748cd8SNickeau    public
336*4cadd4f8SNickeau    function getActualCall(): ?Call
33737748cd8SNickeau    {
33837748cd8SNickeau        if ($this->endWasReached) {
33937748cd8SNickeau            LogUtility::msg("The actual call cannot be ask because the end of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
34037748cd8SNickeau            return null;
34137748cd8SNickeau        }
34237748cd8SNickeau        if ($this->startWasReached) {
34337748cd8SNickeau            LogUtility::msg("The actual call cannot be ask because the start of the stack was reached", LogUtility::LVL_MSG_ERROR, self::CANONICAL);
34437748cd8SNickeau            return null;
34537748cd8SNickeau        }
34637748cd8SNickeau        $actualCallKey = key($this->callStack);
34737748cd8SNickeau        $actualCallArray = &$this->callStack[$actualCallKey];
34837748cd8SNickeau        return new Call($actualCallArray, $actualCallKey);
34937748cd8SNickeau
35037748cd8SNickeau    }
35137748cd8SNickeau
35237748cd8SNickeau    /**
35337748cd8SNickeau     * put the pointer one position further
35437748cd8SNickeau     * false if at the end
35537748cd8SNickeau     * @return false|Call
35637748cd8SNickeau     */
35737748cd8SNickeau    public
35837748cd8SNickeau    function next()
35937748cd8SNickeau    {
36037748cd8SNickeau        if ($this->startWasReached) {
36137748cd8SNickeau            $this->startWasReached = false;
36237748cd8SNickeau            $result = reset($this->callStack);
36337748cd8SNickeau            if ($result === false) {
36437748cd8SNickeau                return false;
36537748cd8SNickeau            } else {
36637748cd8SNickeau                return $this->getActualCall();
36737748cd8SNickeau            }
36837748cd8SNickeau        } else {
36937748cd8SNickeau            $next = next($this->callStack);
37037748cd8SNickeau            if ($next === false) {
37137748cd8SNickeau                $this->endWasReached = true;
37237748cd8SNickeau                return $next;
37337748cd8SNickeau            } else {
37437748cd8SNickeau                return $this->getActualCall();
37537748cd8SNickeau            }
37637748cd8SNickeau        }
37737748cd8SNickeau
37837748cd8SNickeau    }
37937748cd8SNickeau
38037748cd8SNickeau    /**
38137748cd8SNickeau     *
38237748cd8SNickeau     * From an exit call, move the corresponding Opening call
38337748cd8SNickeau     *
38437748cd8SNickeau     * This is used mostly in {@link SyntaxPlugin::handle()} from a {@link DOKU_LEXER_EXIT}
38537748cd8SNickeau     * to retrieve the {@link DOKU_LEXER_ENTER} call
38637748cd8SNickeau     *
38737748cd8SNickeau     * @return bool|Call
38837748cd8SNickeau     */
38937748cd8SNickeau    public
39037748cd8SNickeau    function moveToPreviousCorrespondingOpeningCall()
39137748cd8SNickeau    {
39237748cd8SNickeau
39337748cd8SNickeau        /**
394*4cadd4f8SNickeau         * Edge case
39537748cd8SNickeau         */
39637748cd8SNickeau        if (empty($this->callStack)) {
39737748cd8SNickeau            return false;
39837748cd8SNickeau        }
39937748cd8SNickeau
40037748cd8SNickeau        if (!$this->endWasReached) {
40137748cd8SNickeau            $actualCall = $this->getActualCall();
40237748cd8SNickeau            $actualState = $actualCall->getState();
40337748cd8SNickeau            if ($actualState != DOKU_LEXER_EXIT) {
40437748cd8SNickeau                /**
40537748cd8SNickeau                 * Check if we are at the end of the stack
40637748cd8SNickeau                 */
40737748cd8SNickeau                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");
40837748cd8SNickeau                return false;
40937748cd8SNickeau            }
41037748cd8SNickeau        }
41137748cd8SNickeau        $level = 0;
41237748cd8SNickeau        while ($actualCall = $this->previous()) {
41337748cd8SNickeau
41437748cd8SNickeau            $state = $actualCall->getState();
41537748cd8SNickeau            switch ($state) {
41637748cd8SNickeau                case DOKU_LEXER_ENTER:
41737748cd8SNickeau                    $level++;
41837748cd8SNickeau                    break;
41937748cd8SNickeau                case DOKU_LEXER_EXIT:
42037748cd8SNickeau                    $level--;
42137748cd8SNickeau                    break;
42237748cd8SNickeau            }
42337748cd8SNickeau            if ($level > 0) {
42437748cd8SNickeau                break;
42537748cd8SNickeau            }
42637748cd8SNickeau
42737748cd8SNickeau        }
42837748cd8SNickeau        if ($level > 0) {
42937748cd8SNickeau            return $actualCall;
43037748cd8SNickeau        } else {
43137748cd8SNickeau            return false;
43237748cd8SNickeau        }
43337748cd8SNickeau    }
43437748cd8SNickeau
43537748cd8SNickeau
43637748cd8SNickeau    /**
43737748cd8SNickeau     * @return Call|false the previous call or false if there is no more previous call
43837748cd8SNickeau     */
43937748cd8SNickeau    public
44037748cd8SNickeau    function previous()
44137748cd8SNickeau    {
44237748cd8SNickeau        if ($this->endWasReached) {
44337748cd8SNickeau            $this->endWasReached = false;
44437748cd8SNickeau            $return = end($this->callStack);
44537748cd8SNickeau            if ($return == false) {
44637748cd8SNickeau                // empty array (first call on the stack)
44737748cd8SNickeau                return false;
44837748cd8SNickeau            } else {
44937748cd8SNickeau                return $this->getActualCall();
45037748cd8SNickeau            }
45137748cd8SNickeau        } else {
45237748cd8SNickeau            $prev = prev($this->callStack);
45337748cd8SNickeau            if ($prev === false) {
45437748cd8SNickeau                $this->startWasReached = true;
45537748cd8SNickeau                return $prev;
45637748cd8SNickeau            } else {
45737748cd8SNickeau                return $this->getActualCall();
45837748cd8SNickeau            }
45937748cd8SNickeau        }
46037748cd8SNickeau
46137748cd8SNickeau    }
46237748cd8SNickeau
46337748cd8SNickeau    /**
46437748cd8SNickeau     * Return the first enter or special child call (ie a tag)
46537748cd8SNickeau     * @return Call|false
46637748cd8SNickeau     */
46737748cd8SNickeau    public
46837748cd8SNickeau    function moveToFirstChildTag()
46937748cd8SNickeau    {
47037748cd8SNickeau        $found = false;
47137748cd8SNickeau        while ($this->next()) {
47237748cd8SNickeau
47337748cd8SNickeau            $actualCall = $this->getActualCall();
47437748cd8SNickeau            $state = $actualCall->getState();
47537748cd8SNickeau            switch ($state) {
47637748cd8SNickeau                case DOKU_LEXER_ENTER:
47737748cd8SNickeau                case DOKU_LEXER_SPECIAL:
47837748cd8SNickeau                    $found = true;
47937748cd8SNickeau                    break 2;
48037748cd8SNickeau                case DOKU_LEXER_EXIT:
48137748cd8SNickeau                    break 2;
48237748cd8SNickeau            }
48337748cd8SNickeau
48437748cd8SNickeau        }
48537748cd8SNickeau        if ($found) {
48637748cd8SNickeau            return $this->getActualCall();
48737748cd8SNickeau        } else {
48837748cd8SNickeau            return false;
48937748cd8SNickeau        }
49037748cd8SNickeau
49137748cd8SNickeau
49237748cd8SNickeau    }
49337748cd8SNickeau
49437748cd8SNickeau    /**
49537748cd8SNickeau     * The end is the one after the last element
49637748cd8SNickeau     */
49737748cd8SNickeau    public
49837748cd8SNickeau    function moveToEnd()
49937748cd8SNickeau    {
50037748cd8SNickeau        if ($this->startWasReached) {
50137748cd8SNickeau            $this->startWasReached = false;
50237748cd8SNickeau        }
50337748cd8SNickeau        end($this->callStack);
504*4cadd4f8SNickeau        return $this->next();
50537748cd8SNickeau    }
50637748cd8SNickeau
50737748cd8SNickeau    /**
50837748cd8SNickeau     * On the same level
50937748cd8SNickeau     */
51037748cd8SNickeau    public
51137748cd8SNickeau    function moveToNextSiblingTag()
51237748cd8SNickeau    {
51337748cd8SNickeau
51437748cd8SNickeau        /**
51537748cd8SNickeau         * Edgde case
51637748cd8SNickeau         */
51737748cd8SNickeau        if (empty($this->callStack)) {
51837748cd8SNickeau            return false;
51937748cd8SNickeau        }
52037748cd8SNickeau
52137748cd8SNickeau        $actualCall = $this->getActualCall();
522*4cadd4f8SNickeau        $enterState = $actualCall->getState();
523*4cadd4f8SNickeau        if (!in_array($enterState, CallStack::TAG_STATE)) {
52437748cd8SNickeau            LogUtility::msg("A next sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support");
52537748cd8SNickeau            return false;
52637748cd8SNickeau        }
52737748cd8SNickeau        $level = 0;
52837748cd8SNickeau        while ($this->next()) {
52937748cd8SNickeau
53037748cd8SNickeau            $actualCall = $this->getActualCall();
53137748cd8SNickeau            $state = $actualCall->getState();
53237748cd8SNickeau            switch ($state) {
53337748cd8SNickeau                case DOKU_LEXER_ENTER:
53437748cd8SNickeau                    $level++;
53537748cd8SNickeau                    break;
536*4cadd4f8SNickeau                case DOKU_LEXER_SPECIAL:
537*4cadd4f8SNickeau                    if ($enterState === DOKU_LEXER_SPECIAL) {
538*4cadd4f8SNickeau                        break;
539*4cadd4f8SNickeau                    } else {
540*4cadd4f8SNickeau                        // ENTER TAG
541*4cadd4f8SNickeau                        continue 2;
542*4cadd4f8SNickeau                    }
54337748cd8SNickeau                case DOKU_LEXER_EXIT:
54437748cd8SNickeau                    $level--;
54537748cd8SNickeau                    break;
54637748cd8SNickeau            }
54737748cd8SNickeau
54837748cd8SNickeau            if ($level == 0 && in_array($state, self::TAG_STATE)) {
54937748cd8SNickeau                break;
55037748cd8SNickeau            }
55137748cd8SNickeau        }
55237748cd8SNickeau        if ($level == 0 && !$this->endWasReached) {
55337748cd8SNickeau            return $this->getActualCall();
55437748cd8SNickeau        } else {
55537748cd8SNickeau            return false;
55637748cd8SNickeau        }
55737748cd8SNickeau    }
55837748cd8SNickeau
55937748cd8SNickeau    /**
56037748cd8SNickeau     * @param Call $call
56137748cd8SNickeau     * @return Call the inserted call
56237748cd8SNickeau     */
56337748cd8SNickeau    public
5641fa8c418SNickeau    function insertBefore(Call $call): Call
56537748cd8SNickeau    {
56637748cd8SNickeau        if ($this->endWasReached) {
56737748cd8SNickeau
56837748cd8SNickeau            $this->callStack[] = $call->toCallArray();
56937748cd8SNickeau
57037748cd8SNickeau        } else {
57137748cd8SNickeau
57237748cd8SNickeau            $offset = $this->getActualOffset();
57337748cd8SNickeau            array_splice($this->callStack, $offset, 0, [$call->toCallArray()]);
57437748cd8SNickeau            // array splice reset the pointer
57537748cd8SNickeau            // we move it to the actual element (ie the key is offset +1)
57637748cd8SNickeau            $this->moveToOffset($offset + 1);
57737748cd8SNickeau
57837748cd8SNickeau        }
57937748cd8SNickeau        return $call;
58037748cd8SNickeau    }
58137748cd8SNickeau
58237748cd8SNickeau    /**
58337748cd8SNickeau     * Move pointer by offset
58437748cd8SNickeau     * @param $offset
58537748cd8SNickeau     */
58637748cd8SNickeau    private
58737748cd8SNickeau    function moveToOffset($offset)
58837748cd8SNickeau    {
58937748cd8SNickeau        $this->resetPointer();
59037748cd8SNickeau        for ($i = 0; $i < $offset; $i++) {
59137748cd8SNickeau            $result = $this->next();
59237748cd8SNickeau            if ($result === false) {
59337748cd8SNickeau                break;
59437748cd8SNickeau            }
59537748cd8SNickeau        }
59637748cd8SNickeau    }
59737748cd8SNickeau
59837748cd8SNickeau    /**
59937748cd8SNickeau     * Move pointer by key
60037748cd8SNickeau     * @param $targetKey
60137748cd8SNickeau     */
60237748cd8SNickeau    private
60337748cd8SNickeau    function moveToKey($targetKey)
60437748cd8SNickeau    {
60537748cd8SNickeau        $this->resetPointer();
60637748cd8SNickeau        for ($i = 0; $i < $targetKey; $i++) {
60737748cd8SNickeau            next($this->callStack);
60837748cd8SNickeau        }
60937748cd8SNickeau        $actualKey = key($this->callStack);
61037748cd8SNickeau        if ($actualKey != $targetKey) {
61137748cd8SNickeau            LogUtility::msg("The target key ($targetKey) is not equal to the actual key ($actualKey). The moveToKey was not successful");
61237748cd8SNickeau        }
61337748cd8SNickeau    }
61437748cd8SNickeau
61537748cd8SNickeau    /**
61637748cd8SNickeau     * Insert After. The pointer stays at the current state.
61737748cd8SNickeau     * If you don't need to process the call that you just
61837748cd8SNickeau     * inserted, you may want to call {@link CallStack::next()}
61937748cd8SNickeau     * @param Call $call
62037748cd8SNickeau     */
62137748cd8SNickeau    public
62237748cd8SNickeau    function insertAfter($call)
62337748cd8SNickeau    {
62437748cd8SNickeau        $actualKey = key($this->callStack);
62537748cd8SNickeau        if ($actualKey == null) {
62637748cd8SNickeau            if ($this->endWasReached == true) {
62737748cd8SNickeau                $this->callStack[] = $call->toCallArray();
62837748cd8SNickeau            } else {
62937748cd8SNickeau                LogUtility::msg("Callstack: Actual key is null, we can't insert after null");
63037748cd8SNickeau            }
63137748cd8SNickeau        } else {
63237748cd8SNickeau            $offset = array_search($actualKey, array_keys($this->callStack), true);
63337748cd8SNickeau            array_splice($this->callStack, $offset + 1, 0, [$call->toCallArray()]);
63437748cd8SNickeau            // array splice reset the pointer
63537748cd8SNickeau            // we move it to the actual element
63637748cd8SNickeau            $this->moveToKey($actualKey);
63737748cd8SNickeau        }
63837748cd8SNickeau    }
63937748cd8SNickeau
64037748cd8SNickeau    public
64137748cd8SNickeau    function getActualKey()
64237748cd8SNickeau    {
64337748cd8SNickeau        return key($this->callStack);
64437748cd8SNickeau    }
64537748cd8SNickeau
64637748cd8SNickeau    /**
64737748cd8SNickeau     * Insert an EOL call if the next call is not an EOL
64837748cd8SNickeau     * This is to enforce an new paragraph
64937748cd8SNickeau     */
65037748cd8SNickeau    public
65137748cd8SNickeau    function insertEolIfNextCallIsNotEolOrBlock()
65237748cd8SNickeau    {
65337748cd8SNickeau        if (!$this->isPointerAtEnd()) {
65437748cd8SNickeau            $nextCall = $this->next();
65537748cd8SNickeau            if ($nextCall != false) {
65637748cd8SNickeau                if ($nextCall->getTagName() != "eol" && $nextCall->getDisplay() != "block") {
65737748cd8SNickeau                    $this->insertBefore(
65837748cd8SNickeau                        Call::createNativeCall("eol")
65937748cd8SNickeau                    );
66037748cd8SNickeau                    // move on the eol
66137748cd8SNickeau                    $this->previous();
66237748cd8SNickeau                }
66337748cd8SNickeau                // move back
66437748cd8SNickeau                $this->previous();
66537748cd8SNickeau            }
66637748cd8SNickeau        }
66737748cd8SNickeau    }
66837748cd8SNickeau
66937748cd8SNickeau    private
67037748cd8SNickeau    function isPointerAtEnd()
67137748cd8SNickeau    {
67237748cd8SNickeau        return $this->endWasReached;
67337748cd8SNickeau    }
67437748cd8SNickeau
67537748cd8SNickeau    public
67637748cd8SNickeau    function &getHandler()
67737748cd8SNickeau    {
67837748cd8SNickeau        return $this->handler;
67937748cd8SNickeau    }
68037748cd8SNickeau
68137748cd8SNickeau    /**
68237748cd8SNickeau     * Return The offset (not the key):
68337748cd8SNickeau     *   * starting at 0 for the first element
68437748cd8SNickeau     *   * 1 for the second ...
68537748cd8SNickeau     *
68637748cd8SNickeau     * @return false|int|string
68737748cd8SNickeau     */
68837748cd8SNickeau    private
68937748cd8SNickeau    function getActualOffset()
69037748cd8SNickeau    {
69137748cd8SNickeau        $actualKey = key($this->callStack);
69237748cd8SNickeau        return array_search($actualKey, array_keys($this->callStack), true);
69337748cd8SNickeau    }
69437748cd8SNickeau
69537748cd8SNickeau    private
69637748cd8SNickeau    function resetPointer()
69737748cd8SNickeau    {
69837748cd8SNickeau        reset($this->callStack);
69937748cd8SNickeau        $this->endWasReached = false;
70037748cd8SNickeau    }
70137748cd8SNickeau
70237748cd8SNickeau    public
70337748cd8SNickeau    function moveToStart()
70437748cd8SNickeau    {
70537748cd8SNickeau        $this->resetPointer();
706*4cadd4f8SNickeau        return $this->previous();
70737748cd8SNickeau    }
70837748cd8SNickeau
70937748cd8SNickeau    /**
71037748cd8SNickeau     * @return Call|false the parent call or false if there is no parent
71137748cd8SNickeau     * If you are on an {@link DOKU_LEXER_EXIT} state, you should
71237748cd8SNickeau     * call first the {@link CallStack::moveToPreviousCorrespondingOpeningCall()}
71337748cd8SNickeau     */
71437748cd8SNickeau    public function moveToParent()
71537748cd8SNickeau    {
71637748cd8SNickeau
71737748cd8SNickeau        /**
71837748cd8SNickeau         * Case when we start from the exit state element
71937748cd8SNickeau         * We go first to the opening tag
72037748cd8SNickeau         * because the algorithm is level based.
72137748cd8SNickeau         *
72237748cd8SNickeau         * When the end is reached, there is no call
72337748cd8SNickeau         * (this not the {@link end php end} but one further
72437748cd8SNickeau         */
72537748cd8SNickeau        if (!$this->endWasReached && !$this->startWasReached && $this->getActualCall()->getState() == DOKU_LEXER_EXIT) {
72637748cd8SNickeau
72737748cd8SNickeau            $this->moveToPreviousCorrespondingOpeningCall();
72837748cd8SNickeau
72937748cd8SNickeau        }
73037748cd8SNickeau
73137748cd8SNickeau
73237748cd8SNickeau        /**
73337748cd8SNickeau         * We are in a parent when the tree level is negative
73437748cd8SNickeau         */
73537748cd8SNickeau        $treeLevel = 0;
73637748cd8SNickeau        while ($actualCall = $this->previous()) {
73737748cd8SNickeau
73837748cd8SNickeau            /**
73937748cd8SNickeau             * Add
74037748cd8SNickeau             * would become a parent on its enter state
74137748cd8SNickeau             */
74237748cd8SNickeau            $actualCallState = $actualCall->getState();
74337748cd8SNickeau            switch ($actualCallState) {
74437748cd8SNickeau                case DOKU_LEXER_ENTER:
74537748cd8SNickeau                    $treeLevel = $treeLevel - 1;
74637748cd8SNickeau                    break;
74737748cd8SNickeau                case DOKU_LEXER_EXIT:
74837748cd8SNickeau                    /**
74937748cd8SNickeau                     * When the tag has a sibling with an exit tag
75037748cd8SNickeau                     */
75137748cd8SNickeau                    $treeLevel = $treeLevel + 1;
75237748cd8SNickeau                    break;
75337748cd8SNickeau            }
75437748cd8SNickeau
75537748cd8SNickeau            /**
75637748cd8SNickeau             * The breaking statement
75737748cd8SNickeau             */
75837748cd8SNickeau            if ($treeLevel < 0) {
75937748cd8SNickeau                break;
76037748cd8SNickeau            }
76137748cd8SNickeau
76237748cd8SNickeau        }
76337748cd8SNickeau        return $actualCall;
76437748cd8SNickeau
76537748cd8SNickeau
76637748cd8SNickeau    }
76737748cd8SNickeau
76837748cd8SNickeau    /**
76937748cd8SNickeau     * Delete the anchor link to the image (ie the lightbox)
77037748cd8SNickeau     *
77137748cd8SNickeau     * This is used in navigation and for instance
77237748cd8SNickeau     * in heading
77337748cd8SNickeau     */
77437748cd8SNickeau    public function processNoLinkOnImageToEndStack()
77537748cd8SNickeau    {
77637748cd8SNickeau        while ($this->next()) {
77737748cd8SNickeau            $actualCall = $this->getActualCall();
77837748cd8SNickeau            if ($actualCall->getTagName() == syntax_plugin_combo_media::TAG) {
77937748cd8SNickeau                $actualCall->addAttribute(MediaLink::LINKING_KEY, MediaLink::LINKING_NOLINK_VALUE);
78037748cd8SNickeau            }
78137748cd8SNickeau        }
78237748cd8SNickeau    }
78337748cd8SNickeau
78437748cd8SNickeau    /**
78537748cd8SNickeau     * Append instructions to the callstack (ie at the end)
78637748cd8SNickeau     * @param array $instructions
787*4cadd4f8SNickeau     * @return CallStack
78837748cd8SNickeau     */
789*4cadd4f8SNickeau    public function appendInstructionsFromNativeArray(array $instructions): CallStack
79037748cd8SNickeau    {
79137748cd8SNickeau        array_splice($this->callStack, count($this->callStack), 0, $instructions);
792*4cadd4f8SNickeau        return $this;
79337748cd8SNickeau    }
79437748cd8SNickeau
79537748cd8SNickeau    /**
79637748cd8SNickeau     * @param Call $call
79737748cd8SNickeau     */
79837748cd8SNickeau    public function appendCallAtTheEnd($call)
79937748cd8SNickeau    {
80037748cd8SNickeau        $this->callStack[] = $call->toCallArray();
80137748cd8SNickeau    }
80237748cd8SNickeau
80337748cd8SNickeau    public function moveToPreviousSiblingTag()
80437748cd8SNickeau    {
80537748cd8SNickeau        /**
80637748cd8SNickeau         * Edge case
80737748cd8SNickeau         */
80837748cd8SNickeau        if (empty($this->callStack)) {
80937748cd8SNickeau            return false;
81037748cd8SNickeau        }
81137748cd8SNickeau
812*4cadd4f8SNickeau        $enterState = null;
81337748cd8SNickeau        if (!$this->endWasReached) {
81437748cd8SNickeau            $actualCall = $this->getActualCall();
815*4cadd4f8SNickeau            $enterState = $actualCall->getState();
816*4cadd4f8SNickeau            if (!in_array($enterState, CallStack::TAG_STATE)) {
81737748cd8SNickeau                LogUtility::msg("A previous sibling can be asked only from a tag call. The state is " . $actualState, LogUtility::LVL_MSG_ERROR, "support");
81837748cd8SNickeau                return false;
81937748cd8SNickeau            }
82037748cd8SNickeau        }
82137748cd8SNickeau        $level = 0;
822*4cadd4f8SNickeau        while ($actualCall = $this->previous()) {
82337748cd8SNickeau
82437748cd8SNickeau            $state = $actualCall->getState();
82537748cd8SNickeau            switch ($state) {
82637748cd8SNickeau                case DOKU_LEXER_ENTER:
82737748cd8SNickeau                    $level++;
82837748cd8SNickeau                    break;
829*4cadd4f8SNickeau                case DOKU_LEXER_SPECIAL:
830*4cadd4f8SNickeau                    if ($enterState === DOKU_LEXER_SPECIAL) {
831*4cadd4f8SNickeau                        break;
832*4cadd4f8SNickeau                    } else {
833*4cadd4f8SNickeau                        continue 2;
834*4cadd4f8SNickeau                    }
83537748cd8SNickeau                case DOKU_LEXER_EXIT:
83637748cd8SNickeau                    $level--;
83737748cd8SNickeau                    break;
838*4cadd4f8SNickeau                default:
839*4cadd4f8SNickeau                    // cdata
840*4cadd4f8SNickeau                    continue 2;
84137748cd8SNickeau            }
84237748cd8SNickeau
84337748cd8SNickeau            if ($level == 0 && in_array($state, self::TAG_STATE)) {
84437748cd8SNickeau                break;
84537748cd8SNickeau            }
84637748cd8SNickeau        }
84737748cd8SNickeau        if ($level == 0 && !$this->startWasReached) {
84837748cd8SNickeau            return $this->getActualCall();
84937748cd8SNickeau        } else {
85037748cd8SNickeau            return false;
85137748cd8SNickeau        }
85237748cd8SNickeau    }
85337748cd8SNickeau
85437748cd8SNickeau    /**
85537748cd8SNickeau     * Delete all calls after the passed call
85637748cd8SNickeau     *
85737748cd8SNickeau     * It's used in syntax generator that:
85837748cd8SNickeau     *   * capture the children callstack at the end,
85937748cd8SNickeau     *   * delete it
86037748cd8SNickeau     *   * and use it to generate more calls.
86137748cd8SNickeau     *
86237748cd8SNickeau     * @param Call $call
86337748cd8SNickeau     */
86437748cd8SNickeau    public function deleteAllCallsAfter(Call $call)
86537748cd8SNickeau    {
86637748cd8SNickeau        $key = $call->getKey();
86737748cd8SNickeau        $offset = array_search($key, array_keys($this->callStack), true);
86837748cd8SNickeau        if ($offset !== false) {
86937748cd8SNickeau            /**
87037748cd8SNickeau             * We delete from the next
87137748cd8SNickeau             * {@link array_splice()} delete also the given offset
87237748cd8SNickeau             */
87337748cd8SNickeau            array_splice($this->callStack, $offset + 1);
87437748cd8SNickeau        } else {
87537748cd8SNickeau            LogUtility::msg("The call ($call) could not be found in the callStack. We couldn't therefore delete the calls after");
87637748cd8SNickeau        }
87737748cd8SNickeau
87837748cd8SNickeau    }
87937748cd8SNickeau
88037748cd8SNickeau    /**
88137748cd8SNickeau     * @param Call[] $calls
88237748cd8SNickeau     */
88337748cd8SNickeau    public function appendInstructionsFromCallObjects($calls)
88437748cd8SNickeau    {
88537748cd8SNickeau        foreach ($calls as $call) {
88637748cd8SNickeau            $this->appendCallAtTheEnd($call);
88737748cd8SNickeau        }
888c3437056SNickeau
889c3437056SNickeau    }
890c3437056SNickeau
891c3437056SNickeau    /**
892c3437056SNickeau     *
893c3437056SNickeau     * @return int|mixed - the last position on the callstack
894c3437056SNickeau     * If you are at the end of the callstack after a full parsing,
895c3437056SNickeau     * this should be the length of the string of the page
896c3437056SNickeau     */
897c3437056SNickeau    public function getLastCharacterPosition()
898c3437056SNickeau    {
899c3437056SNickeau        $offset = $this->getActualOffset();
900c3437056SNickeau
901c3437056SNickeau        $lastEndPosition = 0;
902c3437056SNickeau        $this->moveToEnd();
903c3437056SNickeau        while ($actualCall = $this->previous()) {
904c3437056SNickeau            // p_open and p_close have always a position value of 0
905c3437056SNickeau            $lastEndPosition = $actualCall->getLastMatchedCharacterPosition();
906c3437056SNickeau            if ($lastEndPosition !== 0) {
907c3437056SNickeau                break;
908c3437056SNickeau            }
909c3437056SNickeau        }
910c3437056SNickeau        if ($offset == null) {
911c3437056SNickeau            $this->moveToEnd();
912c3437056SNickeau        } else {
913c3437056SNickeau            $this->moveToOffset($offset);
914c3437056SNickeau        }
915c3437056SNickeau        return $lastEndPosition;
91637748cd8SNickeau
91737748cd8SNickeau    }
91837748cd8SNickeau
919*4cadd4f8SNickeau    public function getStack(): array
920*4cadd4f8SNickeau    {
921*4cadd4f8SNickeau        return $this->callStack;
922*4cadd4f8SNickeau    }
923*4cadd4f8SNickeau
924*4cadd4f8SNickeau    public function moveToFirstEnterTag()
925*4cadd4f8SNickeau    {
926*4cadd4f8SNickeau
927*4cadd4f8SNickeau        while ($actualCall = $this->next()) {
928*4cadd4f8SNickeau
929*4cadd4f8SNickeau            if ($actualCall->getState() === DOKU_LEXER_ENTER) {
930*4cadd4f8SNickeau                return $this->getActualCall();
931*4cadd4f8SNickeau            }
932*4cadd4f8SNickeau        }
933*4cadd4f8SNickeau        return false;
934*4cadd4f8SNickeau
935*4cadd4f8SNickeau    }
936*4cadd4f8SNickeau
937*4cadd4f8SNickeau    /**
938*4cadd4f8SNickeau     * Move the pointer to the corresponding exit call
939*4cadd4f8SNickeau     * and return it or false if not found
940*4cadd4f8SNickeau     * @return Call|false
941*4cadd4f8SNickeau     */
942*4cadd4f8SNickeau    public function moveToNextCorrespondingExitTag()
943*4cadd4f8SNickeau    {
944*4cadd4f8SNickeau        /**
945*4cadd4f8SNickeau         * Edge case
946*4cadd4f8SNickeau         */
947*4cadd4f8SNickeau        if (empty($this->callStack)) {
948*4cadd4f8SNickeau            return false;
949*4cadd4f8SNickeau        }
950*4cadd4f8SNickeau
951*4cadd4f8SNickeau        /**
952*4cadd4f8SNickeau         * Check if we are on an enter tag
953*4cadd4f8SNickeau         */
954*4cadd4f8SNickeau        $actualCall = $this->getActualCall();
955*4cadd4f8SNickeau        if ($actualCall === null) {
956*4cadd4f8SNickeau            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);
957*4cadd4f8SNickeau            return false;
958*4cadd4f8SNickeau        }
959*4cadd4f8SNickeau        $actualState = $actualCall->getState();
960*4cadd4f8SNickeau        if ($actualState != DOKU_LEXER_ENTER) {
961*4cadd4f8SNickeau            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);
962*4cadd4f8SNickeau            return false;
963*4cadd4f8SNickeau        }
964*4cadd4f8SNickeau
965*4cadd4f8SNickeau        $level = 0;
966*4cadd4f8SNickeau        while ($actualCall = $this->next()) {
967*4cadd4f8SNickeau
968*4cadd4f8SNickeau            $state = $actualCall->getState();
969*4cadd4f8SNickeau            switch ($state) {
970*4cadd4f8SNickeau                case DOKU_LEXER_ENTER:
971*4cadd4f8SNickeau                    $level++;
972*4cadd4f8SNickeau                    break;
973*4cadd4f8SNickeau                case DOKU_LEXER_EXIT:
974*4cadd4f8SNickeau                    $level--;
975*4cadd4f8SNickeau                    break;
976*4cadd4f8SNickeau            }
977*4cadd4f8SNickeau            if ($level < 0) {
978*4cadd4f8SNickeau                break;
979*4cadd4f8SNickeau            }
980*4cadd4f8SNickeau
981*4cadd4f8SNickeau        }
982*4cadd4f8SNickeau        if ($level < 0) {
983*4cadd4f8SNickeau            return $actualCall;
984*4cadd4f8SNickeau        } else {
985*4cadd4f8SNickeau            return false;
986*4cadd4f8SNickeau        }
987*4cadd4f8SNickeau
988*4cadd4f8SNickeau    }
989*4cadd4f8SNickeau
990*4cadd4f8SNickeau    public function moveToCall(Call $call): ?Call
991*4cadd4f8SNickeau    {
992*4cadd4f8SNickeau        $targetKey = $call->getKey();
993*4cadd4f8SNickeau        $actualKey = $this->getActualKey();
994*4cadd4f8SNickeau        $diff = $targetKey - $actualKey ;
995*4cadd4f8SNickeau        for ($i = 0; $i < abs($diff); $i++) {
996*4cadd4f8SNickeau            if ($diff > 0) {
997*4cadd4f8SNickeau                $this->next();
998*4cadd4f8SNickeau            } else {
999*4cadd4f8SNickeau                $this->previous();
1000*4cadd4f8SNickeau            }
1001*4cadd4f8SNickeau        }
1002*4cadd4f8SNickeau        return $this->getActualCall();
1003*4cadd4f8SNickeau    }
1004*4cadd4f8SNickeau
100537748cd8SNickeau
100637748cd8SNickeau}
1007