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