xref: /template/strap/ComboStrap/CallStack.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
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