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