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