3 * @see https://github.com/zendframework/zend-text for the canonical source repository
4 * @copyright Copyright (c) 2005-2018 Zend Technologies USA Inc. (https://www.zend.com)
5 * @license https://github.com/zendframework/zend-text/blob/master/LICENSE.md New BSD License
8 namespace Zend\Text\Table
;
11 use Zend\ServiceManager\ServiceManager
;
12 use Zend\Stdlib\ArrayUtils
;
13 use Zend\Text\Table\Decorator\DecoratorInterface
as Decorator
;
16 * Zend\Text\Table\Table enables developers to create tables out of characters
21 * Auto separator settings
23 const AUTO_SEPARATE_NONE
= 0x0;
24 const AUTO_SEPARATE_HEADER
= 0x1;
25 const AUTO_SEPARATE_FOOTER
= 0x2;
26 const AUTO_SEPARATE_ALL
= 0x4;
29 * Decorator used for the table borders
33 protected $decorator = null;
36 * List of all column widths
40 protected $columnWidths = null;
50 * Auto separation mode
54 protected $autoSeparate = self
::AUTO_SEPARATE_ALL
;
61 protected $padding = 0;
64 * Default column aligns for rows created by appendRow(array $data)
68 protected $defaultColumnAligns = [];
71 * Plugin loader for decorators
73 * @var DecoratorManager
75 protected $decoratorManager = null;
78 * Charset which is used for input by default
82 protected static $inputCharset = 'utf-8';
85 * Charset which is used internally
89 protected static $outputCharset = 'utf-8';
92 * Option keys to skip when calling setOptions()
96 protected $skipOptions = [
103 * Create a basic table object
105 * @param array|Traversable $options Configuration options
106 * @throws Exception\UnexpectedValueException When no columns widths were set
108 public function __construct($options = null)
111 if ($options instanceof Traversable
) {
112 $options = ArrayUtils
::iteratorToArray($options);
114 if (is_array($options)) {
115 $this->setOptions($options);
118 // If no decorator was given, use default unicode decorator
119 if ($this->decorator
=== null) {
120 if (static::getOutputCharset() === 'utf-8') {
121 $this->setDecorator('unicode');
123 $this->setDecorator('ascii');
129 * Set options from array
131 * @param array $options Configuration for Table
134 public function setOptions(array $options)
136 foreach ($options as $key => $value) {
137 if (in_array(strtolower($key), $this->skipOptions
)) {
141 $method = 'set' . ucfirst($key);
142 if (method_exists($this, $method)) {
143 $this->$method($value);
153 * @param array $columnWidths Widths of all columns
154 * @throws Exception\InvalidArgumentException When no columns were supplied
155 * @throws Exception\InvalidArgumentException When a column has an invalid width
158 public function setColumnWidths(array $columnWidths)
160 if (count($columnWidths) === 0) {
161 throw new Exception\
InvalidArgumentException('You must supply at least one column');
164 foreach ($columnWidths as $columnNum => $columnWidth) {
165 if (is_int($columnWidth) === false or $columnWidth < 1) {
166 throw new Exception\
InvalidArgumentException('Column ' . $columnNum . ' has an invalid column width');
170 $this->columnWidths
= $columnWidths;
176 * Set auto separation mode
178 * @param int $autoSeparate Auto separation mode
181 public function setAutoSeparate($autoSeparate)
183 $this->autoSeparate
= (int) $autoSeparate;
190 * @param Decorator|string $decorator Decorator to use
193 public function setDecorator($decorator)
195 if (! $decorator instanceof Decorator
) {
196 $decorator = $this->getDecoratorManager()->get($decorator);
199 $this->decorator
= $decorator;
205 * Set the column padding
207 * @param int $padding The padding for the columns
210 public function setPadding($padding)
212 $this->padding
= max(0, (int) $padding);
217 * Get the plugin manager for decorators
219 * @return DecoratorManager
221 public function getDecoratorManager()
223 if ($this->decoratorManager
instanceof DecoratorManager
) {
224 return $this->decoratorManager
;
227 $this->setDecoratorManager(new DecoratorManager(new ServiceManager()));
228 return $this->decoratorManager
;
232 * Set the plugin manager instance for decorators
234 * @param DecoratorManager $decoratorManager
237 public function setDecoratorManager(DecoratorManager
$decoratorManager)
239 $this->decoratorManager
= $decoratorManager;
244 * Set default column align for rows created by appendRow(array $data)
246 * @param int $columnNum
247 * @param string $align
250 public function setDefaultColumnAlign($columnNum, $align)
252 $this->defaultColumnAligns
[$columnNum] = $align;
258 * Set the input charset for column contents
260 * @param string $charset
262 public static function setInputCharset($charset)
264 static::$inputCharset = strtolower($charset);
268 * Get the input charset for column contents
272 public static function getInputCharset()
274 return static::$inputCharset;
278 * Set the output charset for column contents
280 * @param string $charset
282 public static function setOutputCharset($charset)
284 static::$outputCharset = strtolower($charset);
288 * Get the output charset for column contents
292 public static function getOutputCharset()
294 return static::$outputCharset;
298 * Append a row to the table
300 * @param array|Row $row The row to append to the table
301 * @throws Exception\InvalidArgumentException When $row is neither an array nor Zend\Text\Table\Row
302 * @throws Exception\OverflowException When a row contains too many columns
305 public function appendRow($row)
307 if (! is_array($row) && ! ($row instanceof Row
)) {
308 throw new Exception\
InvalidArgumentException('$row must be an array or instance of Zend\Text\Table\Row');
311 if (is_array($row)) {
312 if (count($row) > count($this->columnWidths
)) {
313 throw new Exception\
OverflowException('Row contains too many columns');
319 foreach ($data as $columnData) {
320 if (isset($this->defaultColumnAligns
[$colNum])) {
321 $align = $this->defaultColumnAligns
[$colNum];
326 $row->appendColumn(new Column($columnData, $align));
331 $this->rows
[] = $row;
339 * @throws Exception\UnexpectedValueException When no rows were added to the table
342 public function render()
344 // There should be at least one row
345 if (count($this->rows
) === 0) {
346 throw new Exception\
UnexpectedValueException('No rows were added to the table yet');
349 // Initiate the result variable
352 // Count total columns
353 $totalNumColumns = count($this->columnWidths
);
355 // Check if we have a horizontal character defined
356 $hasHorizontal = $this->decorator
->getHorizontal() !== '';
358 // Now render all rows, starting from the first one
359 $numRows = count($this->rows
);
360 foreach ($this->rows
as $rowNum => $row) {
361 // Get all column widths
362 if (isset($columnWidths) === true) {
363 $lastColumnWidths = $columnWidths;
366 $renderedRow = $row->render($this->columnWidths
, $this->decorator
, $this->padding
);
367 $columnWidths = $row->getColumnWidths();
368 $numColumns = count($columnWidths);
370 // Check what we have to draw
371 if ($rowNum === 0 && $hasHorizontal) {
372 // If this is the first row, draw the table top
373 $result .= $this->decorator
->getTopLeft();
375 foreach ($columnWidths as $columnNum => $columnWidth) {
376 $result .= str_repeat($this->decorator
->getHorizontal(), $columnWidth);
378 if (($columnNum +
1) === $numColumns) {
379 $result .= $this->decorator
->getTopRight();
381 $result .= $this->decorator
->getHorizontalDown();
387 // Else check if we have to draw the row separator
388 if (! $hasHorizontal) {
389 $drawSeparator = false; // there is no horizontal character;
390 } elseif ($this->autoSeparate
& self
::AUTO_SEPARATE_ALL
) {
391 $drawSeparator = true;
392 } elseif ($rowNum === 1 && $this->autoSeparate
& self
::AUTO_SEPARATE_HEADER
) {
393 $drawSeparator = true;
394 } elseif ($rowNum === ($numRows - 1) && $this->autoSeparate
& self
::AUTO_SEPARATE_FOOTER
) {
395 $drawSeparator = true;
397 $drawSeparator = false;
400 if ($drawSeparator) {
401 $result .= $this->decorator
->getVerticalRight();
403 $currentUpperColumn = 0;
404 $currentLowerColumn = 0;
405 $currentUpperWidth = 0;
406 $currentLowerWidth = 0;
408 // Add horizontal lines
409 // Loop through all column widths
410 foreach ($this->columnWidths
as $columnNum => $columnWidth) {
411 // Add the horizontal line
412 $result .= str_repeat($this->decorator
->getHorizontal(), $columnWidth);
414 // If this is the last line, break out
415 if (($columnNum +
1) === $totalNumColumns) {
419 // Else check, which connector style has to be used
421 $currentUpperWidth +
= $columnWidth;
422 $currentLowerWidth +
= $columnWidth;
424 if ($lastColumnWidths[$currentUpperColumn] === $currentUpperWidth) {
426 $currentUpperColumn +
= 1;
427 $currentUpperWidth = 0;
429 $currentUpperWidth +
= 1;
432 if ($columnWidths[$currentLowerColumn] === $currentLowerWidth) {
434 $currentLowerColumn +
= 1;
435 $currentLowerWidth = 0;
437 $currentLowerWidth +
= 1;
440 switch ($connector) {
442 $result .= $this->decorator
->getHorizontal();
446 $result .= $this->decorator
->getHorizontalUp();
450 $result .= $this->decorator
->getHorizontalDown();
454 $result .= $this->decorator
->getCross();
458 // This can never happen, but the CS tells I have to have it ...
463 $result .= $this->decorator
->getVerticalLeft() . "\n";
467 // Add the rendered row to the result
468 $result .= $renderedRow;
470 // If this is the last row, draw the table bottom
471 if (($rowNum +
1) === $numRows && $hasHorizontal) {
472 $result .= $this->decorator
->getBottomLeft();
474 foreach ($columnWidths as $columnNum => $columnWidth) {
475 $result .= str_repeat($this->decorator
->getHorizontal(), $columnWidth);
477 if (($columnNum +
1) === $numColumns) {
478 $result .= $this->decorator
->getBottomRight();
480 $result .= $this->decorator
->getHorizontalUp();
492 * Magic method which returns the rendered table
496 public function __toString()
499 return $this->render();
500 } catch (\Exception
$e) {
501 trigger_error($e->getMessage(), E_USER_ERROR
);