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