[ Index ] |
PHP Cross Reference of phpBB-3.3.12-deutsch |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12 namespace Symfony\Component\Console\Helper; 13 14 use Symfony\Component\Console\Exception\InvalidArgumentException; 15 use Symfony\Component\Console\Output\OutputInterface; 16 17 /** 18 * Provides helpers to display a table. 19 * 20 * @author Fabien Potencier <fabien@symfony.com> 21 * @author Саша Стаменковић <umpirsky@gmail.com> 22 * @author Abdellatif Ait boudad <a.aitboudad@gmail.com> 23 * @author Max Grigorian <maxakawizard@gmail.com> 24 */ 25 class Table 26 { 27 /** 28 * Table headers. 29 */ 30 private $headers = []; 31 32 /** 33 * Table rows. 34 */ 35 private $rows = []; 36 37 /** 38 * Column widths cache. 39 */ 40 private $effectiveColumnWidths = []; 41 42 /** 43 * Number of columns cache. 44 * 45 * @var int 46 */ 47 private $numberOfColumns; 48 49 /** 50 * @var OutputInterface 51 */ 52 private $output; 53 54 /** 55 * @var TableStyle 56 */ 57 private $style; 58 59 /** 60 * @var array 61 */ 62 private $columnStyles = []; 63 64 /** 65 * User set column widths. 66 * 67 * @var array 68 */ 69 private $columnWidths = []; 70 71 private static $styles; 72 73 public function __construct(OutputInterface $output) 74 { 75 $this->output = $output; 76 77 if (!self::$styles) { 78 self::$styles = self::initStyles(); 79 } 80 81 $this->setStyle('default'); 82 } 83 84 /** 85 * Sets a style definition. 86 * 87 * @param string $name The style name 88 * @param TableStyle $style A TableStyle instance 89 */ 90 public static function setStyleDefinition($name, TableStyle $style) 91 { 92 if (!self::$styles) { 93 self::$styles = self::initStyles(); 94 } 95 96 self::$styles[$name] = $style; 97 } 98 99 /** 100 * Gets a style definition by name. 101 * 102 * @param string $name The style name 103 * 104 * @return TableStyle 105 */ 106 public static function getStyleDefinition($name) 107 { 108 if (!self::$styles) { 109 self::$styles = self::initStyles(); 110 } 111 112 if (isset(self::$styles[$name])) { 113 return self::$styles[$name]; 114 } 115 116 throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); 117 } 118 119 /** 120 * Sets table style. 121 * 122 * @param TableStyle|string $name The style name or a TableStyle instance 123 * 124 * @return $this 125 */ 126 public function setStyle($name) 127 { 128 $this->style = $this->resolveStyle($name); 129 130 return $this; 131 } 132 133 /** 134 * Gets the current table style. 135 * 136 * @return TableStyle 137 */ 138 public function getStyle() 139 { 140 return $this->style; 141 } 142 143 /** 144 * Sets table column style. 145 * 146 * @param int $columnIndex Column index 147 * @param TableStyle|string $name The style name or a TableStyle instance 148 * 149 * @return $this 150 */ 151 public function setColumnStyle($columnIndex, $name) 152 { 153 $columnIndex = (int) $columnIndex; 154 155 $this->columnStyles[$columnIndex] = $this->resolveStyle($name); 156 157 return $this; 158 } 159 160 /** 161 * Gets the current style for a column. 162 * 163 * If style was not set, it returns the global table style. 164 * 165 * @param int $columnIndex Column index 166 * 167 * @return TableStyle 168 */ 169 public function getColumnStyle($columnIndex) 170 { 171 if (isset($this->columnStyles[$columnIndex])) { 172 return $this->columnStyles[$columnIndex]; 173 } 174 175 return $this->getStyle(); 176 } 177 178 /** 179 * Sets the minimum width of a column. 180 * 181 * @param int $columnIndex Column index 182 * @param int $width Minimum column width in characters 183 * 184 * @return $this 185 */ 186 public function setColumnWidth($columnIndex, $width) 187 { 188 $this->columnWidths[(int) $columnIndex] = (int) $width; 189 190 return $this; 191 } 192 193 /** 194 * Sets the minimum width of all columns. 195 * 196 * @return $this 197 */ 198 public function setColumnWidths(array $widths) 199 { 200 $this->columnWidths = []; 201 foreach ($widths as $index => $width) { 202 $this->setColumnWidth($index, $width); 203 } 204 205 return $this; 206 } 207 208 public function setHeaders(array $headers) 209 { 210 $headers = array_values($headers); 211 if (!empty($headers) && !\is_array($headers[0])) { 212 $headers = [$headers]; 213 } 214 215 $this->headers = $headers; 216 217 return $this; 218 } 219 220 public function setRows(array $rows) 221 { 222 $this->rows = []; 223 224 return $this->addRows($rows); 225 } 226 227 public function addRows(array $rows) 228 { 229 foreach ($rows as $row) { 230 $this->addRow($row); 231 } 232 233 return $this; 234 } 235 236 public function addRow($row) 237 { 238 if ($row instanceof TableSeparator) { 239 $this->rows[] = $row; 240 241 return $this; 242 } 243 244 if (!\is_array($row)) { 245 throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); 246 } 247 248 $this->rows[] = array_values($row); 249 250 return $this; 251 } 252 253 public function setRow($column, array $row) 254 { 255 $this->rows[$column] = $row; 256 257 return $this; 258 } 259 260 /** 261 * Renders table to output. 262 * 263 * Example: 264 * 265 * +---------------+-----------------------+------------------+ 266 * | ISBN | Title | Author | 267 * +---------------+-----------------------+------------------+ 268 * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 269 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 270 * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | 271 * +---------------+-----------------------+------------------+ 272 */ 273 public function render() 274 { 275 $this->calculateNumberOfColumns(); 276 $rows = $this->buildTableRows($this->rows); 277 $headers = $this->buildTableRows($this->headers); 278 279 $this->calculateColumnsWidth(array_merge($headers, $rows)); 280 281 $this->renderRowSeparator(); 282 if (!empty($headers)) { 283 foreach ($headers as $header) { 284 $this->renderRow($header, $this->style->getCellHeaderFormat()); 285 $this->renderRowSeparator(); 286 } 287 } 288 foreach ($rows as $row) { 289 if ($row instanceof TableSeparator) { 290 $this->renderRowSeparator(); 291 } else { 292 $this->renderRow($row, $this->style->getCellRowFormat()); 293 } 294 } 295 if (!empty($rows)) { 296 $this->renderRowSeparator(); 297 } 298 299 $this->cleanup(); 300 } 301 302 /** 303 * Renders horizontal header separator. 304 * 305 * Example: 306 * 307 * +-----+-----------+-------+ 308 */ 309 private function renderRowSeparator() 310 { 311 if (0 === $count = $this->numberOfColumns) { 312 return; 313 } 314 315 if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { 316 return; 317 } 318 319 $markup = $this->style->getCrossingChar(); 320 for ($column = 0; $column < $count; ++$column) { 321 $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->effectiveColumnWidths[$column]).$this->style->getCrossingChar(); 322 } 323 324 $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); 325 } 326 327 /** 328 * Renders vertical column separator. 329 */ 330 private function renderColumnSeparator() 331 { 332 return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); 333 } 334 335 /** 336 * Renders table row. 337 * 338 * Example: 339 * 340 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 341 * 342 * @param string $cellFormat 343 */ 344 private function renderRow(array $row, $cellFormat) 345 { 346 if (empty($row)) { 347 return; 348 } 349 350 $rowContent = $this->renderColumnSeparator(); 351 foreach ($this->getRowColumns($row) as $column) { 352 $rowContent .= $this->renderCell($row, $column, $cellFormat); 353 $rowContent .= $this->renderColumnSeparator(); 354 } 355 $this->output->writeln($rowContent); 356 } 357 358 /** 359 * Renders table cell with padding. 360 * 361 * @param int $column 362 * @param string $cellFormat 363 */ 364 private function renderCell(array $row, $column, $cellFormat) 365 { 366 $cell = isset($row[$column]) ? $row[$column] : ''; 367 $width = $this->effectiveColumnWidths[$column]; 368 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 369 // add the width of the following columns(numbers of colspan). 370 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { 371 $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; 372 } 373 } 374 375 // str_pad won't work properly with multi-byte strings, we need to fix the padding 376 if (false !== $encoding = mb_detect_encoding($cell, null, true)) { 377 $width += \strlen($cell) - mb_strwidth($cell, $encoding); 378 } 379 380 $style = $this->getColumnStyle($column); 381 382 if ($cell instanceof TableSeparator) { 383 return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width)); 384 } 385 386 $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); 387 $content = sprintf($style->getCellRowContentFormat(), $cell); 388 389 return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); 390 } 391 392 /** 393 * Calculate number of columns for this table. 394 */ 395 private function calculateNumberOfColumns() 396 { 397 if (null !== $this->numberOfColumns) { 398 return; 399 } 400 401 $columns = [0]; 402 foreach (array_merge($this->headers, $this->rows) as $row) { 403 if ($row instanceof TableSeparator) { 404 continue; 405 } 406 407 $columns[] = $this->getNumberOfColumns($row); 408 } 409 410 $this->numberOfColumns = max($columns); 411 } 412 413 private function buildTableRows($rows) 414 { 415 $unmergedRows = []; 416 for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { 417 $rows = $this->fillNextRows($rows, $rowKey); 418 419 // Remove any new line breaks and replace it with a new line 420 foreach ($rows[$rowKey] as $column => $cell) { 421 if (!strstr($cell, "\n")) { 422 continue; 423 } 424 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); 425 foreach ($lines as $lineKey => $line) { 426 if ($cell instanceof TableCell) { 427 $line = new TableCell($line, ['colspan' => $cell->getColspan()]); 428 } 429 if (0 === $lineKey) { 430 $rows[$rowKey][$column] = $line; 431 } else { 432 $unmergedRows[$rowKey][$lineKey][$column] = $line; 433 } 434 } 435 } 436 } 437 438 $tableRows = []; 439 foreach ($rows as $rowKey => $row) { 440 $tableRows[] = $this->fillCells($row); 441 if (isset($unmergedRows[$rowKey])) { 442 $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); 443 } 444 } 445 446 return $tableRows; 447 } 448 449 /** 450 * fill rows that contains rowspan > 1. 451 * 452 * @param int $line 453 * 454 * @return array 455 * 456 * @throws InvalidArgumentException 457 */ 458 private function fillNextRows(array $rows, $line) 459 { 460 $unmergedRows = []; 461 foreach ($rows[$line] as $column => $cell) { 462 if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { 463 throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', \gettype($cell))); 464 } 465 if ($cell instanceof TableCell && $cell->getRowspan() > 1) { 466 $nbLines = $cell->getRowspan() - 1; 467 $lines = [$cell]; 468 if (strstr($cell, "\n")) { 469 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); 470 $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; 471 472 $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan()]); 473 unset($lines[0]); 474 } 475 476 // create a two dimensional array (rowspan x colspan) 477 $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); 478 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { 479 $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; 480 $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan()]); 481 if ($nbLines === $unmergedRowKey - $line) { 482 break; 483 } 484 } 485 } 486 } 487 488 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { 489 // we need to know if $unmergedRow will be merged or inserted into $rows 490 if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { 491 foreach ($unmergedRow as $cellKey => $cell) { 492 // insert cell into row at cellKey position 493 array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); 494 } 495 } else { 496 $row = $this->copyRow($rows, $unmergedRowKey - 1); 497 foreach ($unmergedRow as $column => $cell) { 498 if (!empty($cell)) { 499 $row[$column] = $unmergedRow[$column]; 500 } 501 } 502 array_splice($rows, $unmergedRowKey, 0, [$row]); 503 } 504 } 505 506 return $rows; 507 } 508 509 /** 510 * fill cells for a row that contains colspan > 1. 511 * 512 * @return array 513 */ 514 private function fillCells($row) 515 { 516 $newRow = []; 517 foreach ($row as $column => $cell) { 518 $newRow[] = $cell; 519 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 520 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { 521 // insert empty value at column position 522 $newRow[] = ''; 523 } 524 } 525 } 526 527 return $newRow ?: $row; 528 } 529 530 /** 531 * @param int $line 532 * 533 * @return array 534 */ 535 private function copyRow(array $rows, $line) 536 { 537 $row = $rows[$line]; 538 foreach ($row as $cellKey => $cellValue) { 539 $row[$cellKey] = ''; 540 if ($cellValue instanceof TableCell) { 541 $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); 542 } 543 } 544 545 return $row; 546 } 547 548 /** 549 * Gets number of columns by row. 550 * 551 * @return int 552 */ 553 private function getNumberOfColumns(array $row) 554 { 555 $columns = \count($row); 556 foreach ($row as $column) { 557 $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; 558 } 559 560 return $columns; 561 } 562 563 /** 564 * Gets list of columns for the given row. 565 * 566 * @return array 567 */ 568 private function getRowColumns(array $row) 569 { 570 $columns = range(0, $this->numberOfColumns - 1); 571 foreach ($row as $cellKey => $cell) { 572 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 573 // exclude grouped columns. 574 $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); 575 } 576 } 577 578 return $columns; 579 } 580 581 /** 582 * Calculates columns widths. 583 */ 584 private function calculateColumnsWidth(array $rows) 585 { 586 for ($column = 0; $column < $this->numberOfColumns; ++$column) { 587 $lengths = []; 588 foreach ($rows as $row) { 589 if ($row instanceof TableSeparator) { 590 continue; 591 } 592 593 foreach ($row as $i => $cell) { 594 if ($cell instanceof TableCell) { 595 $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); 596 $textLength = Helper::strlen($textContent); 597 if ($textLength > 0) { 598 $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); 599 foreach ($contentColumns as $position => $content) { 600 $row[$i + $position] = $content; 601 } 602 } 603 } 604 } 605 606 $lengths[] = $this->getCellWidth($row, $column); 607 } 608 609 $this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2; 610 } 611 } 612 613 /** 614 * Gets column width. 615 * 616 * @return int 617 */ 618 private function getColumnSeparatorWidth() 619 { 620 return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); 621 } 622 623 /** 624 * Gets cell width. 625 * 626 * @param int $column 627 * 628 * @return int 629 */ 630 private function getCellWidth(array $row, $column) 631 { 632 $cellWidth = 0; 633 634 if (isset($row[$column])) { 635 $cell = $row[$column]; 636 $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); 637 } 638 639 $columnWidth = isset($this->columnWidths[$column]) ? $this->columnWidths[$column] : 0; 640 641 return max($cellWidth, $columnWidth); 642 } 643 644 /** 645 * Called after rendering to cleanup cache data. 646 */ 647 private function cleanup() 648 { 649 $this->effectiveColumnWidths = []; 650 $this->numberOfColumns = null; 651 } 652 653 private static function initStyles() 654 { 655 $borderless = new TableStyle(); 656 $borderless 657 ->setHorizontalBorderChar('=') 658 ->setVerticalBorderChar(' ') 659 ->setCrossingChar(' ') 660 ; 661 662 $compact = new TableStyle(); 663 $compact 664 ->setHorizontalBorderChar('') 665 ->setVerticalBorderChar(' ') 666 ->setCrossingChar('') 667 ->setCellRowContentFormat('%s') 668 ; 669 670 $styleGuide = new TableStyle(); 671 $styleGuide 672 ->setHorizontalBorderChar('-') 673 ->setVerticalBorderChar(' ') 674 ->setCrossingChar(' ') 675 ->setCellHeaderFormat('%s') 676 ; 677 678 return [ 679 'default' => new TableStyle(), 680 'borderless' => $borderless, 681 'compact' => $compact, 682 'symfony-style-guide' => $styleGuide, 683 ]; 684 } 685 686 private function resolveStyle($name) 687 { 688 if ($name instanceof TableStyle) { 689 return $name; 690 } 691 692 if (isset(self::$styles[$name])) { 693 return self::$styles[$name]; 694 } 695 696 throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); 697 } 698 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Jun 23 12:25:44 2024 | Cross-referenced by PHPXref 0.7.1 |