xref: /dokuwiki/inc/Parsing/Handler/Lists.php (revision 093fe67e98c0cdb4b73fd46938e49b64971483c2)
1be906b56SAndreas Gohr<?php
2be906b56SAndreas Gohr
3be906b56SAndreas Gohrnamespace dokuwiki\Parsing\Handler;
4be906b56SAndreas Gohr
5533aca44SAndreas Gohrclass Lists extends AbstractRewriter
6be906b56SAndreas Gohr{
7bcaec9f4SAndreas Gohr    protected $listCalls = [];
8bcaec9f4SAndreas Gohr    protected $listStack = [];
9be906b56SAndreas Gohr
10be906b56SAndreas Gohr    protected $initialDepth = 0;
11be906b56SAndreas Gohr
1274981a4eSAndreas Gohr    public const NODE = 1;
13be906b56SAndreas Gohr
14be906b56SAndreas Gohr    /** @inheritdoc */
15be906b56SAndreas Gohr    public function finalise()
16be906b56SAndreas Gohr    {
17be906b56SAndreas Gohr        $last_call = end($this->calls);
18bcaec9f4SAndreas Gohr        $this->writeCall(['list_close', [], $last_call[2]]);
19be906b56SAndreas Gohr
20be906b56SAndreas Gohr        $this->process();
21be906b56SAndreas Gohr        $this->callWriter->finalise();
22be906b56SAndreas Gohr        unset($this->callWriter);
23be906b56SAndreas Gohr    }
24be906b56SAndreas Gohr
25be906b56SAndreas Gohr    /** @inheritdoc */
26be906b56SAndreas Gohr    public function process()
27be906b56SAndreas Gohr    {
28be906b56SAndreas Gohr
29be906b56SAndreas Gohr        foreach ($this->calls as $call) {
30*093fe67eSAndreas Gohr            match ($call[0]) {
31*093fe67eSAndreas Gohr                'list_item' => $this->listOpen($call),
32*093fe67eSAndreas Gohr                'list_open' => $this->listStart($call),
33*093fe67eSAndreas Gohr                'list_close' => $this->listEnd($call),
34*093fe67eSAndreas Gohr                default => $this->listContent($call),
35*093fe67eSAndreas Gohr            };
36be906b56SAndreas Gohr        }
37be906b56SAndreas Gohr
38be906b56SAndreas Gohr        $this->callWriter->writeCalls($this->listCalls);
39be906b56SAndreas Gohr        return $this->callWriter;
40be906b56SAndreas Gohr    }
41be906b56SAndreas Gohr
42be906b56SAndreas Gohr    protected function listStart($call)
43be906b56SAndreas Gohr    {
44be906b56SAndreas Gohr        $depth = $this->interpretSyntax($call[1][0], $listType);
45be906b56SAndreas Gohr
46be906b56SAndreas Gohr        $this->initialDepth = $depth;
47be906b56SAndreas Gohr        //                   array(list type, current depth, index of current listitem_open)
48bcaec9f4SAndreas Gohr        $this->listStack[] = [$listType, $depth, 1];
49be906b56SAndreas Gohr
50bcaec9f4SAndreas Gohr        $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]];
51bcaec9f4SAndreas Gohr        $this->listCalls[] = ['listitem_open', [1], $call[2]];
52bcaec9f4SAndreas Gohr        $this->listCalls[] = ['listcontent_open', [], $call[2]];
53be906b56SAndreas Gohr    }
54be906b56SAndreas Gohr
55be906b56SAndreas Gohr
56be906b56SAndreas Gohr    protected function listEnd($call)
57be906b56SAndreas Gohr    {
58be906b56SAndreas Gohr        $closeContent = true;
59be906b56SAndreas Gohr
60be906b56SAndreas Gohr        while ($list = array_pop($this->listStack)) {
61be906b56SAndreas Gohr            if ($closeContent) {
62bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listcontent_close', [], $call[2]];
63be906b56SAndreas Gohr                $closeContent = false;
64be906b56SAndreas Gohr            }
65bcaec9f4SAndreas Gohr            $this->listCalls[] = ['listitem_close', [], $call[2]];
66bcaec9f4SAndreas Gohr            $this->listCalls[] = ['list' . $list[0] . '_close', [], $call[2]];
67be906b56SAndreas Gohr        }
68be906b56SAndreas Gohr    }
69be906b56SAndreas Gohr
70be906b56SAndreas Gohr    protected function listOpen($call)
71be906b56SAndreas Gohr    {
72be906b56SAndreas Gohr        $depth = $this->interpretSyntax($call[1][0], $listType);
73be906b56SAndreas Gohr        $end = end($this->listStack);
74be906b56SAndreas Gohr        $key = key($this->listStack);
75be906b56SAndreas Gohr
76be906b56SAndreas Gohr        // Not allowed to be shallower than initialDepth
77be906b56SAndreas Gohr        if ($depth < $this->initialDepth) {
78be906b56SAndreas Gohr            $depth = $this->initialDepth;
79be906b56SAndreas Gohr        }
80be906b56SAndreas Gohr
81be906b56SAndreas Gohr        if ($depth == $end[1]) {
82be906b56SAndreas Gohr            // Just another item in the list...
83be906b56SAndreas Gohr            if ($listType == $end[0]) {
84bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listcontent_close', [], $call[2]];
85bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listitem_close', [], $call[2]];
86bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]];
87bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listcontent_open', [], $call[2]];
88be906b56SAndreas Gohr
89be906b56SAndreas Gohr                // new list item, update list stack's index into current listitem_open
90be906b56SAndreas Gohr                $this->listStack[$key][2] = count($this->listCalls) - 2;
91be906b56SAndreas Gohr
92be906b56SAndreas Gohr                // Switched list type...
93be906b56SAndreas Gohr            } else {
94bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listcontent_close', [], $call[2]];
95bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listitem_close', [], $call[2]];
96bcaec9f4SAndreas Gohr                $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]];
97bcaec9f4SAndreas Gohr                $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]];
98bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]];
99bcaec9f4SAndreas Gohr                $this->listCalls[] = ['listcontent_open', [], $call[2]];
100be906b56SAndreas Gohr
101be906b56SAndreas Gohr                array_pop($this->listStack);
102bcaec9f4SAndreas Gohr                $this->listStack[] = [$listType, $depth, count($this->listCalls) - 2];
103be906b56SAndreas Gohr            }
104be906b56SAndreas Gohr        } elseif ($depth > $end[1]) { // Getting deeper...
105bcaec9f4SAndreas Gohr            $this->listCalls[] = ['listcontent_close', [], $call[2]];
106bcaec9f4SAndreas Gohr            $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]];
107bcaec9f4SAndreas Gohr            $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]];
108bcaec9f4SAndreas Gohr            $this->listCalls[] = ['listcontent_open', [], $call[2]];
109be906b56SAndreas Gohr
110be906b56SAndreas Gohr            // set the node/leaf state of this item's parent listitem_open to NODE
111be906b56SAndreas Gohr            $this->listCalls[$this->listStack[$key][2]][1][1] = self::NODE;
112be906b56SAndreas Gohr
113bcaec9f4SAndreas Gohr            $this->listStack[] = [$listType, $depth, count($this->listCalls) - 2];
114be906b56SAndreas Gohr        } else { // Getting shallower ( $depth < $end[1] )
115bcaec9f4SAndreas Gohr            $this->listCalls[] = ['listcontent_close', [], $call[2]];
116bcaec9f4SAndreas Gohr            $this->listCalls[] = ['listitem_close', [], $call[2]];
117bcaec9f4SAndreas Gohr            $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]];
118be906b56SAndreas Gohr
119be906b56SAndreas Gohr            // Throw away the end - done
120be906b56SAndreas Gohr            array_pop($this->listStack);
121be906b56SAndreas Gohr
122be906b56SAndreas Gohr            while (1) {
123be906b56SAndreas Gohr                $end = end($this->listStack);
124be906b56SAndreas Gohr                $key = key($this->listStack);
125be906b56SAndreas Gohr
126be906b56SAndreas Gohr                if ($end[1] <= $depth) {
127be906b56SAndreas Gohr                    // Normalize depths
128be906b56SAndreas Gohr                    $depth = $end[1];
129be906b56SAndreas Gohr
130bcaec9f4SAndreas Gohr                    $this->listCalls[] = ['listitem_close', [], $call[2]];
131be906b56SAndreas Gohr
132be906b56SAndreas Gohr                    if ($end[0] == $listType) {
133bcaec9f4SAndreas Gohr                        $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]];
134bcaec9f4SAndreas Gohr                        $this->listCalls[] = ['listcontent_open', [], $call[2]];
135be906b56SAndreas Gohr
136be906b56SAndreas Gohr                        // new list item, update list stack's index into current listitem_open
137be906b56SAndreas Gohr                        $this->listStack[$key][2] = count($this->listCalls) - 2;
138be906b56SAndreas Gohr                    } else {
139be906b56SAndreas Gohr                        // Switching list type...
140bcaec9f4SAndreas Gohr                        $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]];
141bcaec9f4SAndreas Gohr                        $this->listCalls[] = ['list' . $listType . '_open', [], $call[2]];
142bcaec9f4SAndreas Gohr                        $this->listCalls[] = ['listitem_open', [$depth - 1], $call[2]];
143bcaec9f4SAndreas Gohr                        $this->listCalls[] = ['listcontent_open', [], $call[2]];
144be906b56SAndreas Gohr
145be906b56SAndreas Gohr                        array_pop($this->listStack);
146bcaec9f4SAndreas Gohr                        $this->listStack[] = [$listType, $depth, count($this->listCalls) - 2];
147be906b56SAndreas Gohr                    }
148be906b56SAndreas Gohr
149be906b56SAndreas Gohr                    break;
150be906b56SAndreas Gohr
151be906b56SAndreas Gohr                    // Haven't dropped down far enough yet.... ( $end[1] > $depth )
152be906b56SAndreas Gohr                } else {
153bcaec9f4SAndreas Gohr                    $this->listCalls[] = ['listitem_close', [], $call[2]];
154bcaec9f4SAndreas Gohr                    $this->listCalls[] = ['list' . $end[0] . '_close', [], $call[2]];
155be906b56SAndreas Gohr
156be906b56SAndreas Gohr                    array_pop($this->listStack);
157be906b56SAndreas Gohr                }
158be906b56SAndreas Gohr            }
159be906b56SAndreas Gohr        }
160be906b56SAndreas Gohr    }
161be906b56SAndreas Gohr
162be906b56SAndreas Gohr    protected function listContent($call)
163be906b56SAndreas Gohr    {
164be906b56SAndreas Gohr        $this->listCalls[] = $call;
165be906b56SAndreas Gohr    }
166be906b56SAndreas Gohr
167be906b56SAndreas Gohr    protected function interpretSyntax($match, &$type)
168be906b56SAndreas Gohr    {
1696c16a3a9Sfiwswe        if (str_ends_with($match, '*')) {
170be906b56SAndreas Gohr            $type = 'u';
171be906b56SAndreas Gohr        } else {
172be906b56SAndreas Gohr            $type = 'o';
173be906b56SAndreas Gohr        }
174be906b56SAndreas Gohr        // Is the +1 needed? It used to be count(explode(...))
175be906b56SAndreas Gohr        // but I don't think the number is seen outside this handler
176be906b56SAndreas Gohr        return substr_count(str_replace("\t", '  ', $match), '  ') + 1;
177be906b56SAndreas Gohr    }
178be906b56SAndreas Gohr}
179