Failed Conditions
Push — stable ( 017e16...b83837 )
by
unknown
10:45 queued 07:58
created

Table::writeCalls()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace dokuwiki\Parsing\Handler;
4
5
class Table implements ReWriterInterface
6
{
7
8
    /** @var CallWriterInterface original CallWriter */
9
    protected $callWriter;
10
11
    protected $calls = array();
12
    protected $tableCalls = array();
13
    protected $maxCols = 0;
14
    protected $maxRows = 1;
15
    protected $currentCols = 0;
16
    protected $firstCell = false;
17
    protected $lastCellType = 'tablecell';
18
    protected $inTableHead = true;
19
    protected $currentRow = array('tableheader' => 0, 'tablecell' => 0);
20
    protected $countTableHeadRows = 0;
21
22
    /** @inheritdoc */
23
    public function __construct(CallWriterInterface $CallWriter)
24
    {
25
        $this->callWriter = $CallWriter;
26
    }
27
28
    /** @inheritdoc */
29
    public function writeCall($call)
30
    {
31
        $this->calls[] = $call;
32
    }
33
34
    /**
35
     * @inheritdoc
36
     * Probably not needed but just in case...
37
     */
38
    public function writeCalls($calls)
39
    {
40
        $this->calls = array_merge($this->calls, $calls);
41
    }
42
43
    /** @inheritdoc */
44
    public function finalise()
45
    {
46
        $last_call = end($this->calls);
47
        $this->writeCall(array('table_end',array(), $last_call[2]));
48
49
        $this->process();
50
        $this->callWriter->finalise();
51
        unset($this->callWriter);
52
    }
53
54
    /** @inheritdoc */
55
    public function process()
56
    {
57
        foreach ($this->calls as $call) {
58
            switch ($call[0]) {
59
                case 'table_start':
60
                    $this->tableStart($call);
61
                    break;
62
                case 'table_row':
63
                    $this->tableRowClose($call);
64
                    $this->tableRowOpen(array('tablerow_open',$call[1],$call[2]));
65
                    break;
66
                case 'tableheader':
67
                case 'tablecell':
68
                    $this->tableCell($call);
69
                    break;
70
                case 'table_end':
71
                    $this->tableRowClose($call);
72
                    $this->tableEnd($call);
73
                    break;
74
                default:
75
                    $this->tableDefault($call);
76
                    break;
77
            }
78
        }
79
        $this->callWriter->writeCalls($this->tableCalls);
80
81
        return $this->callWriter;
82
    }
83
84
    protected function tableStart($call)
85
    {
86
        $this->tableCalls[] = array('table_open',$call[1],$call[2]);
87
        $this->tableCalls[] = array('tablerow_open',array(),$call[2]);
88
        $this->firstCell = true;
89
    }
90
91
    protected function tableEnd($call)
92
    {
93
        $this->tableCalls[] = array('table_close',$call[1],$call[2]);
94
        $this->finalizeTable();
95
    }
96
97
    protected function tableRowOpen($call)
98
    {
99
        $this->tableCalls[] = $call;
100
        $this->currentCols = 0;
101
        $this->firstCell = true;
102
        $this->lastCellType = 'tablecell';
103
        $this->maxRows++;
104
        if ($this->inTableHead) {
105
            $this->currentRow = array('tablecell' => 0, 'tableheader' => 0);
106
        }
107
    }
108
109
    protected function tableRowClose($call)
110
    {
111
        if ($this->inTableHead && ($this->inTableHead = $this->isTableHeadRow())) {
112
            $this->countTableHeadRows++;
113
        }
114
        // Strip off final cell opening and anything after it
115
        while ($discard = array_pop($this->tableCalls)) {
116
            if ($discard[0] == 'tablecell_open' || $discard[0] == 'tableheader_open') {
117
                break;
118
            }
119
            if (!empty($this->currentRow[$discard[0]])) {
120
                $this->currentRow[$discard[0]]--;
121
            }
122
        }
123
        $this->tableCalls[] = array('tablerow_close', array(), $call[2]);
124
125
        if ($this->currentCols > $this->maxCols) {
126
            $this->maxCols = $this->currentCols;
127
        }
128
    }
129
130
    protected function isTableHeadRow()
131
    {
132
        $td = $this->currentRow['tablecell'];
133
        $th = $this->currentRow['tableheader'];
134
135
        if (!$th || $td > 2) return false;
136
        if (2*$td > $th) return false;
137
138
        return true;
139
    }
140
141
    protected function tableCell($call)
142
    {
143
        if ($this->inTableHead) {
144
            $this->currentRow[$call[0]]++;
145
        }
146
        if (!$this->firstCell) {
147
            // Increase the span
148
            $lastCall = end($this->tableCalls);
149
150
            // A cell call which follows an open cell means an empty cell so span
151
            if ($lastCall[0] == 'tablecell_open' || $lastCall[0] == 'tableheader_open') {
152
                $this->tableCalls[] = array('colspan',array(),$call[2]);
153
            }
154
155
            $this->tableCalls[] = array($this->lastCellType.'_close',array(),$call[2]);
156
            $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
157
            $this->lastCellType = $call[0];
158
        } else {
159
            $this->tableCalls[] = array($call[0].'_open',array(1,null,1),$call[2]);
160
            $this->lastCellType = $call[0];
161
            $this->firstCell = false;
162
        }
163
164
        $this->currentCols++;
165
    }
166
167
    protected function tableDefault($call)
168
    {
169
        $this->tableCalls[] = $call;
170
    }
171
172
    protected function finalizeTable()
173
    {
174
175
        // Add the max cols and rows to the table opening
176
        if ($this->tableCalls[0][0] == 'table_open') {
177
            // Adjust to num cols not num col delimeters
178
            $this->tableCalls[0][1][] = $this->maxCols - 1;
179
            $this->tableCalls[0][1][] = $this->maxRows;
180
            $this->tableCalls[0][1][] = array_shift($this->tableCalls[0][1]);
181
        } else {
182
            trigger_error('First element in table call list is not table_open');
183
        }
184
185
        $lastRow = 0;
186
        $lastCell = 0;
187
        $cellKey = array();
188
        $toDelete = array();
189
190
        // if still in tableheader, then there can be no table header
191
        // as all rows can't be within <THEAD>
192
        if ($this->inTableHead) {
193
            $this->inTableHead = false;
194
            $this->countTableHeadRows = 0;
195
        }
196
197
        // Look for the colspan elements and increment the colspan on the
198
        // previous non-empty opening cell. Once done, delete all the cells
199
        // that contain colspans
200
        for ($key = 0; $key < count($this->tableCalls); ++$key) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
201
            $call = $this->tableCalls[$key];
202
203
            switch ($call[0]) {
204
                case 'table_open':
205
                    if ($this->countTableHeadRows) {
206
                        array_splice($this->tableCalls, $key+1, 0, array(
207
                                                          array('tablethead_open', array(), $call[2])));
208
                    }
209
                    break;
210
211
                case 'tablerow_open':
212
                    $lastRow++;
213
                    $lastCell = 0;
214
                    break;
215
216
                case 'tablecell_open':
217
                case 'tableheader_open':
218
                    $lastCell++;
219
                    $cellKey[$lastRow][$lastCell] = $key;
220
                    break;
221
222
                case 'table_align':
223
                    $prev = in_array($this->tableCalls[$key-1][0], array('tablecell_open', 'tableheader_open'));
224
                    $next = in_array($this->tableCalls[$key+1][0], array('tablecell_close', 'tableheader_close'));
225
                    // If the cell is empty, align left
226
                    if ($prev && $next) {
227
                        $this->tableCalls[$key-1][1][1] = 'left';
228
229
                        // If the previous element was a cell open, align right
230
                    } elseif ($prev) {
231
                        $this->tableCalls[$key-1][1][1] = 'right';
232
233
                        // If the next element is the close of an element, align either center or left
234
                    } elseif ($next) {
235
                        if ($this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] == 'right') {
236
                            $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'center';
237
                        } else {
238
                            $this->tableCalls[$cellKey[$lastRow][$lastCell]][1][1] = 'left';
239
                        }
240
                    }
241
242
                    // Now convert the whitespace back to cdata
243
                    $this->tableCalls[$key][0] = 'cdata';
244
                    break;
245
246
                case 'colspan':
247
                    $this->tableCalls[$key-1][1][0] = false;
248
249
                    for ($i = $key-2; $i >= $cellKey[$lastRow][1]; $i--) {
250
                        if ($this->tableCalls[$i][0] == 'tablecell_open' ||
251
                            $this->tableCalls[$i][0] == 'tableheader_open'
252
                        ) {
253
                            if (false !== $this->tableCalls[$i][1][0]) {
254
                                $this->tableCalls[$i][1][0]++;
255
                                break;
256
                            }
257
                        }
258
                    }
259
260
                    $toDelete[] = $key-1;
261
                    $toDelete[] = $key;
262
                    $toDelete[] = $key+1;
263
                    break;
264
265
                case 'rowspan':
266
                    if ($this->tableCalls[$key-1][0] == 'cdata') {
267
                        // ignore rowspan if previous call was cdata (text mixed with :::)
268
                        // we don't have to check next call as that wont match regex
269
                        $this->tableCalls[$key][0] = 'cdata';
270
                    } else {
271
                        $spanning_cell = null;
272
273
                        // can't cross thead/tbody boundary
274
                        if (!$this->countTableHeadRows || ($lastRow-1 != $this->countTableHeadRows)) {
275
                            for ($i = $lastRow-1; $i > 0; $i--) {
276
                                if ($this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tablecell_open' ||
277
                                    $this->tableCalls[$cellKey[$i][$lastCell]][0] == 'tableheader_open'
278
                                ) {
279
                                    if ($this->tableCalls[$cellKey[$i][$lastCell]][1][2] >= $lastRow - $i) {
280
                                        $spanning_cell = $i;
281
                                        break;
282
                                    }
283
                                }
284
                            }
285
                        }
286
                        if (is_null($spanning_cell)) {
287
                            // No spanning cell found, so convert this cell to
288
                            // an empty one to avoid broken tables
289
                            $this->tableCalls[$key][0] = 'cdata';
290
                            $this->tableCalls[$key][1][0] = '';
291
                            break;
292
                        }
293
                        $this->tableCalls[$cellKey[$spanning_cell][$lastCell]][1][2]++;
294
295
                        $this->tableCalls[$key-1][1][2] = false;
296
297
                        $toDelete[] = $key-1;
298
                        $toDelete[] = $key;
299
                        $toDelete[] = $key+1;
300
                    }
301
                    break;
302
303
                case 'tablerow_close':
304
                    // Fix broken tables by adding missing cells
305
                    $moreCalls = array();
306
                    while (++$lastCell < $this->maxCols) {
307
                        $moreCalls[] = array('tablecell_open', array(1, null, 1), $call[2]);
308
                        $moreCalls[] = array('cdata', array(''), $call[2]);
309
                        $moreCalls[] = array('tablecell_close', array(), $call[2]);
310
                    }
311
                    $moreCallsLength = count($moreCalls);
312
                    if ($moreCallsLength) {
313
                        array_splice($this->tableCalls, $key, 0, $moreCalls);
314
                        $key += $moreCallsLength;
315
                    }
316
317
                    if ($this->countTableHeadRows == $lastRow) {
318
                        array_splice($this->tableCalls, $key+1, 0, array(
319
                            array('tablethead_close', array(), $call[2])));
320
                    }
321
                    break;
322
            }
323
        }
324
325
        // condense cdata
326
        $cnt = count($this->tableCalls);
327
        for ($key = 0; $key < $cnt; $key++) {
328
            if ($this->tableCalls[$key][0] == 'cdata') {
329
                $ckey = $key;
330
                $key++;
331
                while ($this->tableCalls[$key][0] == 'cdata') {
332
                    $this->tableCalls[$ckey][1][0] .= $this->tableCalls[$key][1][0];
333
                    $toDelete[] = $key;
334
                    $key++;
335
                }
336
                continue;
337
            }
338
        }
339
340
        foreach ($toDelete as $delete) {
341
            unset($this->tableCalls[$delete]);
342
        }
343
        $this->tableCalls = array_values($this->tableCalls);
344
    }
345
}
346