[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/symfony/console/Helper/ -> Table.php (source)

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


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1