Table   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 354
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 10
Bugs 1 Features 1
Metric Value
wmc 43
c 10
b 1
f 1
lcom 1
cbo 3
dl 0
loc 354
rs 8.3157

9 Methods

Rating   Name   Duplication   Size   Complexity  
A renderInternal() 0 9 1
B parseHtmlTable() 0 49 5
B renderLine() 0 17 7
B renderHeader() 0 14 5
B renderCell() 0 20 5
C increaseLengthToo100Percent() 0 37 8
B getTable() 0 27 6
A columnsHeaders() 0 4 1
B columnsLengths() 0 22 5

How to fix   Complexity   

Complex Class

Complex classes like Table often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Table, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Render a table
4
 *
5
 * @author Tim Lochmüller
6
 */
7
8
namespace FRUIT\Ink\Rendering;
9
10
use FRUIT\Ink\Configuration;
11
use TYPO3\CMS\Core\Utility\GeneralUtility;
12
13
/**
14
 * Render a table
15
 */
16
class Table extends AbstractRendering
17
{
18
19
    /**
20
     * Table settings
21
     *
22
     * @var array
23
     */
24
    protected $tableSettings = array(
25
        'default'       => array(
26
            'join'           => '+',
27
            'xChar'          => '-',
28
            'xCharHeader'    => '-',
29
            'yChar'          => '|',
30
            'yCharHeader'    => '|',
31
            'xSpace'         => 1,
32
            'ySpace'         => 0,
33
            'separateData'   => false,
34
            'separateHeader' => true,
35
            'outerTop'       => true,
36
            'outerBottom'    => true,
37
            'outerLeft'      => true,
38
            'outerRight'     => true,
39
        ),
40
        'ascii_old'     => array(
41
            'join'           => '+',
42
            'xChar'          => '-',
43
            'xCharHeader'    => '=',
44
            'yChar'          => '|',
45
            'yCharHeader'    => '|',
46
            'xSpace'         => 1,
47
            'ySpace'         => 0,
48
            'separateData'   => true,
49
            'separateHeader' => true,
50
            'outerTop'       => true,
51
            'outerBottom'    => true,
52
            'outerLeft'      => true,
53
            'outerRight'     => true,
54
        ),
55
        'ascii_compact' => array(
56
            'join'           => ' ',
57
            'xChar'          => ' ',
58
            'xCharHeader'    => '=',
59
            'yChar'          => ' ',
60
            'yCharHeader'    => ' ',
61
            'xSpace'         => 0,
62
            'ySpace'         => 0,
63
            'separateData'   => false,
64
            'separateHeader' => true,
65
            'outerTop'       => false,
66
            'outerBottom'    => false,
67
            'outerLeft'      => false,
68
            'outerRight'     => false,
69
        ),
70
        'unicode'       => array(
71
            'join'           => '╬',
72
            'xChar'          => '═',
73
            'xCharHeader'    => '═',
74
            'yChar'          => '║',
75
            'yCharHeader'    => '║',
76
            'xSpace'         => 1,
77
            'ySpace'         => 0,
78
            'separateData'   => false,
79
            'separateHeader' => true,
80
            'outerTop'       => true,
81
            'outerBottom'    => true,
82
            'outerLeft'      => true,
83
            'outerRight'     => true,
84
        ),
85
        'markdown'      => array(
86
            'join'           => ' ',
87
            'xChar'          => ' ',
88
            'xCharHeader'    => '-',
89
            'yChar'          => '|',
90
            'yCharHeader'    => '|',
91
            'xSpace'         => 1,
92
            'ySpace'         => 0,
93
            'separateData'   => false,
94
            'separateHeader' => true,
95
            'outerTop'       => false,
96
            'outerBottom'    => false,
97
            'outerLeft'      => true,
98
            'outerRight'     => true,
99
        ),
100
    );
101
102
    /**
103
     * Render mode
104
     *
105
     * @var string
106
     */
107
    protected $renderMode = 'markdown';
108
109
    /**
110
     * @return array
111
     */
112
    public function renderInternal()
113
    {
114
        $controller = GeneralUtility::makeInstance('TYPO3\\CMS\\CssStyledContent\\Controller\\CssStyledContentController');
115
        $controller->cObj = $this->contentObject;
116
        $this->renderMode = Configuration::getTableMode();
117
        $htmlTable = $controller->render_table();
118
        $tableData = $this->parseHtmlTable($htmlTable);
119
        return $this->getTable($tableData);
120
    }
121
122
    /**
123
     * @param $html
124
     *
125
     * @return array
126
     */
127
    protected function parseHtmlTable($html)
128
    {
129
        $dom = new \DOMDocument();
130
131
        //load the html
132
        $html = $dom->loadHTML(utf8_decode($html));
0 ignored issues
show
Unused Code introduced by
$html is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
133
134
        //discard white space
135
        $dom->preserveWhiteSpace = false;
136
137
        //the table by its tag name
138
        $tables = $dom->getElementsByTagName('table');
139
140
        //get all rows from the table
141
        $rows = $tables->item(0)
142
            ->getElementsByTagName('tr');
143
        // get each column by tag name
144
        $cols = $rows->item(0)
145
            ->getElementsByTagName('th');
146
        $row_headers = null;
147
        foreach ($cols as $node) {
148
            //print $node->nodeValue."\n";
149
            $row_headers[] = $node->nodeValue;
150
        }
151
152
        $table = array();
153
        //get all rows from the table
154
        $rows = $tables->item(0)
155
            ->getElementsByTagName('tr');
156
        foreach ($rows as $row) {
157
            // get each column by tag name
158
            $cols = $row->getElementsByTagName('td');
159
            $row = array();
160
            $i = 0;
161
            foreach ($cols as $node) {
162
                # code...
163
                //print $node->nodeValue."\n";
164
                if ($row_headers == null) {
165
                    $row[] = $node->nodeValue;
166
                } else {
167
                    $row[$row_headers[$i]] = $node->nodeValue;
168
                }
169
                $i++;
170
            }
171
            $table[] = $row;
172
        }
173
174
        return $table;
175
    }
176
177
    /**
178
     * Get the table
179
     *
180
     * @param $table
181
     *
182
     * @return string
183
     */
184
    public function getTable($table)
185
    {
186
        $lines = array();
187
        $columnsHeaders = $this->columnsHeaders($table);
188
        $columns_lengths = $this->columnsLengths($table, $columnsHeaders);
189
190
        $topLine = $this->renderLine($columns_lengths, 'top', 'CharHeader');
191
        if ($topLine) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $topLine of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
192
            $lines[] = $topLine;
193
        }
194
        $lines[] = $this->renderHeader($columns_lengths, $columnsHeaders);
195
        if ($this->tableSettings[$this->renderMode]['separateHeader']) {
196
            $lines[] = $this->renderLine($columns_lengths, 'default', 'CharHeader');
197
        }
198
        foreach ($table as $row_cells) {
199
            $lines[] = $this->renderCell($row_cells, $columnsHeaders, $columns_lengths);
200
            if ($this->tableSettings[$this->renderMode]['separateData']) {
201
                $lines[] = $this->renderLine($columns_lengths);
202
            }
203
        }
204
205
        $bottomLine = $this->renderLine($columns_lengths, 'bottom');
206
        if ($bottomLine) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bottomLine of type false|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
207
            $lines[] = $bottomLine;
208
        }
