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