[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 3 /* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <[email protected]> 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\Exception\RuntimeException; 16 use Symfony\Component\Console\Formatter\OutputFormatter; 17 use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface; 18 use Symfony\Component\Console\Output\ConsoleSectionOutput; 19 use Symfony\Component\Console\Output\OutputInterface; 20 21 /** 22 * Provides helpers to display a table. 23 * 24 * @author Fabien Potencier <[email protected]> 25 * @author Саша Стаменковић <[email protected]> 26 * @author Abdellatif Ait boudad <[email protected]> 27 * @author Max Grigorian <[email protected]> 28 * @author Dany Maillard <[email protected]> 29 */ 30 class Table 31 { 32 private const SEPARATOR_TOP = 0; 33 private const SEPARATOR_TOP_BOTTOM = 1; 34 private const SEPARATOR_MID = 2; 35 private const SEPARATOR_BOTTOM = 3; 36 private const BORDER_OUTSIDE = 0; 37 private const BORDER_INSIDE = 1; 38 39 private $headerTitle; 40 private $footerTitle; 41 42 /** 43 * Table headers. 44 */ 45 private $headers = []; 46 47 /** 48 * Table rows. 49 */ 50 private $rows = []; 51 private $horizontal = false; 52 53 /** 54 * Column widths cache. 55 */ 56 private $effectiveColumnWidths = []; 57 58 /** 59 * Number of columns cache. 60 * 61 * @var int 62 */ 63 private $numberOfColumns; 64 65 /** 66 * @var OutputInterface 67 */ 68 private $output; 69 70 /** 71 * @var TableStyle 72 */ 73 private $style; 74 75 /** 76 * @var array 77 */ 78 private $columnStyles = []; 79 80 /** 81 * User set column widths. 82 * 83 * @var array 84 */ 85 private $columnWidths = []; 86 private $columnMaxWidths = []; 87 88 /** 89 * @var array<string, TableStyle>|null 90 */ 91 private static $styles; 92 93 private $rendered = false; 94 95 public function __construct(OutputInterface $output) 96 { 97 $this->output = $output; 98 99 if (!self::$styles) { 100 self::$styles = self::initStyles(); 101 } 102 103 $this->setStyle('default'); 104 } 105 106 /** 107 * Sets a style definition. 108 */ 109 public static function setStyleDefinition(string $name, TableStyle $style) 110 { 111 if (!self::$styles) { 112 self::$styles = self::initStyles(); 113 } 114 115 self::$styles[$name] = $style; 116 } 117 118 /** 119 * Gets a style definition by name. 120 * 121 * @return TableStyle 122 */ 123 public static function getStyleDefinition(string $name) 124 { 125 if (!self::$styles) { 126 self::$styles = self::initStyles(); 127 } 128 129 if (isset(self::$styles[$name])) { 130 return self::$styles[$name]; 131 } 132 133 throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); 134 } 135 136 /** 137 * Sets table style. 138 * 139 * @param TableStyle|string $name The style name or a TableStyle instance 140 * 141 * @return $this 142 */ 143 public function setStyle($name) 144 { 145 $this->style = $this->resolveStyle($name); 146 147 return $this; 148 } 149 150 /** 151 * Gets the current table style. 152 * 153 * @return TableStyle 154 */ 155 public function getStyle() 156 { 157 return $this->style; 158 } 159 160 /** 161 * Sets table column style. 162 * 163 * @param TableStyle|string $name The style name or a TableStyle instance 164 * 165 * @return $this 166 */ 167 public function setColumnStyle(int $columnIndex, $name) 168 { 169 $this->columnStyles[$columnIndex] = $this->resolveStyle($name); 170 171 return $this; 172 } 173 174 /** 175 * Gets the current style for a column. 176 * 177 * If style was not set, it returns the global table style. 178 * 179 * @return TableStyle 180 */ 181 public function getColumnStyle(int $columnIndex) 182 { 183 return $this->columnStyles[$columnIndex] ?? $this->getStyle(); 184 } 185 186 /** 187 * Sets the minimum width of a column. 188 * 189 * @return $this 190 */ 191 public function setColumnWidth(int $columnIndex, int $width) 192 { 193 $this->columnWidths[$columnIndex] = $width; 194 195 return $this; 196 } 197 198 /** 199 * Sets the minimum width of all columns. 200 * 201 * @return $this 202 */ 203 public function setColumnWidths(array $widths) 204 { 205 $this->columnWidths = []; 206 foreach ($widths as $index => $width) { 207 $this->setColumnWidth($index, $width); 208 } 209 210 return $this; 211 } 212 213 /** 214 * Sets the maximum width of a column. 215 * 216 * Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while 217 * formatted strings are preserved. 218 * 219 * @return $this 220 */ 221 public function setColumnMaxWidth(int $columnIndex, int $width): self 222 { 223 if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) { 224 throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter()))); 225 } 226 227 $this->columnMaxWidths[$columnIndex] = $width; 228 229 return $this; 230 } 231 232 /** 233 * @return $this 234 */ 235 public function setHeaders(array $headers) 236 { 237 $headers = array_values($headers); 238 if (!empty($headers) && !\is_array($headers[0])) { 239 $headers = [$headers]; 240 } 241 242 $this->headers = $headers; 243 244 return $this; 245 } 246 247 public function setRows(array $rows) 248 { 249 $this->rows = []; 250 251 return $this->addRows($rows); 252 } 253 254 /** 255 * @return $this 256 */ 257 public function addRows(array $rows) 258 { 259 foreach ($rows as $row) { 260 $this->addRow($row); 261 } 262 263 return $this; 264 } 265 266 /** 267 * @return $this 268 */ 269 public function addRow($row) 270 { 271 if ($row instanceof TableSeparator) { 272 $this->rows[] = $row; 273 274 return $this; 275 } 276 277 if (!\is_array($row)) { 278 throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); 279 } 280 281 $this->rows[] = array_values($row); 282 283 return $this; 284 } 285 286 /** 287 * Adds a row to the table, and re-renders the table. 288 * 289 * @return $this 290 */ 291 public function appendRow($row): self 292 { 293 if (!$this->output instanceof ConsoleSectionOutput) { 294 throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__)); 295 } 296 297 if ($this->rendered) { 298 $this->output->clear($this->calculateRowCount()); 299 } 300 301 $this->addRow($row); 302 $this->render(); 303 304 return $this; 305 } 306 307 /** 308 * @return $this 309 */ 310 public function setRow($column, array $row) 311 { 312 $this->rows[$column] = $row; 313 314 return $this; 315 } 316 317 /** 318 * @return $this 319 */ 320 public function setHeaderTitle(?string $title): self 321 { 322 $this->headerTitle = $title; 323 324 return $this; 325 } 326 327 /** 328 * @return $this 329 */ 330 public function setFooterTitle(?string $title): self 331 { 332 $this->footerTitle = $title; 333 334 return $this; 335 } 336 337 /** 338 * @return $this 339 */ 340 public function setHorizontal(bool $horizontal = true): self 341 { 342 $this->horizontal = $horizontal; 343 344 return $this; 345 } 346 347 /** 348 * Renders table to output. 349 * 350 * Example: 351 * 352 * +---------------+-----------------------+------------------+ 353 * | ISBN | Title | Author | 354 * +---------------+-----------------------+------------------+ 355 * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 356 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 357 * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | 358 * +---------------+-----------------------+------------------+ 359 */ 360 public function render() 361 { 362 $divider = new TableSeparator(); 363 if ($this->horizontal) { 364 $rows = []; 365 foreach ($this->headers[0] ?? [] as $i => $header) { 366 $rows[$i] = [$header]; 367 foreach ($this->rows as $row) { 368 if ($row instanceof TableSeparator) { 369 continue; 370 } 371 if (isset($row[$i])) { 372 $rows[$i][] = $row[$i]; 373 } elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) { 374 // Noop, there is a "title" 375 } else { 376 $rows[$i][] = null; 377 } 378 } 379 } 380 } else { 381 $rows = array_merge($this->headers, [$divider], $this->rows); 382 } 383 384 $this->calculateNumberOfColumns($rows); 385 386 $rows = $this->buildTableRows($rows); 387 $this->calculateColumnsWidth($rows); 388 389 $isHeader = !$this->horizontal; 390 $isFirstRow = $this->horizontal; 391 $hasTitle = (bool) $this->headerTitle; 392 foreach ($rows as $row) { 393 if ($divider === $row) { 394 $isHeader = false; 395 $isFirstRow = true; 396 397 continue; 398 } 399 if ($row instanceof TableSeparator) { 400 $this->renderRowSeparator(); 401 402 continue; 403 } 404 if (!$row) { 405 continue; 406 } 407 408 if ($isHeader || $isFirstRow) { 409 $this->renderRowSeparator( 410 $isHeader ? self::SEPARATOR_TOP : self::SEPARATOR_TOP_BOTTOM, 411 $hasTitle ? $this->headerTitle : null, 412 $hasTitle ? $this->style->getHeaderTitleFormat() : null 413 ); 414 $isFirstRow = false; 415 $hasTitle = false; 416 } 417 if ($this->horizontal) { 418 $this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat()); 419 } else { 420 $this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat()); 421 } 422 } 423 $this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat()); 424 425 $this->cleanup(); 426 $this->rendered = true; 427 } 428 429 /** 430 * Renders horizontal header separator. 431 * 432 * Example: 433 * 434 * +-----+-----------+-------+ 435 */ 436 private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null) 437 { 438 if (0 === $count = $this->numberOfColumns) { 439 return; 440 } 441 442 $borders = $this->style->getBorderChars(); 443 if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) { 444 return; 445 } 446 447 $crossings = $this->style->getCrossingChars(); 448 if (self::SEPARATOR_MID === $type) { 449 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]]; 450 } elseif (self::SEPARATOR_TOP === $type) { 451 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]]; 452 } elseif (self::SEPARATOR_TOP_BOTTOM === $type) { 453 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]]; 454 } else { 455 [$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]]; 456 } 457 458 $markup = $leftChar; 459 for ($column = 0; $column < $count; ++$column) { 460 $markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]); 461 $markup .= $column === $count - 1 ? $rightChar : $midChar; 462 } 463 464 if (null !== $title) { 465 $titleLength = Helper::width(Helper::removeDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title))); 466 $markupLength = Helper::width($markup); 467 if ($titleLength > $limit = $markupLength - 4) { 468 $titleLength = $limit; 469 $formatLength = Helper::width(Helper::removeDecoration($formatter, sprintf($titleFormat, ''))); 470 $formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...'); 471 } 472 473 $titleStart = intdiv($markupLength - $titleLength, 2); 474 if (false === mb_detect_encoding($markup, null, true)) { 475 $markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength); 476 } else { 477 $markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength); 478 } 479 } 480 481 $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); 482 } 483 484 /** 485 * Renders vertical column separator. 486 */ 487 private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string 488 { 489 $borders = $this->style->getBorderChars(); 490 491 return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]); 492 } 493 494 /** 495 * Renders table row. 496 * 497 * Example: 498 * 499 * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 500 */ 501 private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null) 502 { 503 $rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE); 504 $columns = $this->getRowColumns($row); 505 $last = \count($columns) - 1; 506 foreach ($columns as $i => $column) { 507 if ($firstCellFormat && 0 === $i) { 508 $rowContent .= $this->renderCell($row, $column, $firstCellFormat); 509 } else { 510 $rowContent .= $this->renderCell($row, $column, $cellFormat); 511 } 512 $rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE); 513 } 514 $this->output->writeln($rowContent); 515 } 516 517 /** 518 * Renders table cell with padding. 519 */ 520 private function renderCell(array $row, int $column, string $cellFormat): string 521 { 522 $cell = $row[$column] ?? ''; 523 $width = $this->effectiveColumnWidths[$column]; 524 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 525 // add the width of the following columns(numbers of colspan). 526 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { 527 $width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn]; 528 } 529 } 530 531 // str_pad won't work properly with multi-byte strings, we need to fix the padding 532 if (false !== $encoding = mb_detect_encoding($cell, null, true)) { 533 $width += \strlen($cell) - mb_strwidth($cell, $encoding); 534 } 535 536 $style = $this->getColumnStyle($column); 537 538 if ($cell instanceof TableSeparator) { 539 return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width)); 540 } 541 542 $width += Helper::length($cell) - Helper::length(Helper::removeDecoration($this->output->getFormatter(), $cell)); 543 $content = sprintf($style->getCellRowContentFormat(), $cell); 544 545 $padType = $style->getPadType(); 546 if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) { 547 $isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell); 548 if ($isNotStyledByTag) { 549 $cellFormat = $cell->getStyle()->getCellFormat(); 550 if (!\is_string($cellFormat)) { 551 $tag = http_build_query($cell->getStyle()->getTagOptions(), '', ';'); 552 $cellFormat = '<'.$tag.'>%s</>'; 553 } 554 555 if (strstr($content, '</>')) { 556 $content = str_replace('</>', '', $content); 557 $width -= 3; 558 } 559 if (strstr($content, '<fg=default;bg=default>')) { 560 $content = str_replace('<fg=default;bg=default>', '', $content); 561 $width -= \strlen('<fg=default;bg=default>'); 562 } 563 } 564 565 $padType = $cell->getStyle()->getPadByAlign(); 566 } 567 568 return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType)); 569 } 570 571 /** 572 * Calculate number of columns for this table. 573 */ 574 private function calculateNumberOfColumns(array $rows) 575 { 576 $columns = [0]; 577 foreach ($rows as $row) { 578 if ($row instanceof TableSeparator) { 579 continue; 580 } 581 582 $columns[] = $this->getNumberOfColumns($row); 583 } 584 585 $this->numberOfColumns = max($columns); 586 } 587 588 private function buildTableRows(array $rows): TableRows 589 { 590 /** @var WrappableOutputFormatterInterface $formatter */ 591 $formatter = $this->output->getFormatter(); 592 $unmergedRows = []; 593 for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { 594 $rows = $this->fillNextRows($rows, $rowKey); 595 596 // Remove any new line breaks and replace it with a new line 597 foreach ($rows[$rowKey] as $column => $cell) { 598 $colspan = $cell instanceof TableCell ? $cell->getColspan() : 1; 599 600 if (isset($this->columnMaxWidths[$column]) && Helper::width(Helper::removeDecoration($formatter, $cell)) > $this->columnMaxWidths[$column]) { 601 $cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan); 602 } 603 if (!strstr($cell ?? '', "\n")) { 604 continue; 605 } 606 $escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell))); 607 $cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped; 608 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); 609 foreach ($lines as $lineKey => $line) { 610 if ($colspan > 1) { 611 $line = new TableCell($line, ['colspan' => $colspan]); 612 } 613 if (0 === $lineKey) { 614 $rows[$rowKey][$column] = $line; 615 } else { 616 if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) { 617 $unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey); 618 } 619 $unmergedRows[$rowKey][$lineKey][$column] = $line; 620 } 621 } 622 } 623 } 624 625 return new TableRows(function () use ($rows, $unmergedRows): \Traversable { 626 foreach ($rows as $rowKey => $row) { 627 yield $row instanceof TableSeparator ? $row : $this->fillCells($row); 628 629 if (isset($unmergedRows[$rowKey])) { 630 foreach ($unmergedRows[$rowKey] as $row) { 631 yield $row instanceof TableSeparator ? $row : $this->fillCells($row); 632 } 633 } 634 } 635 }); 636 } 637 638 private function calculateRowCount(): int 639 { 640 $numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows)))); 641 642 if ($this->headers) { 643 ++$numberOfRows; // Add row for header separator 644 } 645 646 if (\count($this->rows) > 0) { 647 ++$numberOfRows; // Add row for footer separator 648 } 649 650 return $numberOfRows; 651 } 652 653 /** 654 * fill rows that contains rowspan > 1. 655 * 656 * @throws InvalidArgumentException 657 */ 658 private function fillNextRows(array $rows, int $line): array 659 { 660 $unmergedRows = []; 661 foreach ($rows[$line] as $column => $cell) { 662 if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) { 663 throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell))); 664 } 665 if ($cell instanceof TableCell && $cell->getRowspan() > 1) { 666 $nbLines = $cell->getRowspan() - 1; 667 $lines = [$cell]; 668 if (strstr($cell, "\n")) { 669 $lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell)); 670 $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; 671 672 $rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); 673 unset($lines[0]); 674 } 675 676 // create a two dimensional array (rowspan x colspan) 677 $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows); 678 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { 679 $value = $lines[$unmergedRowKey - $line] ?? ''; 680 $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]); 681 if ($nbLines === $unmergedRowKey - $line) { 682 break; 683 } 684 } 685 } 686 } 687 688 foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { 689 // we need to know if $unmergedRow will be merged or inserted into $rows 690 if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { 691 foreach ($unmergedRow as $cellKey => $cell) { 692 // insert cell into row at cellKey position 693 array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]); 694 } 695 } else { 696 $row = $this->copyRow($rows, $unmergedRowKey - 1); 697 foreach ($unmergedRow as $column => $cell) { 698 if (!empty($cell)) { 699 $row[$column] = $unmergedRow[$column]; 700 } 701 } 702 array_splice($rows, $unmergedRowKey, 0, [$row]); 703 } 704 } 705 706 return $rows; 707 } 708 709 /** 710 * fill cells for a row that contains colspan > 1. 711 */ 712 private function fillCells(iterable $row) 713 { 714 $newRow = []; 715 716 foreach ($row as $column => $cell) { 717 $newRow[] = $cell; 718 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 719 foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { 720 // insert empty value at column position 721 $newRow[] = ''; 722 } 723 } 724 } 725 726 return $newRow ?: $row; 727 } 728 729 private function copyRow(array $rows, int $line): array 730 { 731 $row = $rows[$line]; 732 foreach ($row as $cellKey => $cellValue) { 733 $row[$cellKey] = ''; 734 if ($cellValue instanceof TableCell) { 735 $row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]); 736 } 737 } 738 739 return $row; 740 } 741 742 /** 743 * Gets number of columns by row. 744 */ 745 private function getNumberOfColumns(array $row): int 746 { 747 $columns = \count($row); 748 foreach ($row as $column) { 749 $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; 750 } 751 752 return $columns; 753 } 754 755 /** 756 * Gets list of columns for the given row. 757 */ 758 private function getRowColumns(array $row): array 759 { 760 $columns = range(0, $this->numberOfColumns - 1); 761 foreach ($row as $cellKey => $cell) { 762 if ($cell instanceof TableCell && $cell->getColspan() > 1) { 763 // exclude grouped columns. 764 $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); 765 } 766 } 767 768 return $columns; 769 } 770 771 /** 772 * Calculates columns widths. 773 */ 774 private function calculateColumnsWidth(iterable $rows) 775 { 776 for ($column = 0; $column < $this->numberOfColumns; ++$column) { 777 $lengths = []; 778 foreach ($rows as $row) { 779 if ($row instanceof TableSeparator) { 780 continue; 781 } 782 783 foreach ($row as $i => $cell) { 784 if ($cell instanceof TableCell) { 785 $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); 786 $textLength = Helper::width($textContent); 787 if ($textLength > 0) { 788 $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); 789 foreach ($contentColumns as $position => $content) { 790 $row[$i + $position] = $content; 791 } 792 } 793 } 794 } 795 796 $lengths[] = $this->getCellWidth($row, $column); 797 } 798 799 $this->effectiveColumnWidths[$column] = max($lengths) + Helper::width($this->style->getCellRowContentFormat()) - 2; 800 } 801 } 802 803 private function getColumnSeparatorWidth(): int 804 { 805 return Helper::width(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3])); 806 } 807 808 private function getCellWidth(array $row, int $column): int 809 { 810 $cellWidth = 0; 811 812 if (isset($row[$column])) { 813 $cell = $row[$column]; 814 $cellWidth = Helper::width(Helper::removeDecoration($this->output->getFormatter(), $cell)); 815 } 816 817 $columnWidth = $this->columnWidths[$column] ?? 0; 818 $cellWidth = max($cellWidth, $columnWidth); 819 820 return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth; 821 } 822 823 /** 824 * Called after rendering to cleanup cache data. 825 */ 826 private function cleanup() 827 { 828 $this->effectiveColumnWidths = []; 829 $this->numberOfColumns = null; 830 } 831 832 /** 833 * @return array<string, TableStyle> 834 */ 835 private static function initStyles(): array 836 { 837 $borderless = new TableStyle(); 838 $borderless 839 ->setHorizontalBorderChars('=') 840 ->setVerticalBorderChars(' ') 841 ->setDefaultCrossingChar(' ') 842 ; 843 844 $compact = new TableStyle(); 845 $compact 846 ->setHorizontalBorderChars('') 847 ->setVerticalBorderChars('') 848 ->setDefaultCrossingChar('') 849 ->setCellRowContentFormat('%s ') 850 ; 851 852 $styleGuide = new TableStyle(); 853 $styleGuide 854 ->setHorizontalBorderChars('-') 855 ->setVerticalBorderChars(' ') 856 ->setDefaultCrossingChar(' ') 857 ->setCellHeaderFormat('%s') 858 ; 859 860 $box = (new TableStyle()) 861 ->setHorizontalBorderChars('─') 862 ->setVerticalBorderChars('│') 863 ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├') 864 ; 865 866 $boxDouble = (new TableStyle()) 867 ->setHorizontalBorderChars('═', '─') 868 ->setVerticalBorderChars('║', '│') 869 ->setCrossingChars('┼', '╔', '╤', '╗', '╢', '╝', '╧', '╚', '╟', '╠', '╪', '╣') 870 ; 871 872 return [ 873 'default' => new TableStyle(), 874 'borderless' => $borderless, 875 'compact' => $compact, 876 'symfony-style-guide' => $styleGuide, 877 'box' => $box, 878 'box-double' => $boxDouble, 879 ]; 880 } 881 882 private function resolveStyle($name): TableStyle 883 { 884 if ($name instanceof TableStyle) { 885 return $name; 886 } 887 888 if (isset(self::$styles[$name])) { 889 return self::$styles[$name]; 890 } 891 892 throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); 893 } 894 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |