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