209
        return $lines;
210
    }
211
212
    /**
213
     * Render a separation line
214
     *
215
     * @param        $columnsLengths
216
     * @param string $specialLine
217
     * @param string $charMode
218
     *
219
     * @return string
220
     */
221
    protected function renderLine($columnsLengths, $specialLine = 'default', $charMode = 'Char')
222
    {
223
        if (isset($this->tableSettings[$this->renderMode]['outer' . ucfirst($specialLine)]) && $this->tableSettings[$this->renderMode]['outer' . ucfirst($specialLine)] === false) {
224
            return false;
225
        }
226
        $row = '';
227
        foreach ($columnsLengths as $key => $column_length) {
228
            if ($key !== 0 || $this->tableSettings[$this->renderMode]['outerLeft']) {
229
                $row .= $this->tableSettings[$this->renderMode]['join'];
230
            }
231
            $row .= str_repeat($this->tableSettings[$this->renderMode]['x' . $charMode], ($this->tableSettings[$this->renderMode]['xSpace'] * 2) + $column_length);
232
        }
233
        if ($this->tableSettings[$this->renderMode]['outerRight']) {
234
            $row .= $this->tableSettings[$this->renderMode]['join'];
235
        }
236
        return $row;
237
    }
238
239
    /**
240
     * @param $columnsLengths
241
     * @param $columnsHeaders
242
     *
243
     * @return string
244
     */
