1
|
|
|
<?php
|
2
|
|
|
|
3
|
|
|
/**
|
4
|
|
|
* Utility for printing tables from commandline scripts.
|
5
|
|
|
*
|
6
|
|
|
* PHP versions 5 and 7
|
7
|
|
|
*
|
8
|
|
|
* All rights reserved.
|
9
|
|
|
*
|
10
|
|
|
* Redistribution and use in source and binary forms, with or without
|
11
|
|
|
* modification, are permitted provided that the following conditions are met:
|
12
|
|
|
*
|
13
|
|
|
* o Redistributions of source code must retain the above copyright notice,
|
14
|
|
|
* this list of conditions and the following disclaimer.
|
15
|
|
|
* o Redistributions in binary form must reproduce the above copyright notice,
|
16
|
|
|
* this list of conditions and the following disclaimer in the documentation
|
17
|
|
|
* and/or other materials provided with the distribution.
|
18
|
|
|
* o The names of the authors may not be used to endorse or promote products
|
19
|
|
|
* derived from this software without specific prior written permission.
|
20
|
|
|
*
|
21
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
22
|
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
23
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
24
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
25
|
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
26
|
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
27
|
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
28
|
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
29
|
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
30
|
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
31
|
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
32
|
|
|
*
|
33
|
|
|
* @category Console
|
34
|
|
|
* @package Console_Table
|
35
|
|
|
* @author Richard Heyes <[email protected]>
|
36
|
|
|
* @author Jan Schneider <[email protected]>
|
37
|
|
|
* @copyright 2002-2005 Richard Heyes
|
38
|
|
|
* @copyright 2006-2008 Jan Schneider
|
39
|
|
|
* @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
|
40
|
|
|
* @version CVS: $Id$
|
41
|
|
|
* @link http://pear.php.net/package/Console_Table
|
42
|
|
|
*/
|
43
|
|
|
define('CONSOLE_TABLE_HORIZONTAL_RULE', 1);
|
44
|
|
|
define('CONSOLE_TABLE_ALIGN_LEFT', -1);
|
45
|
|
|
define('CONSOLE_TABLE_ALIGN_CENTER', 0);
|
46
|
|
|
define('CONSOLE_TABLE_ALIGN_RIGHT', 1);
|
47
|
|
|
define('CONSOLE_TABLE_BORDER_ASCII', -1);
|
48
|
|
|
/**
|
49
|
|
|
* The main class.
|
50
|
|
|
*
|
51
|
|
|
* @category Console
|
52
|
|
|
* @package Console_Table
|
53
|
|
|
* @author Jan Schneider <[email protected]>
|
54
|
|
|
* @license http://www.debian.org/misc/bsd.license BSD License (3 Clause)
|
55
|
|
|
* @link http://pear.php.net/package/Console_Table
|
56
|
|
|
*/
|
57
|
|
|
class console_table
|
58
|
|
|
{
|
59
|
|
|
/**
|
60
|
|
|
* The table headers.
|
61
|
|
|
*
|
62
|
|
|
* @var array
|
63
|
|
|
*/
|
64
|
|
|
var $_headers = array();
|
65
|
|
|
/**
|
66
|
|
|
* The data of the table.
|
67
|
|
|
*
|
68
|
|
|
* @var array
|
69
|
|
|
*/
|
70
|
|
|
var $_data = array();
|
71
|
|
|
/**
|
72
|
|
|
* The maximum number of columns in a row.
|
73
|
|
|
*
|
74
|
|
|
* @var integer
|
75
|
|
|
*/
|
76
|
|
|
var $_max_cols = 0;
|
77
|
|
|
/**
|
78
|
|
|
* The maximum number of rows in the table.
|
79
|
|
|
*
|
80
|
|
|
* @var integer
|
81
|
|
|
*/
|
82
|
|
|
var $_max_rows = 0;
|
83
|
|
|
/**
|
84
|
|
|
* Lengths of the columns, calculated when rows are added to the table.
|
85
|
|
|
*
|
86
|
|
|
* @var array
|
87
|
|
|
*/
|
88
|
|
|
var $_cell_lengths = array();
|
89
|
|
|
/**
|
90
|
|
|
* Heights of the rows.
|
91
|
|
|
*
|
92
|
|
|
* @var array
|
93
|
|
|
*/
|
94
|
|
|
var $_row_heights = array();
|
95
|
|
|
/**
|
96
|
|
|
* How many spaces to use to pad the table.
|
97
|
|
|
*
|
98
|
|
|
* @var integer
|
99
|
|
|
*/
|
100
|
|
|
var $_padding = 1;
|
101
|
|
|
/**
|
102
|
|
|
* Column filters.
|
103
|
|
|
*
|
104
|
|
|
* @var array
|
105
|
|
|
*/
|
106
|
|
|
var $_filters = array();
|
107
|
|
|
/**
|
108
|
|
|
* Columns to calculate totals for.
|
109
|
|
|
*
|
110
|
|
|
* @var array
|
111
|
|
|
*/
|
112
|
|
|
var $_calculateTotals;
|
113
|
|
|
/**
|
114
|
|
|
* Alignment of the columns.
|
115
|
|
|
*
|
116
|
|
|
* @var array
|
117
|
|
|
*/
|
118
|
|
|
var $_col_align = array();
|
119
|
|
|
/**
|
120
|
|
|
* Default alignment of columns.
|
121
|
|
|
*
|
122
|
|
|
* @var integer
|
123
|
|
|
*/
|
124
|
|
|
var $_defaultAlign;
|
125
|
|
|
/**
|
126
|
|
|
* Character set of the data.
|
127
|
|
|
*
|
128
|
|
|
* @var string
|
129
|
|
|
*/
|
130
|
|
|
var $_charset = 'utf-8';
|
131
|
|
|
/**
|
132
|
|
|
* Border characters.
|
133
|
|
|
* Allowed keys:
|
134
|
|
|
* - intersection - intersection ("+")
|
135
|
|
|
* - horizontal - horizontal rule character ("-")
|
136
|
|
|
* - vertical - vertical rule character ("|")
|
137
|
|
|
*
|
138
|
|
|
* @var array
|
139
|
|
|
*/
|
140
|
|
|
var $_border = array(
|
141
|
|
|
'intersection' => '+',
|
142
|
|
|
'horizontal' => '-',
|
143
|
|
|
'vertical' => '|',
|
144
|
|
|
);
|
145
|
|
|
/**
|
146
|
|
|
* If borders are shown or not
|
147
|
|
|
* Allowed keys: top, right, bottom, left, inner: true and false
|
148
|
|
|
*
|
149
|
|
|
* @var array
|
150
|
|
|
*/
|
151
|
|
|
var $_borderVisibility = array(
|
152
|
|
|
'top' => true,
|
153
|
|
|
'right' => true,
|
154
|
|
|
'bottom' => true,
|
155
|
|
|
'left' => true,
|
156
|
|
|
'inner' => true
|
157
|
|
|
);
|
158
|
|
|
/**
|
159
|
|
|
* Whether the data has ANSI colors.
|
160
|
|
|
*
|
161
|
|
|
* @var Console_Color2
|
|
|
|
|
162
|
|
|
*/
|
163
|
|
|
var $_ansiColor = false;
|
164
|
|
|
/**
|
165
|
|
|
* Constructor.
|
166
|
|
|
*
|
167
|
|
|
* @param integer $align Default alignment. One of
|
168
|
|
|
* CONSOLE_TABLE_ALIGN_LEFT,
|
169
|
|
|
* CONSOLE_TABLE_ALIGN_CENTER or
|
170
|
|
|
* CONSOLE_TABLE_ALIGN_RIGHT.
|
171
|
|
|
* @param string $border The character used for table borders or
|
172
|
|
|
* CONSOLE_TABLE_BORDER_ASCII.
|
173
|
|
|
* @param integer $padding How many spaces to use to pad the table.
|
174
|
|
|
* @param string $charset A charset supported by the mbstring PHP
|
175
|
|
|
* extension.
|
176
|
|
|
* @param boolean $color Whether the data contains ansi color codes.
|
177
|
|
|
*/
|
178
|
|
|
function __construct($align = CONSOLE_TABLE_ALIGN_LEFT,
|
|
|
|
|
179
|
|
|
$border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1,
|
180
|
|
|
$charset = null, $color = false)
|
181
|
|
|
{
|
182
|
|
|
$this->_defaultAlign = $align;
|
183
|
|
|
$this->setBorder($border);
|
184
|
|
|
$this->_padding = $padding;
|
185
|
|
|
if ($color) {
|
186
|
|
|
if (!class_exists('Console_Color2')) {
|
187
|
|
|
include_once 'Console/Color2.php';
|
188
|
|
|
}
|
189
|
|
|
$this->_ansiColor = new Console_Color2();
|
190
|
|
|
}
|
191
|
|
|
if (!empty($charset)) {
|
192
|
|
|
$this->setCharset($charset);
|
193
|
|
|
}
|
194
|
|
|
}
|
195
|
|
|
/**
|
196
|
|
|
* Converts an array to a table.
|
197
|
|
|
*
|
198
|
|
|
* @param array $headers Headers for the table.
|
199
|
|
|
* @param array $data A two dimensional array with the table
|
200
|
|
|
* data.
|
201
|
|
|
* @param boolean $returnObject Whether to return the Console_Table object
|
202
|
|
|
* instead of the rendered table.
|
203
|
|
|
*
|
204
|
|
|
* @static
|
205
|
|
|
*
|
206
|
|
|
* @return Console_Table|string A Console_Table object or the generated
|
207
|
|
|
* table.
|
208
|
|
|
*/
|
209
|
|
|
function fromArray($headers, $data, $returnObject = false)
|
|
|
|
|
210
|
|
|
{
|
211
|
|
|
if (!is_array($headers) || !is_array($data)) {
|
|
|
|
|
212
|
|
|
return false;
|
213
|
|
|
}
|
214
|
|
|
$table = new Console_Table();
|
215
|
|
|
$table->setHeaders($headers);
|
216
|
|
|
foreach ($data as $row) {
|
217
|
|
|
$table->addRow($row);
|
218
|
|
|
}
|
219
|
|
|
return $returnObject ? $table : $table->getTable();
|
220
|
|
|
}
|
221
|
|
|
/**
|
222
|
|
|
* Adds a filter to a column.
|
223
|
|
|
*
|
224
|
|
|
* Filters are standard PHP callbacks which are run on the data before
|
225
|
|
|
* table generation is performed. Filters are applied in the order they
|
226
|
|
|
* are added. The callback function must accept a single argument, which
|
227
|
|
|
* is a single table cell.
|
228
|
|
|
*
|
229
|
|
|
* @param integer $col Column to apply filter to.
|
230
|
|
|
* @param mixed &$callback PHP callback to apply.
|
231
|
|
|
*
|
232
|
|
|
* @return void
|
233
|
|
|
*/
|
234
|
|
|
function addFilter($col, &$callback)
|
|
|
|
|
235
|
|
|
{
|
236
|
|
|
$this->_filters[] = array($col, &$callback);
|
237
|
|
|
}
|
238
|
|
|
/**
|
239
|
|
|
* Sets the charset of the provided table data.
|
240
|
|
|
*
|
241
|
|
|
* @param string $charset A charset supported by the mbstring PHP
|
242
|
|
|
* extension.
|
243
|
|
|
*
|
244
|
|
|
* @return void
|
245
|
|
|
*/
|
246
|
|
|
function setCharset($charset)
|
|
|
|
|
247
|
|
|
{
|
248
|
|
|
$locale = setlocale(LC_CTYPE, 0);
|
249
|
|
|
setlocale(LC_CTYPE, 'en_US');
|
250
|
|
|
$this->_charset = strtolower($charset);
|
251
|
|
|
setlocale(LC_CTYPE, $locale);
|
252
|
|
|
}
|
253
|
|
|
/**
|
254
|
|
|
* Set the table border settings
|
255
|
|
|
*
|
256
|
|
|
* Border definition modes:
|
257
|
|
|
* - CONSOLE_TABLE_BORDER_ASCII: Default border with +, - and |
|
258
|
|
|
* - array with keys "intersection", "horizontal" and "vertical"
|
259
|
|
|
* - single character string that sets all three of the array keys
|
260
|
|
|
*
|
261
|
|
|
* @param mixed $border Border definition
|
262
|
|
|
*
|
263
|
|
|
* @return void
|
264
|
|
|
* @see $_border
|
265
|
|
|
*/
|
266
|
|
|
function setBorder($border)
|
|
|
|
|
267
|
|
|
{
|
268
|
|
|
if ($border === CONSOLE_TABLE_BORDER_ASCII) {
|
269
|
|
|
$intersection = '+';
|
270
|
|
|
$horizontal = '-';
|
271
|
|
|
$vertical = '|';
|
272
|
|
|
} else if (is_string($border)) {
|
273
|
|
|
$intersection = $horizontal = $vertical = $border;
|
274
|
|
|
} else if ($border == '') {
|
275
|
|
|
$intersection = $horizontal = $vertical = '';
|
276
|
|
|
} else {
|
277
|
|
|
extract($border);
|
278
|
|
|
}
|
279
|
|
|
$this->_border = array(
|
280
|
|
|
'intersection' => $intersection,
|
281
|
|
|
'horizontal' => $horizontal,
|
282
|
|
|
'vertical' => $vertical,
|
283
|
|
|
);
|
284
|
|
|
}
|
285
|
|
|
/**
|
286
|
|
|
* Set which borders shall be shown.
|
287
|
|
|
*
|
288
|
|
|
* @param array $visibility Visibility settings.
|
289
|
|
|
* Allowed keys: left, right, top, bottom, inner
|
290
|
|
|
*
|
291
|
|
|
* @return void
|
292
|
|
|
* @see $_borderVisibility
|
293
|
|
|
*/
|
294
|
|
|
function setBorderVisibility($visibility)
|
|
|
|
|
295
|
|
|
{
|
296
|
|
|
$this->_borderVisibility = array_merge(
|
297
|
|
|
$this->_borderVisibility,
|
298
|
|
|
array_intersect_key(
|
299
|
|
|
$visibility,
|
300
|
|
|
$this->_borderVisibility
|
301
|
|
|
)
|
302
|
|
|
);
|
303
|
|
|
}
|
304
|
|
|
/**
|
305
|
|
|
* Sets the alignment for the columns.
|
306
|
|
|
*
|
307
|
|
|
* @param integer $col_id The column number.
|
308
|
|
|
* @param integer $align Alignment to set for this column. One of
|
309
|
|
|
* CONSOLE_TABLE_ALIGN_LEFT
|
310
|
|
|
* CONSOLE_TABLE_ALIGN_CENTER
|
311
|
|
|
* CONSOLE_TABLE_ALIGN_RIGHT.
|
312
|
|
|
*
|
313
|
|
|
* @return void
|
314
|
|
|
*/
|
315
|
|
|
function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT)
|
|
|
|
|
316
|
|
|
{
|
317
|
|
|
switch ($align) {
|
318
|
|
|
case CONSOLE_TABLE_ALIGN_CENTER:
|
319
|
|
|
$pad = STR_PAD_BOTH;
|
320
|
|
|
break;
|
321
|
|
|
case CONSOLE_TABLE_ALIGN_RIGHT:
|
322
|
|
|
$pad = STR_PAD_LEFT;
|
323
|
|
|
break;
|
324
|
|
|
default:
|
325
|
|
|
$pad = STR_PAD_RIGHT;
|
326
|
|
|
break;
|
327
|
|
|
}
|
328
|
|
|
$this->_col_align[$col_id] = $pad;
|
329
|
|
|
}
|
330
|
|
|
/**
|
331
|
|
|
* Specifies which columns are to have totals calculated for them and
|
332
|
|
|
* added as a new row at the bottom.
|
333
|
|
|
*
|
334
|
|
|
* @param array $cols Array of column numbers (starting with 0).
|
335
|
|
|
*
|
336
|
|
|
* @return void
|
337
|
|
|
*/
|
338
|
|
|
function calculateTotalsFor($cols)
|
|
|
|
|
339
|
|
|
{
|
340
|
|
|
$this->_calculateTotals = $cols;
|
341
|
|
|
}
|
342
|
|
|
/**
|
343
|
|
|
* Sets the headers for the columns.
|
344
|
|
|
*
|
345
|
|
|
* @param array $headers The column headers.
|
346
|
|
|
*
|
347
|
|
|
* @return void
|
348
|
|
|
*/
|
349
|
|
|
function setHeaders($headers)
|
|
|
|
|
350
|
|
|
{
|
351
|
|
|
$this->_headers = array(array_values($headers));
|
352
|
|
|
$this->_updateRowsCols($headers);
|
353
|
|
|
}
|
354
|
|
|
/**
|
355
|
|
|
* Adds a row to the table.
|
356
|
|
|
*
|
357
|
|
|
* @param array $row The row data to add.
|
358
|
|
|
* @param boolean $append Whether to append or prepend the row.
|
359
|
|
|
*
|
360
|
|
|
* @return void
|
361
|
|
|
*/
|
362
|
|
|
function addRow($row, $append = true)
|
|
|
|
|
363
|
|
|
{
|
364
|
|
|
if ($append) {
|
365
|
|
|
$this->_data[] = array_values($row);
|
366
|
|
|
} else {
|
367
|
|
|
array_unshift($this->_data, array_values($row));
|
368
|
|
|
}
|
369
|
|
|
$this->_updateRowsCols($row);
|
370
|
|
|
}
|
371
|
|
|
/**
|
372
|
|
|
* Inserts a row after a given row number in the table.
|
373
|
|
|
*
|
374
|
|
|
* If $row_id is not given it will prepend the row.
|
375
|
|
|
*
|
376
|
|
|
* @param array $row The data to insert.
|
377
|
|
|
* @param integer $row_id Row number to insert before.
|
378
|
|
|
*
|
379
|
|
|
* @return void
|
380
|
|
|
*/
|
381
|
|
|
function insertRow($row, $row_id = 0)
|
|
|
|
|
382
|
|
|
{
|
383
|
|
|
array_splice($this->_data, $row_id, 0, array($row));
|
384
|
|
|
$this->_updateRowsCols($row);
|
385
|
|
|
}
|
386
|
|
|
/**
|
387
|
|
|
* Adds a column to the table.
|
388
|
|
|
*
|
389
|
|
|
* @param array $col_data The data of the column.
|
390
|
|
|
* @param integer $col_id The column index to populate.
|
391
|
|
|
* @param integer $row_id If starting row is not zero, specify it here.
|
392
|
|
|
*
|
393
|
|
|
* @return void
|
394
|
|
|
*/
|
395
|
|
|
function addCol($col_data, $col_id = 0, $row_id = 0)
|
|
|
|
|
396
|
|
|
{
|
397
|
|
|
foreach ($col_data as $col_cell) {
|
398
|
|
|
$this->_data[$row_id++][$col_id] = $col_cell;
|
399
|
|
|
}
|
400
|
|
|
$this->_updateRowsCols();
|
401
|
|
|
$this->_max_cols = max($this->_max_cols, $col_id + 1);
|
402
|
|
|
}
|
403
|
|
|
/**
|
404
|
|
|
* Adds data to the table.
|
405
|
|
|
*
|
406
|
|
|
* @param array $data A two dimensional array with the table data.
|
407
|
|
|
* @param integer $col_id Starting column number.
|
408
|
|
|
* @param integer $row_id Starting row number.
|
409
|
|
|
*
|
410
|
|
|
* @return void
|
411
|
|
|
*/
|
412
|
|
|
function addData($data, $col_id = 0, $row_id = 0)
|
|
|
|
|
413
|
|
|
{
|
414
|
|
|
foreach ($data as $row) {
|
415
|
|
|
if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
|
416
|
|
|
$this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE;
|
417
|
|
|
$row_id++;
|
418
|
|
|
continue;
|
419
|
|
|
}
|
420
|
|
|
$starting_col = $col_id;
|
421
|
|
|
foreach ($row as $cell) {
|
422
|
|
|
$this->_data[$row_id][$starting_col++] = $cell;
|
423
|
|
|
}
|
424
|
|
|
$this->_updateRowsCols();
|
425
|
|
|
$this->_max_cols = max($this->_max_cols, $starting_col);
|
426
|
|
|
$row_id++;
|
427
|
|
|
}
|
428
|
|
|
}
|
429
|
|
|
/**
|
430
|
|
|
* Adds a horizontal seperator to the table.
|
431
|
|
|
*
|
432
|
|
|
* @return void
|
433
|
|
|
*/
|
434
|
|
|
function addSeparator()
|
|
|
|
|
435
|
|
|
{
|
436
|
|
|
$this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE;
|
437
|
|
|
}
|
438
|
|
|
/**
|
439
|
|
|
* Returns the generated table.
|
440
|
|
|
*
|
441
|
|
|
* @return string The generated table.
|
442
|
|
|
*/
|
443
|
|
|
function getTable()
|
|
|
|
|
444
|
|
|
{
|
445
|
|
|
$this->_applyFilters();
|
446
|
|
|
$this->_calculateTotals();
|
447
|
|
|
$this->_validateTable();
|
448
|
|
|
return $this->_buildTable();
|
449
|
|
|
}
|
450
|
|
|
/**
|
451
|
|
|
* Calculates totals for columns.
|
452
|
|
|
*
|
453
|
|
|
* @return void
|
454
|
|
|
*/
|
455
|
|
|
function _calculateTotals()
|
|
|
|
|
456
|
|
|
{
|
457
|
|
|
if (empty($this->_calculateTotals)) {
|
458
|
|
|
return;
|
459
|
|
|
}
|
460
|
|
|
$this->addSeparator();
|
461
|
|
|
$totals = array();
|
462
|
|
|
foreach ($this->_data as $row) {
|
463
|
|
|
if (is_array($row)) {
|
464
|
|
|
foreach ($this->_calculateTotals as $columnID) {
|
465
|
|
|
$totals[$columnID] += $row[$columnID];
|
466
|
|
|
}
|
467
|
|
|
}
|
468
|
|
|
}
|
469
|
|
|
$this->_data[] = $totals;
|
470
|
|
|
$this->_updateRowsCols();
|
471
|
|
|
}
|
472
|
|
|
/**
|
473
|
|
|
* Applies any column filters to the data.
|
474
|
|
|
*
|
475
|
|
|
* @return void
|
476
|
|
|
*/
|
477
|
|
|
function _applyFilters()
|
|
|
|
|
478
|
|
|
{
|
479
|
|
|
if (empty($this->_filters)) {
|
480
|
|
|
return;
|
481
|
|
|
}
|
482
|
|
|
foreach ($this->_filters as $filter) {
|
483
|
|
|
$column = $filter[0];
|
484
|
|
|
$callback = $filter[1];
|
485
|
|
|
foreach ($this->_data as $row_id => $row_data) {
|
486
|
|
|
if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) {
|
487
|
|
|
$this->_data[$row_id][$column] =
|
488
|
|
|
call_user_func($callback, $row_data[$column]);
|
489
|
|
|
}
|
490
|
|
|
}
|
491
|
|
|
}
|
492
|
|
|
}
|
493
|
|
|
/**
|
494
|
|
|
* Ensures that column and row counts are correct.
|
495
|
|
|
*
|
496
|
|
|
* @return void
|
497
|
|
|
*/
|
498
|
|
|
function _validateTable()
|
|
|
|
|
499
|
|
|
{
|
500
|
|
|
if (!empty($this->_headers)) {
|
501
|
|
|
$this->_calculateRowHeight(-1, $this->_headers[0]);
|
502
|
|
|
}
|
503
|
|
|
for ($i = 0; $i < $this->_max_rows; $i++) {
|
504
|
|
|
for ($j = 0; $j < $this->_max_cols; $j++) {
|
505
|
|
|
if (!isset($this->_data[$i][$j]) &&
|
506
|
|
|
(!isset($this->_data[$i]) ||
|
507
|
|
|
$this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) {
|
508
|
|
|
$this->_data[$i][$j] = '';
|
509
|
|
|
}
|
510
|
|
|
}
|
511
|
|
|
$this->_calculateRowHeight($i, $this->_data[$i]);
|
512
|
|
|
if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
|
513
|
|
|
ksort($this->_data[$i]);
|
514
|
|
|
}
|
515
|
|
|
}
|
516
|
|
|
$this->_splitMultilineRows();
|
517
|
|
|
// Update cell lengths.
|
518
|
|
|
for ($i = 0; $i < count($this->_headers); $i++) {
|
|
|
|
|
519
|
|
|
$this->_calculateCellLengths($this->_headers[$i]);
|
520
|
|
|
}
|
521
|
|
|
for ($i = 0; $i < $this->_max_rows; $i++) {
|
522
|
|
|
$this->_calculateCellLengths($this->_data[$i]);
|
523
|
|
|
}
|
524
|
|
|
ksort($this->_data);
|
525
|
|
|
}
|
526
|
|
|
/**
|
527
|
|
|
* Splits multiline rows into many smaller one-line rows.
|
528
|
|
|
*
|
529
|
|
|
* @return void
|
530
|
|
|
*/
|
531
|
|
|
function _splitMultilineRows()
|
|
|
|
|
532
|
|
|
{
|
533
|
|
|
ksort($this->_data);
|
534
|
|
|
$sections = array(&$this->_headers, &$this->_data);
|
535
|
|
|
$max_rows = array(count($this->_headers), $this->_max_rows);
|
536
|
|
|
$row_height_offset = array(-1, 0);
|
537
|
|
|
for ($s = 0; $s <= 1; $s++) {
|
538
|
|
|
$inserted = 0;
|
539
|
|
|
$new_data = $sections[$s];
|
540
|
|
|
for ($i = 0; $i < $max_rows[$s]; $i++) {
|
541
|
|
|
// Process only rows that have many lines.
|
542
|
|
|
$height = $this->_row_heights[$i + $row_height_offset[$s]];
|
543
|
|
|
if ($height > 1) {
|
544
|
|
|
// Split column data into one-liners.
|
545
|
|
|
$split = array();
|
546
|
|
|
for ($j = 0; $j < $this->_max_cols; $j++) {
|
547
|
|
|
$split[$j] = preg_split('/\r?\n|\r/',
|
548
|
|
|
$sections[$s][$i][$j]);
|
549
|
|
|
}
|
550
|
|
|
$new_rows = array();
|
551
|
|
|
// Construct new 'virtual' rows - insert empty strings for
|
552
|
|
|
// columns that have less lines that the highest one.
|
553
|
|
|
for ($i2 = 0; $i2 < $height; $i2++) {
|
554
|
|
|
for ($j = 0; $j < $this->_max_cols; $j++) {
|
555
|
|
|
$new_rows[$i2][$j] = !isset($split[$j][$i2])
|
556
|
|
|
? ''
|
557
|
|
|
: $split[$j][$i2];
|
558
|
|
|
}
|
559
|
|
|
}
|
560
|
|
|
// Replace current row with smaller rows. $inserted is
|
561
|
|
|
// used to take account of bigger array because of already
|
562
|
|
|
// inserted rows.
|
563
|
|
|
array_splice($new_data, $i + $inserted, 1, $new_rows);
|
564
|
|
|
$inserted += count($new_rows) - 1;
|
565
|
|
|
}
|
566
|
|
|
}
|
567
|
|
|
// Has the data been modified?
|
568
|
|
|
if ($inserted > 0) {
|
569
|
|
|
$sections[$s] = $new_data;
|
570
|
|
|
$this->_updateRowsCols();
|
571
|
|
|
}
|
572
|
|
|
}
|
573
|
|
|
}
|
574
|
|
|
/**
|
575
|
|
|
* Builds the table.
|
576
|
|
|
*
|
577
|
|
|
* @return string The generated table string.
|
578
|
|
|
*/
|
579
|
|
|
function _buildTable()
|
|
|
|
|
580
|
|
|
{
|
581
|
|
|
if (!count($this->_data)) {
|
582
|
|
|
return '';
|
583
|
|
|
}
|
584
|
|
|
$vertical = $this->_border['vertical'];
|
585
|
|
|
$separator = $this->_getSeparator();
|
586
|
|
|
$return = array();
|
587
|
|
|
for ($i = 0; $i < count($this->_data); $i++) {
|
|
|
|
|
588
|
|
|
for ($j = 0; $j < count($this->_data[$i]); $j++) {
|
|
|
|
|
589
|
|
|
if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE &&
|
590
|
|
|
$this->_strlen($this->_data[$i][$j]) <
|
591
|
|
|
$this->_cell_lengths[$j]) {
|
592
|
|
|
$this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j],
|
593
|
|
|
$this->_cell_lengths[$j],
|
594
|
|
|
' ',
|
595
|
|
|
$this->_col_align[$j]);
|
596
|
|
|
}
|
597
|
|
|
}
|
598
|
|
|
if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
|
599
|
|
|
$row_begin = $this->_borderVisibility['left']
|
600
|
|
|
? $vertical . str_repeat(' ', $this->_padding)
|
601
|
|
|
: '';
|
602
|
|
|
$row_end = $this->_borderVisibility['right']
|
603
|
|
|
? str_repeat(' ', $this->_padding) . $vertical
|
604
|
|
|
: '';
|
605
|
|
|
$implode_char = str_repeat(' ', $this->_padding) . $vertical
|
606
|
|
|
. str_repeat(' ', $this->_padding);
|
607
|
|
|
$return[] = $row_begin
|
608
|
|
|
. implode($implode_char, $this->_data[$i]) . $row_end;
|
609
|
|
|
} elseif (!empty($separator)) {
|
610
|
|
|
$return[] = $separator;
|
611
|
|
|
}
|
612
|
|
|
}
|
613
|
|
|
$return = implode(PHP_EOL, $return);
|
614
|
|
|
if (!empty($separator)) {
|
615
|
|
|
if ($this->_borderVisibility['inner']) {
|
616
|
|
|
$return = $separator . PHP_EOL . $return;
|
617
|
|
|
}
|
618
|
|
|
if ($this->_borderVisibility['bottom']) {
|
619
|
|
|
$return .= PHP_EOL . $separator;
|
620
|
|
|
}
|
621
|
|
|
}
|
622
|
|
|
$return .= PHP_EOL;
|
623
|
|
|
if (!empty($this->_headers)) {
|
624
|
|
|
$return = $this->_getHeaderLine() . PHP_EOL . $return;
|
625
|
|
|
}
|
626
|
|
|
return $return;
|
627
|
|
|
}
|
628
|
|
|
/**
|
629
|
|
|
* Creates a horizontal separator for header separation and table
|
630
|
|
|
* start/end etc.
|
631
|
|
|
*
|
632
|
|
|
* @return string The horizontal separator.
|
633
|
|
|
*/
|
634
|
|
|
function _getSeparator()
|
|
|
|
|
635
|
|
|
{
|
636
|
|
|
if (!$this->_border) {
|
|
|
|
|
637
|
|
|
return;
|
638
|
|
|
}
|
639
|
|
|
$horizontal = $this->_border['horizontal'];
|
640
|
|
|
$intersection = $this->_border['intersection'];
|
641
|
|
|
$return = array();
|
642
|
|
|
foreach ($this->_cell_lengths as $cl) {
|
643
|
|
|
$return[] = str_repeat($horizontal, $cl);
|
644
|
|
|
}
|
645
|
|
|
$row_begin = $this->_borderVisibility['left']
|
646
|
|
|
? $intersection . str_repeat($horizontal, $this->_padding)
|
647
|
|
|
: '';
|
648
|
|
|
$row_end = $this->_borderVisibility['right']
|
649
|
|
|
? str_repeat($horizontal, $this->_padding) . $intersection
|
650
|
|
|
: '';
|
651
|
|
|
$implode_char = str_repeat($horizontal, $this->_padding) . $intersection
|
652
|
|
|
. str_repeat($horizontal, $this->_padding);
|
653
|
|
|
return $row_begin . implode($implode_char, $return) . $row_end;
|
654
|
|
|
}
|
655
|
|
|
/**
|
656
|
|
|
* Returns the header line for the table.
|
657
|
|
|
*
|
658
|
|
|
* @return string The header line of the table.
|
659
|
|
|
*/
|
660
|
|
|
function _getHeaderLine()
|
|
|
|
|
661
|
|
|
{
|
662
|
|
|
// Make sure column count is correct
|
663
|
|
|
for ($j = 0; $j < count($this->_headers); $j++) {
|
|
|
|
|
664
|
|
|
for ($i = 0; $i < $this->_max_cols; $i++) {
|
665
|
|
|
if (!isset($this->_headers[$j][$i])) {
|
666
|
|
|
$this->_headers[$j][$i] = '';
|
667
|
|
|
}
|
668
|
|
|
}
|
669
|
|
|
}
|
670
|
|
|
for ($j = 0; $j < count($this->_headers); $j++) {
|
|
|
|
|
671
|
|
|
for ($i = 0; $i < count($this->_headers[$j]); $i++) {
|
|
|
|
|
672
|
|
|
if ($this->_strlen($this->_headers[$j][$i]) <
|
673
|
|
|
$this->_cell_lengths[$i]) {
|
674
|
|
|
$this->_headers[$j][$i] =
|
675
|
|
|
$this->_strpad($this->_headers[$j][$i],
|
676
|
|
|
$this->_cell_lengths[$i],
|
677
|
|
|
' ',
|
678
|
|
|
$this->_col_align[$i]);
|
679
|
|
|
}
|
680
|
|
|
}
|
681
|
|
|
}
|
682
|
|
|
$vertical = $this->_border['vertical'];
|
683
|
|
|
$row_begin = $this->_borderVisibility['left']
|
684
|
|
|
? $vertical . str_repeat(' ', $this->_padding)
|
685
|
|
|
: '';
|
686
|
|
|
$row_end = $this->_borderVisibility['right']
|
687
|
|
|
? str_repeat(' ', $this->_padding) . $vertical
|
688
|
|
|
: '';
|
689
|
|
|
$implode_char = str_repeat(' ', $this->_padding) . $vertical
|
690
|
|
|
. str_repeat(' ', $this->_padding);
|
691
|
|
|
$separator = $this->_getSeparator();
|
692
|
|
|
if (!empty($separator) && $this->_borderVisibility['top']) {
|
693
|
|
|
$return[] = $separator;
|
|
|
|
|
694
|
|
|
}
|
695
|
|
|
for ($j = 0; $j < count($this->_headers); $j++) {
|
|
|
|
|
696
|
|
|
$return[] = $row_begin
|
697
|
|
|
. implode($implode_char, $this->_headers[$j]) . $row_end;
|
698
|
|
|
}
|
699
|
|
|
return implode(PHP_EOL, $return);
|
|
|
|
|
700
|
|
|
}
|
701
|
|
|
/**
|
702
|
|
|
* Updates values for maximum columns and rows.
|
703
|
|
|
*
|
704
|
|
|
* @param array $rowdata Data array of a single row.
|
705
|
|
|
*
|
706
|
|
|
* @return void
|
707
|
|
|
*/
|
708
|
|
|
function _updateRowsCols($rowdata = null)
|
|
|
|
|
709
|
|
|
{
|
710
|
|
|
// Update maximum columns.
|
711
|
|
|
$this->_max_cols = max($this->_max_cols, count($rowdata));
|
|
|
|
|
712
|
|
|
// Update maximum rows.
|
713
|
|
|
ksort($this->_data);
|
714
|
|
|
$keys = array_keys($this->_data);
|
715
|
|
|
$this->_max_rows = end($keys) + 1;
|
716
|
|
|
switch ($this->_defaultAlign) {
|
717
|
|
|
case CONSOLE_TABLE_ALIGN_CENTER:
|
718
|
|
|
$pad = STR_PAD_BOTH;
|
719
|
|
|
break;
|
720
|
|
|
case CONSOLE_TABLE_ALIGN_RIGHT:
|
721
|
|
|
$pad = STR_PAD_LEFT;
|
722
|
|
|
break;
|
723
|
|
|
default:
|
724
|
|
|
$pad = STR_PAD_RIGHT;
|
725
|
|
|
break;
|
726
|
|
|
}
|
727
|
|
|
// Set default column alignments
|
728
|
|
|
for ($i = 0; $i < $this->_max_cols; $i++) {
|
729
|
|
|
if (!isset($this->_col_align[$i])) {
|
730
|
|
|
$this->_col_align[$i] = $pad;
|
731
|
|
|
}
|
732
|
|
|
}
|
733
|
|
|
}
|
734
|
|
|
/**
|
735
|
|
|
* Calculates the maximum length for each column of a row.
|
736
|
|
|
*
|
737
|
|
|
* @param array $row The row data.
|
738
|
|
|
*
|
739
|
|
|
* @return void
|
740
|
|
|
*/
|
741
|
|
|
function _calculateCellLengths($row)
|
|
|
|
|
742
|
|
|
{
|
743
|
|
|
for ($i = 0; $i < count($row); $i++) {
|
|
|
|
|
744
|
|
|
if (!isset($this->_cell_lengths[$i])) {
|
745
|
|
|
$this->_cell_lengths[$i] = 0;
|
746
|
|
|
}
|
747
|
|
|
$this->_cell_lengths[$i] = max($this->_cell_lengths[$i],
|
748
|
|
|
$this->_strlen($row[$i]));
|
749
|
|
|
}
|
750
|
|
|
}
|
751
|
|
|
/**
|
752
|
|
|
* Calculates the maximum height for all columns of a row.
|
753
|
|
|
*
|
754
|
|
|
* @param integer $row_number The row number.
|
755
|
|
|
* @param array $row The row data.
|
756
|
|
|
*
|
757
|
|
|
* @return void
|
758
|
|
|
*/
|
759
|
|
|
function _calculateRowHeight($row_number, $row)
|
|
|
|
|
760
|
|
|
{
|
761
|
|
|
if (!isset($this->_row_heights[$row_number])) {
|
762
|
|
|
$this->_row_heights[$row_number] = 1;
|
763
|
|
|
}
|
764
|
|
|
// Do not process horizontal rule rows.
|
765
|
|
|
if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
|
|
|
|
|
766
|
|
|
return;
|
767
|
|
|
}
|
768
|
|
|
for ($i = 0, $c = count($row); $i < $c; ++$i) {
|
769
|
|
|
$lines = preg_split('/\r?\n|\r/', $row[$i]);
|
770
|
|
|
$this->_row_heights[$row_number] = max($this->_row_heights[$row_number],
|
771
|
|
|
count($lines));
|
772
|
|
|
}
|
773
|
|
|
}
|
774
|
|
|
/**
|
775
|
|
|
* Returns the character length of a string.
|
776
|
|
|
*
|
777
|
|
|
* @param string $str A multibyte or singlebyte string.
|
778
|
|
|
*
|
779
|
|
|
* @return integer The string length.
|
780
|
|
|
*/
|
781
|
|
|
function _strlen($str)
|
|
|
|
|
782
|
|
|
{
|
783
|
|
|
static $mbstring;
|
784
|
|
|
// Strip ANSI color codes if requested.
|
785
|
|
|
if ($this->_ansiColor) {
|
786
|
|
|
$str = $this->_ansiColor->strip($str);
|
787
|
|
|
}
|
788
|
|
|
// Cache expensive function_exists() calls.
|
789
|
|
|
if (!isset($mbstring)) {
|
790
|
|
|
$mbstring = function_exists('mb_strwidth');
|
791
|
|
|
}
|
792
|
|
|
if ($mbstring) {
|
793
|
|
|
return mb_strwidth($str, $this->_charset);
|
794
|
|
|
}
|
795
|
|
|
return strlen($str);
|
796
|
|
|
}
|
797
|
|
|
/**
|
798
|
|
|
* Returns part of a string.
|
799
|
|
|
*
|
800
|
|
|
* @param string $string The string to be converted.
|
801
|
|
|
* @param integer $start The part's start position, zero based.
|
802
|
|
|
* @param integer $length The part's length.
|
803
|
|
|
*
|
804
|
|
|
* @return string The string's part.
|
805
|
|
|
*/
|
806
|
|
|
function _substr($string, $start, $length = null)
|
|
|
|
|
807
|
|
|
{
|
808
|
|
|
static $mbstring;
|
809
|
|
|
// Cache expensive function_exists() calls.
|
810
|
|
|
if (!isset($mbstring)) {
|
811
|
|
|
$mbstring = function_exists('mb_substr');
|
812
|
|
|
}
|
813
|
|
|
if (is_null($length)) {
|
814
|
|
|
$length = $this->_strlen($string);
|
815
|
|
|
}
|
816
|
|
|
if ($mbstring) {
|
817
|
|
|
$ret = @mb_substr($string, $start, $length, $this->_charset);
|
818
|
|
|
if (!empty($ret)) {
|
819
|
|
|
return $ret;
|
820
|
|
|
}
|
821
|
|
|
}
|
822
|
|
|
return substr($string, $start, $length);
|
823
|
|
|
}
|
824
|
|
|
/**
|
825
|
|
|
* Returns a string padded to a certain length with another string.
|
826
|
|
|
*
|
827
|
|
|
* This method behaves exactly like str_pad but is multibyte safe.
|
828
|
|
|
*
|
829
|
|
|
* @param string $input The string to be padded.
|
830
|
|
|
* @param integer $length The length of the resulting string.
|
831
|
|
|
* @param string $pad The string to pad the input string with. Must
|
832
|
|
|
* be in the same charset like the input string.
|
833
|
|
|
* @param const $type The padding type. One of STR_PAD_LEFT,
|
|
|
|
|
834
|
|
|
* STR_PAD_RIGHT, or STR_PAD_BOTH.
|
835
|
|
|
*
|
836
|
|
|
* @return string The padded string.
|
837
|
|
|
*/
|
838
|
|
|
function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT)
|
|
|
|
|
839
|
|
|
{
|
840
|
|
|
$mb_length = $this->_strlen($input);
|
841
|
|
|
$sb_length = strlen($input);
|
842
|
|
|
$pad_length = $this->_strlen($pad);
|
843
|
|
|
/* Return if we already have the length. */
|
844
|
|
|
if ($mb_length >= $length) {
|
845
|
|
|
return $input;
|
846
|
|
|
}
|
847
|
|
|
/* Shortcut for single byte strings. */
|
848
|
|
|
if ($mb_length == $sb_length && $pad_length == strlen($pad)) {
|
849
|
|
|
return str_pad($input, $length, $pad, $type);
|
|
|
|
|
850
|
|
|
}
|
851
|
|
|
switch ($type) {
|
852
|
|
|
case STR_PAD_LEFT:
|
853
|
|
|
$left = $length - $mb_length;
|
854
|
|
|
$output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)),
|
|
|
|
|
855
|
|
|
0, $left, $this->_charset) . $input;
|
|
|
|
|
856
|
|
|
break;
|
857
|
|
|
case STR_PAD_BOTH:
|
858
|
|
|
$left = floor(($length - $mb_length) / 2);
|
859
|
|
|
$right = ceil(($length - $mb_length) / 2);
|
860
|
|
|
$output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)),
|
861
|
|
|
0, $left, $this->_charset) .
|
|
|
|
|
862
|
|
|
$input .
|
863
|
|
|
$this->_substr(str_repeat($pad, ceil($right / $pad_length)),
|
864
|
|
|
0, $right, $this->_charset);
|
865
|
|
|
break;
|
866
|
|
|
case STR_PAD_RIGHT:
|
867
|
|
|
$right = $length - $mb_length;
|
868
|
|
|
$output = $input .
|
869
|
|
|
$this->_substr(str_repeat($pad, ceil($right / $pad_length)),
|
870
|
|
|
0, $right, $this->_charset);
|
871
|
|
|
break;
|
872
|
|
|
}
|
873
|
|
|
return $output;
|
|
|
|
|
874
|
|
|
}
|
875
|
|
|
} |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths