Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Table.php

Zuletzt modifiziert: 02.04.2025, 15:03 - Dateigröße: 19.43 KiB


001  <?php
002   
003  /*
004   * This file is part of the Symfony package.
005   *
006   * (c) Fabien Potencier <fabien@symfony.com>
007   *
008   * For the full copyright and license information, please view the LICENSE
009   * file that was distributed with this source code.
010   */
011   
012  namespace Symfony\Component\Console\Helper;
013   
014  use Symfony\Component\Console\Exception\InvalidArgumentException;
015  use Symfony\Component\Console\Output\OutputInterface;
016   
017  /**
018   * Provides helpers to display a table.
019   *
020   * @author Fabien Potencier <fabien@symfony.com>
021   * @author Саша Стаменковић <umpirsky@gmail.com>
022   * @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
023   * @author Max Grigorian <maxakawizard@gmail.com>
024   */
025  class Table
026  {
027      /**
028       * Table headers.
029       */
030      private $headers = [];
031   
032      /**
033       * Table rows.
034       */
035      private $rows = [];
036   
037      /**
038       * Column widths cache.
039       */
040      private $effectiveColumnWidths = [];
041   
042      /**
043       * Number of columns cache.
044       *
045       * @var int
046       */
047      private $numberOfColumns;
048   
049      /**
050       * @var OutputInterface
051       */
052      private $output;
053   
054      /**
055       * @var TableStyle
056       */
057      private $style;
058   
059      /**
060       * @var array
061       */
062      private $columnStyles = [];
063   
064      /**
065       * User set column widths.
066       *
067       * @var array
068       */
069      private $columnWidths = [];
070   
071      private static $styles;
072   
073      public function __construct(OutputInterface $output)
074      {
075          $this->output = $output;
076   
077          if (!self::$styles) {
078              self::$styles = self::initStyles();
079          }
080   
081          $this->setStyle('default');
082      }
083   
084      /**
085       * Sets a style definition.
086       *
087       * @param string     $name  The style name
088       * @param TableStyle $style A TableStyle instance
089       */
090      public static function setStyleDefinition($name, TableStyle $style)
091      {
092          if (!self::$styles) {
093              self::$styles = self::initStyles();
094          }
095   
096          self::$styles[$name] = $style;
097      }
098   
099      /**
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  }
699