annotate vendor/pear/console_table/Table.php @ 19:fa3358dc1485 tip

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