Chris@0: Chris@0: * @author Jan Schneider Chris@0: * @copyright 2002-2005 Richard Heyes Chris@0: * @copyright 2006-2008 Jan Schneider Chris@0: * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) Chris@0: * @version CVS: $Id$ Chris@0: * @link http://pear.php.net/package/Console_Table Chris@0: */ Chris@0: Chris@0: define('CONSOLE_TABLE_HORIZONTAL_RULE', 1); Chris@0: define('CONSOLE_TABLE_ALIGN_LEFT', -1); Chris@0: define('CONSOLE_TABLE_ALIGN_CENTER', 0); Chris@0: define('CONSOLE_TABLE_ALIGN_RIGHT', 1); Chris@0: define('CONSOLE_TABLE_BORDER_ASCII', -1); Chris@0: Chris@0: /** Chris@0: * The main class. Chris@0: * Chris@0: * @category Console Chris@0: * @package Console_Table Chris@0: * @author Jan Schneider Chris@0: * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) Chris@0: * @link http://pear.php.net/package/Console_Table Chris@0: */ Chris@0: class Console_Table Chris@0: { Chris@0: /** Chris@0: * The table headers. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_headers = array(); Chris@0: Chris@0: /** Chris@0: * The data of the table. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_data = array(); Chris@0: Chris@0: /** Chris@0: * The maximum number of columns in a row. Chris@0: * Chris@0: * @var integer Chris@0: */ Chris@0: var $_max_cols = 0; Chris@0: Chris@0: /** Chris@0: * The maximum number of rows in the table. Chris@0: * Chris@0: * @var integer Chris@0: */ Chris@0: var $_max_rows = 0; Chris@0: Chris@0: /** Chris@0: * Lengths of the columns, calculated when rows are added to the table. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_cell_lengths = array(); Chris@0: Chris@0: /** Chris@0: * Heights of the rows. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_row_heights = array(); Chris@0: Chris@0: /** Chris@0: * How many spaces to use to pad the table. Chris@0: * Chris@0: * @var integer Chris@0: */ Chris@0: var $_padding = 1; Chris@0: Chris@0: /** Chris@0: * Column filters. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_filters = array(); Chris@0: Chris@0: /** Chris@0: * Columns to calculate totals for. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_calculateTotals; Chris@0: Chris@0: /** Chris@0: * Alignment of the columns. Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_col_align = array(); Chris@0: Chris@0: /** Chris@0: * Default alignment of columns. Chris@0: * Chris@0: * @var integer Chris@0: */ Chris@0: var $_defaultAlign; Chris@0: Chris@0: /** Chris@0: * Character set of the data. Chris@0: * Chris@0: * @var string Chris@0: */ Chris@0: var $_charset = 'utf-8'; Chris@0: Chris@0: /** Chris@0: * Border characters. Chris@0: * Allowed keys: Chris@0: * - intersection - intersection ("+") Chris@0: * - horizontal - horizontal rule character ("-") Chris@0: * - vertical - vertical rule character ("|") Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_border = array( Chris@0: 'intersection' => '+', Chris@0: 'horizontal' => '-', Chris@0: 'vertical' => '|', Chris@0: ); Chris@0: Chris@0: /** Chris@0: * If borders are shown or not Chris@0: * Allowed keys: top, right, bottom, left, inner: true and false Chris@0: * Chris@0: * @var array Chris@0: */ Chris@0: var $_borderVisibility = array( Chris@0: 'top' => true, Chris@0: 'right' => true, Chris@0: 'bottom' => true, Chris@0: 'left' => true, Chris@0: 'inner' => true Chris@0: ); Chris@0: Chris@0: /** Chris@0: * Whether the data has ANSI colors. Chris@0: * Chris@0: * @var Console_Color2 Chris@0: */ Chris@0: var $_ansiColor = false; Chris@0: Chris@0: /** Chris@0: * Constructor. Chris@0: * Chris@0: * @param integer $align Default alignment. One of Chris@0: * CONSOLE_TABLE_ALIGN_LEFT, Chris@0: * CONSOLE_TABLE_ALIGN_CENTER or Chris@0: * CONSOLE_TABLE_ALIGN_RIGHT. Chris@0: * @param string $border The character used for table borders or Chris@0: * CONSOLE_TABLE_BORDER_ASCII. Chris@0: * @param integer $padding How many spaces to use to pad the table. Chris@0: * @param string $charset A charset supported by the mbstring PHP Chris@0: * extension. Chris@0: * @param boolean $color Whether the data contains ansi color codes. Chris@0: */ Chris@0: function __construct($align = CONSOLE_TABLE_ALIGN_LEFT, Chris@0: $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1, Chris@0: $charset = null, $color = false) Chris@0: { Chris@0: $this->_defaultAlign = $align; Chris@0: $this->setBorder($border); Chris@0: $this->_padding = $padding; Chris@0: if ($color) { Chris@0: if (!class_exists('Console_Color2')) { Chris@0: include_once 'Console/Color2.php'; Chris@0: } Chris@0: $this->_ansiColor = new Console_Color2(); Chris@0: } Chris@0: if (!empty($charset)) { Chris@0: $this->setCharset($charset); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Converts an array to a table. Chris@0: * Chris@0: * @param array $headers Headers for the table. Chris@0: * @param array $data A two dimensional array with the table Chris@0: * data. Chris@0: * @param boolean $returnObject Whether to return the Console_Table object Chris@0: * instead of the rendered table. Chris@0: * Chris@0: * @static Chris@0: * Chris@0: * @return Console_Table|string A Console_Table object or the generated Chris@0: * table. Chris@0: */ Chris@0: function fromArray($headers, $data, $returnObject = false) Chris@0: { Chris@0: if (!is_array($headers) || !is_array($data)) { Chris@0: return false; Chris@0: } Chris@0: Chris@0: $table = new Console_Table(); Chris@0: $table->setHeaders($headers); Chris@0: Chris@0: foreach ($data as $row) { Chris@0: $table->addRow($row); Chris@0: } Chris@0: Chris@0: return $returnObject ? $table : $table->getTable(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a filter to a column. Chris@0: * Chris@0: * Filters are standard PHP callbacks which are run on the data before Chris@0: * table generation is performed. Filters are applied in the order they Chris@0: * are added. The callback function must accept a single argument, which Chris@0: * is a single table cell. Chris@0: * Chris@0: * @param integer $col Column to apply filter to. Chris@0: * @param mixed &$callback PHP callback to apply. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function addFilter($col, &$callback) Chris@0: { Chris@0: $this->_filters[] = array($col, &$callback); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the charset of the provided table data. Chris@0: * Chris@0: * @param string $charset A charset supported by the mbstring PHP Chris@0: * extension. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function setCharset($charset) Chris@0: { Chris@0: $locale = setlocale(LC_CTYPE, 0); Chris@0: setlocale(LC_CTYPE, 'en_US'); Chris@0: $this->_charset = strtolower($charset); Chris@0: setlocale(LC_CTYPE, $locale); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set the table border settings Chris@0: * Chris@0: * Border definition modes: Chris@0: * - CONSOLE_TABLE_BORDER_ASCII: Default border with +, - and | Chris@0: * - array with keys "intersection", "horizontal" and "vertical" Chris@0: * - single character string that sets all three of the array keys Chris@0: * Chris@0: * @param mixed $border Border definition Chris@0: * Chris@0: * @return void Chris@0: * @see $_border Chris@0: */ Chris@0: function setBorder($border) Chris@0: { Chris@0: if ($border === CONSOLE_TABLE_BORDER_ASCII) { Chris@0: $intersection = '+'; Chris@0: $horizontal = '-'; Chris@0: $vertical = '|'; Chris@0: } else if (is_string($border)) { Chris@0: $intersection = $horizontal = $vertical = $border; Chris@0: } else if ($border == '') { Chris@0: $intersection = $horizontal = $vertical = ''; Chris@0: } else { Chris@0: extract($border); Chris@0: } Chris@0: Chris@0: $this->_border = array( Chris@0: 'intersection' => $intersection, Chris@0: 'horizontal' => $horizontal, Chris@0: 'vertical' => $vertical, Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Set which borders shall be shown. Chris@0: * Chris@0: * @param array $visibility Visibility settings. Chris@0: * Allowed keys: left, right, top, bottom, inner Chris@0: * Chris@0: * @return void Chris@0: * @see $_borderVisibility Chris@0: */ Chris@0: function setBorderVisibility($visibility) Chris@0: { Chris@0: $this->_borderVisibility = array_merge( Chris@0: $this->_borderVisibility, Chris@0: array_intersect_key( Chris@0: $visibility, Chris@0: $this->_borderVisibility Chris@0: ) Chris@0: ); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the alignment for the columns. Chris@0: * Chris@0: * @param integer $col_id The column number. Chris@0: * @param integer $align Alignment to set for this column. One of Chris@0: * CONSOLE_TABLE_ALIGN_LEFT Chris@0: * CONSOLE_TABLE_ALIGN_CENTER Chris@0: * CONSOLE_TABLE_ALIGN_RIGHT. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT) Chris@0: { Chris@0: switch ($align) { Chris@0: case CONSOLE_TABLE_ALIGN_CENTER: Chris@0: $pad = STR_PAD_BOTH; Chris@0: break; Chris@0: case CONSOLE_TABLE_ALIGN_RIGHT: Chris@0: $pad = STR_PAD_LEFT; Chris@0: break; Chris@0: default: Chris@0: $pad = STR_PAD_RIGHT; Chris@0: break; Chris@0: } Chris@0: $this->_col_align[$col_id] = $pad; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Specifies which columns are to have totals calculated for them and Chris@0: * added as a new row at the bottom. Chris@0: * Chris@0: * @param array $cols Array of column numbers (starting with 0). Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function calculateTotalsFor($cols) Chris@0: { Chris@0: $this->_calculateTotals = $cols; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Sets the headers for the columns. Chris@0: * Chris@0: * @param array $headers The column headers. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function setHeaders($headers) Chris@0: { Chris@0: $this->_headers = array(array_values($headers)); Chris@0: $this->_updateRowsCols($headers); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a row to the table. Chris@0: * Chris@0: * @param array $row The row data to add. Chris@0: * @param boolean $append Whether to append or prepend the row. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function addRow($row, $append = true) Chris@0: { Chris@0: if ($append) { Chris@0: $this->_data[] = array_values($row); Chris@0: } else { Chris@0: array_unshift($this->_data, array_values($row)); Chris@0: } Chris@0: Chris@0: $this->_updateRowsCols($row); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Inserts a row after a given row number in the table. Chris@0: * Chris@0: * If $row_id is not given it will prepend the row. Chris@0: * Chris@0: * @param array $row The data to insert. Chris@0: * @param integer $row_id Row number to insert before. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function insertRow($row, $row_id = 0) Chris@0: { Chris@0: array_splice($this->_data, $row_id, 0, array($row)); Chris@0: Chris@0: $this->_updateRowsCols($row); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a column to the table. Chris@0: * Chris@0: * @param array $col_data The data of the column. Chris@0: * @param integer $col_id The column index to populate. Chris@0: * @param integer $row_id If starting row is not zero, specify it here. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function addCol($col_data, $col_id = 0, $row_id = 0) Chris@0: { Chris@0: foreach ($col_data as $col_cell) { Chris@0: $this->_data[$row_id++][$col_id] = $col_cell; Chris@0: } Chris@0: Chris@0: $this->_updateRowsCols(); Chris@0: $this->_max_cols = max($this->_max_cols, $col_id + 1); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds data to the table. Chris@0: * Chris@0: * @param array $data A two dimensional array with the table data. Chris@0: * @param integer $col_id Starting column number. Chris@0: * @param integer $row_id Starting row number. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function addData($data, $col_id = 0, $row_id = 0) Chris@0: { Chris@0: foreach ($data as $row) { Chris@0: if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { Chris@0: $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE; Chris@0: $row_id++; Chris@0: continue; Chris@0: } Chris@0: $starting_col = $col_id; Chris@0: foreach ($row as $cell) { Chris@0: $this->_data[$row_id][$starting_col++] = $cell; Chris@0: } Chris@0: $this->_updateRowsCols(); Chris@0: $this->_max_cols = max($this->_max_cols, $starting_col); Chris@0: $row_id++; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Adds a horizontal seperator to the table. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function addSeparator() Chris@0: { Chris@0: $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the generated table. Chris@0: * Chris@0: * @return string The generated table. Chris@0: */ Chris@0: function getTable() Chris@0: { Chris@0: $this->_applyFilters(); Chris@0: $this->_calculateTotals(); Chris@0: $this->_validateTable(); Chris@0: Chris@0: return $this->_buildTable(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Calculates totals for columns. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _calculateTotals() Chris@0: { Chris@0: if (empty($this->_calculateTotals)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $this->addSeparator(); Chris@0: Chris@0: $totals = array(); Chris@0: foreach ($this->_data as $row) { Chris@0: if (is_array($row)) { Chris@0: foreach ($this->_calculateTotals as $columnID) { Chris@0: $totals[$columnID] += $row[$columnID]; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $this->_data[] = $totals; Chris@0: $this->_updateRowsCols(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Applies any column filters to the data. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _applyFilters() Chris@0: { Chris@0: if (empty($this->_filters)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: foreach ($this->_filters as $filter) { Chris@0: $column = $filter[0]; Chris@0: $callback = $filter[1]; Chris@0: Chris@0: foreach ($this->_data as $row_id => $row_data) { Chris@0: if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) { Chris@0: $this->_data[$row_id][$column] = Chris@0: call_user_func($callback, $row_data[$column]); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Ensures that column and row counts are correct. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _validateTable() Chris@0: { Chris@0: if (!empty($this->_headers)) { Chris@0: $this->_calculateRowHeight(-1, $this->_headers[0]); Chris@0: } Chris@0: Chris@0: for ($i = 0; $i < $this->_max_rows; $i++) { Chris@0: for ($j = 0; $j < $this->_max_cols; $j++) { Chris@0: if (!isset($this->_data[$i][$j]) && Chris@0: (!isset($this->_data[$i]) || Chris@0: $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) { Chris@0: $this->_data[$i][$j] = ''; Chris@0: } Chris@0: Chris@0: } Chris@0: $this->_calculateRowHeight($i, $this->_data[$i]); Chris@0: Chris@0: if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { Chris@0: ksort($this->_data[$i]); Chris@0: } Chris@0: Chris@0: } Chris@0: Chris@0: $this->_splitMultilineRows(); Chris@0: Chris@0: // Update cell lengths. Chris@0: for ($i = 0; $i < count($this->_headers); $i++) { Chris@0: $this->_calculateCellLengths($this->_headers[$i]); Chris@0: } Chris@0: for ($i = 0; $i < $this->_max_rows; $i++) { Chris@0: $this->_calculateCellLengths($this->_data[$i]); Chris@0: } Chris@0: Chris@0: ksort($this->_data); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Splits multiline rows into many smaller one-line rows. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _splitMultilineRows() Chris@0: { Chris@0: ksort($this->_data); Chris@0: $sections = array(&$this->_headers, &$this->_data); Chris@0: $max_rows = array(count($this->_headers), $this->_max_rows); Chris@0: $row_height_offset = array(-1, 0); Chris@0: Chris@0: for ($s = 0; $s <= 1; $s++) { Chris@0: $inserted = 0; Chris@0: $new_data = $sections[$s]; Chris@0: Chris@0: for ($i = 0; $i < $max_rows[$s]; $i++) { Chris@0: // Process only rows that have many lines. Chris@0: $height = $this->_row_heights[$i + $row_height_offset[$s]]; Chris@0: if ($height > 1) { Chris@0: // Split column data into one-liners. Chris@0: $split = array(); Chris@0: for ($j = 0; $j < $this->_max_cols; $j++) { Chris@0: $split[$j] = preg_split('/\r?\n|\r/', Chris@0: $sections[$s][$i][$j]); Chris@0: } Chris@0: Chris@0: $new_rows = array(); Chris@0: // Construct new 'virtual' rows - insert empty strings for Chris@0: // columns that have less lines that the highest one. Chris@0: for ($i2 = 0; $i2 < $height; $i2++) { Chris@0: for ($j = 0; $j < $this->_max_cols; $j++) { Chris@0: $new_rows[$i2][$j] = !isset($split[$j][$i2]) Chris@0: ? '' Chris@0: : $split[$j][$i2]; Chris@0: } Chris@0: } Chris@0: Chris@0: // Replace current row with smaller rows. $inserted is Chris@0: // used to take account of bigger array because of already Chris@0: // inserted rows. Chris@0: array_splice($new_data, $i + $inserted, 1, $new_rows); Chris@0: $inserted += count($new_rows) - 1; Chris@0: } Chris@0: } Chris@0: Chris@0: // Has the data been modified? Chris@0: if ($inserted > 0) { Chris@0: $sections[$s] = $new_data; Chris@0: $this->_updateRowsCols(); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds the table. Chris@0: * Chris@0: * @return string The generated table string. Chris@0: */ Chris@0: function _buildTable() Chris@0: { Chris@0: if (!count($this->_data)) { Chris@0: return ''; Chris@0: } Chris@0: Chris@0: $vertical = $this->_border['vertical']; Chris@0: $separator = $this->_getSeparator(); Chris@0: Chris@0: $return = array(); Chris@0: for ($i = 0; $i < count($this->_data); $i++) { Chris@12: if (is_array($this->_data[$i])) { Chris@12: for ($j = 0; $j < count($this->_data[$i]); $j++) { Chris@12: if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE && Chris@12: $this->_strlen($this->_data[$i][$j]) < Chris@12: $this->_cell_lengths[$j]) { Chris@12: $this->_data[$i][$j] = $this->_strpad($this->_data[$i][$j], Chris@12: $this->_cell_lengths[$j], Chris@12: ' ', Chris@12: $this->_col_align[$j]); Chris@12: } Chris@0: } Chris@0: } Chris@0: Chris@0: if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) { Chris@0: $row_begin = $this->_borderVisibility['left'] Chris@0: ? $vertical . str_repeat(' ', $this->_padding) Chris@0: : ''; Chris@0: $row_end = $this->_borderVisibility['right'] Chris@0: ? str_repeat(' ', $this->_padding) . $vertical Chris@0: : ''; Chris@0: $implode_char = str_repeat(' ', $this->_padding) . $vertical Chris@0: . str_repeat(' ', $this->_padding); Chris@0: $return[] = $row_begin Chris@0: . implode($implode_char, $this->_data[$i]) . $row_end; Chris@0: } elseif (!empty($separator)) { Chris@0: $return[] = $separator; Chris@0: } Chris@0: Chris@0: } Chris@0: Chris@0: $return = implode(PHP_EOL, $return); Chris@0: if (!empty($separator)) { Chris@0: if ($this->_borderVisibility['inner']) { Chris@0: $return = $separator . PHP_EOL . $return; Chris@0: } Chris@0: if ($this->_borderVisibility['bottom']) { Chris@0: $return .= PHP_EOL . $separator; Chris@0: } Chris@0: } Chris@0: $return .= PHP_EOL; Chris@0: Chris@0: if (!empty($this->_headers)) { Chris@0: $return = $this->_getHeaderLine() . PHP_EOL . $return; Chris@0: } Chris@0: Chris@0: return $return; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Creates a horizontal separator for header separation and table Chris@0: * start/end etc. Chris@0: * Chris@0: * @return string The horizontal separator. Chris@0: */ Chris@0: function _getSeparator() Chris@0: { Chris@0: if (!$this->_border) { Chris@0: return; Chris@0: } Chris@0: Chris@0: $horizontal = $this->_border['horizontal']; Chris@0: $intersection = $this->_border['intersection']; Chris@0: Chris@0: $return = array(); Chris@0: foreach ($this->_cell_lengths as $cl) { Chris@0: $return[] = str_repeat($horizontal, $cl); Chris@0: } Chris@0: Chris@0: $row_begin = $this->_borderVisibility['left'] Chris@0: ? $intersection . str_repeat($horizontal, $this->_padding) Chris@0: : ''; Chris@0: $row_end = $this->_borderVisibility['right'] Chris@0: ? str_repeat($horizontal, $this->_padding) . $intersection Chris@0: : ''; Chris@0: $implode_char = str_repeat($horizontal, $this->_padding) . $intersection Chris@0: . str_repeat($horizontal, $this->_padding); Chris@0: Chris@0: return $row_begin . implode($implode_char, $return) . $row_end; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the header line for the table. Chris@0: * Chris@0: * @return string The header line of the table. Chris@0: */ Chris@0: function _getHeaderLine() Chris@0: { Chris@0: // Make sure column count is correct Chris@0: for ($j = 0; $j < count($this->_headers); $j++) { Chris@0: for ($i = 0; $i < $this->_max_cols; $i++) { Chris@0: if (!isset($this->_headers[$j][$i])) { Chris@0: $this->_headers[$j][$i] = ''; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: for ($j = 0; $j < count($this->_headers); $j++) { Chris@0: for ($i = 0; $i < count($this->_headers[$j]); $i++) { Chris@0: if ($this->_strlen($this->_headers[$j][$i]) < Chris@0: $this->_cell_lengths[$i]) { Chris@0: $this->_headers[$j][$i] = Chris@0: $this->_strpad($this->_headers[$j][$i], Chris@0: $this->_cell_lengths[$i], Chris@0: ' ', Chris@0: $this->_col_align[$i]); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: $vertical = $this->_border['vertical']; Chris@0: $row_begin = $this->_borderVisibility['left'] Chris@0: ? $vertical . str_repeat(' ', $this->_padding) Chris@0: : ''; Chris@0: $row_end = $this->_borderVisibility['right'] Chris@0: ? str_repeat(' ', $this->_padding) . $vertical Chris@0: : ''; Chris@0: $implode_char = str_repeat(' ', $this->_padding) . $vertical Chris@0: . str_repeat(' ', $this->_padding); Chris@0: Chris@0: $separator = $this->_getSeparator(); Chris@0: if (!empty($separator) && $this->_borderVisibility['top']) { Chris@0: $return[] = $separator; Chris@0: } Chris@0: for ($j = 0; $j < count($this->_headers); $j++) { Chris@0: $return[] = $row_begin Chris@0: . implode($implode_char, $this->_headers[$j]) . $row_end; Chris@0: } Chris@0: Chris@0: return implode(PHP_EOL, $return); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates values for maximum columns and rows. Chris@0: * Chris@0: * @param array $rowdata Data array of a single row. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _updateRowsCols($rowdata = null) Chris@0: { Chris@0: // Update maximum columns. Chris@12: $this->_max_cols = max($this->_max_cols, is_array($rowdata) ? count($rowdata) : 0); Chris@0: Chris@0: // Update maximum rows. Chris@0: ksort($this->_data); Chris@0: $keys = array_keys($this->_data); Chris@0: $this->_max_rows = end($keys) + 1; Chris@0: Chris@0: switch ($this->_defaultAlign) { Chris@0: case CONSOLE_TABLE_ALIGN_CENTER: Chris@0: $pad = STR_PAD_BOTH; Chris@0: break; Chris@0: case CONSOLE_TABLE_ALIGN_RIGHT: Chris@0: $pad = STR_PAD_LEFT; Chris@0: break; Chris@0: default: Chris@0: $pad = STR_PAD_RIGHT; Chris@0: break; Chris@0: } Chris@0: Chris@0: // Set default column alignments Chris@0: for ($i = 0; $i < $this->_max_cols; $i++) { Chris@0: if (!isset($this->_col_align[$i])) { Chris@0: $this->_col_align[$i] = $pad; Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Calculates the maximum length for each column of a row. Chris@0: * Chris@0: * @param array $row The row data. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _calculateCellLengths($row) Chris@0: { Chris@12: if (is_array($row)) { Chris@12: for ($i = 0; $i < count($row); $i++) { Chris@12: if (!isset($this->_cell_lengths[$i])) { Chris@12: $this->_cell_lengths[$i] = 0; Chris@12: } Chris@12: $this->_cell_lengths[$i] = max($this->_cell_lengths[$i], Chris@12: $this->_strlen($row[$i])); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Calculates the maximum height for all columns of a row. Chris@0: * Chris@0: * @param integer $row_number The row number. Chris@0: * @param array $row The row data. Chris@0: * Chris@0: * @return void Chris@0: */ Chris@0: function _calculateRowHeight($row_number, $row) Chris@0: { Chris@0: if (!isset($this->_row_heights[$row_number])) { Chris@0: $this->_row_heights[$row_number] = 1; Chris@0: } Chris@0: Chris@0: // Do not process horizontal rule rows. Chris@0: if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) { Chris@0: return; Chris@0: } Chris@0: Chris@0: for ($i = 0, $c = count($row); $i < $c; ++$i) { Chris@0: $lines = preg_split('/\r?\n|\r/', $row[$i]); Chris@0: $this->_row_heights[$row_number] = max($this->_row_heights[$row_number], Chris@0: count($lines)); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the character length of a string. Chris@0: * Chris@0: * @param string $str A multibyte or singlebyte string. Chris@0: * Chris@0: * @return integer The string length. Chris@0: */ Chris@0: function _strlen($str) Chris@0: { Chris@0: static $mbstring; Chris@0: Chris@0: // Strip ANSI color codes if requested. Chris@0: if ($this->_ansiColor) { Chris@0: $str = $this->_ansiColor->strip($str); Chris@0: } Chris@0: Chris@0: // Cache expensive function_exists() calls. Chris@0: if (!isset($mbstring)) { Chris@0: $mbstring = function_exists('mb_strwidth'); Chris@0: } Chris@0: Chris@0: if ($mbstring) { Chris@0: return mb_strwidth($str, $this->_charset); Chris@0: } Chris@0: Chris@0: return strlen($str); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns part of a string. Chris@0: * Chris@0: * @param string $string The string to be converted. Chris@0: * @param integer $start The part's start position, zero based. Chris@0: * @param integer $length The part's length. Chris@0: * Chris@0: * @return string The string's part. Chris@0: */ Chris@0: function _substr($string, $start, $length = null) Chris@0: { Chris@0: static $mbstring; Chris@0: Chris@0: // Cache expensive function_exists() calls. Chris@0: if (!isset($mbstring)) { Chris@0: $mbstring = function_exists('mb_substr'); Chris@0: } Chris@0: Chris@0: if (is_null($length)) { Chris@0: $length = $this->_strlen($string); Chris@0: } Chris@0: if ($mbstring) { Chris@0: $ret = @mb_substr($string, $start, $length, $this->_charset); Chris@0: if (!empty($ret)) { Chris@0: return $ret; Chris@0: } Chris@0: } Chris@0: return substr($string, $start, $length); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns a string padded to a certain length with another string. Chris@0: * Chris@0: * This method behaves exactly like str_pad but is multibyte safe. Chris@0: * Chris@0: * @param string $input The string to be padded. Chris@0: * @param integer $length The length of the resulting string. Chris@0: * @param string $pad The string to pad the input string with. Must Chris@0: * be in the same charset like the input string. Chris@0: * @param const $type The padding type. One of STR_PAD_LEFT, Chris@0: * STR_PAD_RIGHT, or STR_PAD_BOTH. Chris@0: * Chris@0: * @return string The padded string. Chris@0: */ Chris@0: function _strpad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT) Chris@0: { Chris@0: $mb_length = $this->_strlen($input); Chris@0: $sb_length = strlen($input); Chris@0: $pad_length = $this->_strlen($pad); Chris@0: Chris@0: /* Return if we already have the length. */ Chris@0: if ($mb_length >= $length) { Chris@0: return $input; Chris@0: } Chris@0: Chris@0: /* Shortcut for single byte strings. */ Chris@0: if ($mb_length == $sb_length && $pad_length == strlen($pad)) { Chris@0: return str_pad($input, $length, $pad, $type); Chris@0: } Chris@0: Chris@0: switch ($type) { Chris@0: case STR_PAD_LEFT: Chris@0: $left = $length - $mb_length; Chris@0: $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), Chris@0: 0, $left, $this->_charset) . $input; Chris@0: break; Chris@0: case STR_PAD_BOTH: Chris@0: $left = floor(($length - $mb_length) / 2); Chris@0: $right = ceil(($length - $mb_length) / 2); Chris@0: $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), Chris@0: 0, $left, $this->_charset) . Chris@0: $input . Chris@0: $this->_substr(str_repeat($pad, ceil($right / $pad_length)), Chris@0: 0, $right, $this->_charset); Chris@0: break; Chris@0: case STR_PAD_RIGHT: Chris@0: $right = $length - $mb_length; Chris@0: $output = $input . Chris@0: $this->_substr(str_repeat($pad, ceil($right / $pad_length)), Chris@0: 0, $right, $this->_charset); Chris@0: break; Chris@0: } Chris@0: Chris@0: return $output; Chris@0: } Chris@0: Chris@0: }