245
    protected function renderHeader($columnsLengths, $columnsHeaders)
246
    {
247
        $row = '';
248
        foreach ($columnsHeaders as $key => $header) {
249
            if ($key !== 0 || $this->tableSettings[$this->renderMode]['outerLeft']) {
250
                $row .= $this->tableSettings[$this->renderMode]['yChar'];
251
            }
252
            $row .= str_pad($header, ($this->tableSettings[$this->renderMode]['xSpace'] * 2) + $columnsLengths[$header], ' ', STR_PAD_BOTH);
253
        }
254
        if ($this->tableSettings[$this->renderMode]['outerRight']) {
255
            $row .= $this->tableSettings[$this->renderMode]['yChar'];
256
        }
257
        return $row;
258
    }
259
260
    /**
261
     * @param $row_cells
262
     * @param $columns_headers
263
     * @param $columns_lengths
264
     *
265
     * @return string
266
     */
267
    public function renderCell($row_cells, $columns_headers, $columns_lengths)
268
    {
269
        $row = '';
270
        foreach ($columns_headers as $key => $header) {
271
            $line = array();
272
            $stringLength = mb_strlen(utf8_decode($row_cells[$header]));
273
            if ($key !== 0 || $this->tableSettings[$this->renderMode]['outerLeft']) {
274
                $line[] = $this->tableSettings[$this->renderMode]['yChar'];
275
            }
276
            $line[] = str_repeat(' ', $this->tableSettings[$this->renderMode]['xSpace']);
277
            $line[] = $row_cells[$header];
278
            $line[] = str_repeat(' ', $columns_lengths[$header] - $stringLength + $this->tableSettings[$this->renderMode]['xSpace']);
279
280
            $row .= implode('', $line);
281
        }
282
        if ($this->tableSettings[$this->renderMode]['outerRight']) {
283
            $row .= $this->tableSettings[$this->renderMode]['yChar'];
284
        }
285
        return $row;
286
    }
287
288
    /**
289
     * @param $table
290
     *
291
     * @return array
292
     */
293
    public function columnsHeaders($table)
294
    {
295
        return array_keys(reset($table));
296
    }
297
298
    /**
299
     * @param $table
300
     * @param $columns_headers
301
     *
302
     * @return array
303
     */
304
    public function columnsLengths($table, $columns_headers)
305
    {
306
        $lengths = array();
307
        foreach ($columns_headers as $header) {
308
            $header_length = mb_strlen($header);
309
            $max = $header_length;
310
            foreach ($table as $row) {
311
                $length = mb_strlen($row[$header]);
312
                if ($length > $max) {
313
                    $max = $length;
314
                }
315
            }
316
317
            if (($max % 2) != ($header_length % 2)) {
318
                $max += 1;
319
            }
320
321
            $lengths[$header] = $max;
322
        }
323
324
        return $this->increaseLengthToo100Percent($lengths);
325
    }
326
327
    /**
328
     * @param $lengths
329
     *
330
     * @return mixed
331
     */
332
    protected function increaseLengthToo100Percent($lengths)
333
    {
334
        if (!Configuration::isPlainTable100()) {
335
            return $lengths;
336
        }
337
        $fullWidth = Configuration::getPlainTextWith();
338
339
        // Calc
340
        $rowCount = sizeof($lengths) + 1;
341
        $yCount = $rowCount - 2;
342
        $currentWidth = array_sum($lengths);
343
        if ($this->tableSettings[$this->renderMode]['outerLeft']) {
344
            $yCount++;
345
        }
346
        if ($this->tableSettings[$this->renderMode]['outerRight']) {
347
            $yCount++;
348
        }
349
        $currentWidth += strlen($this->tableSettings[$this->renderMode]['yChar']) * $yCount;
350
        $currentWidth += $this->tableSettings[$this->renderMode]['xSpace'] * $rowCount;
351
352
        if ($fullWidth < $currentWidth) {
353
            return $lengths;
354
        }
355
356
        $moreChars = $fullWidth - $currentWidth;
357
        while ($moreChars > 0) {
358
            foreach ($lengths as $key => $value) {
359
                if ($moreChars <= 0) {
360
                    break;
361
                }
362
                $lengths[$key]++;
363
                $moreChars--;
364
            }
365
        }
366
367
        return $lengths;
368
    }
369
}
370