home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / Console / Table.php < prev   
Encoding:
PHP Script  |  2008-07-02  |  24.1 KB  |  814 lines

  1. <?php
  2. /**
  3.  * Utility for printing tables from commandline scripts.
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * All rights reserved.
  8.  *
  9.  * Redistribution and use in source and binary forms, with or without
  10.  * modification, are permitted provided that the following conditions are met:
  11.  *
  12.  * o Redistributions of source code must retain the above copyright notice,
  13.  *   this list of conditions and the following disclaimer.
  14.  * o Redistributions in binary form must reproduce the above copyright notice,
  15.  *   this list of conditions and the following disclaimer in the documentation
  16.  *   and/or other materials provided with the distribution.
  17.  * o The names of the authors may not be used to endorse or promote products
  18.  *   derived from this software without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  24.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30.  * POSSIBILITY OF SUCH DAMAGE.
  31.  *
  32.  * @category   Console
  33.  * @package    Console_Table
  34.  * @author     Richard Heyes <richard@phpguru.org>
  35.  * @author     Jan Schneider <jan@horde.org>
  36.  * @copyright  2002-2005 Richard Heyes
  37.  * @copyright  2006-2008 Jan Schneider
  38.  * @version    CVS: $Id: Table.php,v 1.24 2008/04/09 09:41:52 yunosh Exp $
  39.  * @link       http://pear.php.net/package/Console_Table
  40.  */
  41.  
  42. define('CONSOLE_TABLE_HORIZONTAL_RULE', 1);
  43. define('CONSOLE_TABLE_ALIGN_LEFT', -1);
  44. define('CONSOLE_TABLE_ALIGN_CENTER', 0);
  45. define('CONSOLE_TABLE_ALIGN_RIGHT', 1);
  46. define('CONSOLE_TABLE_BORDER_ASCII', -1);
  47.  
  48. class Console_Table
  49. {
  50.     /**
  51.      * The table headers.
  52.      *
  53.      * @var array
  54.      */
  55.     var $_headers = array();
  56.  
  57.     /**
  58.      * The data of the table.
  59.      *
  60.      * @var array
  61.      */
  62.     var $_data = array();
  63.  
  64.     /**
  65.      * The maximum number of columns in a row.
  66.      *
  67.      * @var integer
  68.      */
  69.     var $_max_cols = 0;
  70.  
  71.     /**
  72.      * The maximum number of rows in the table.
  73.      *
  74.      * @var integer
  75.      */
  76.     var $_max_rows = 0;
  77.  
  78.     /**
  79.      * Lengths of the columns, calculated when rows are added to the table.
  80.      *
  81.      * @var array
  82.      */
  83.     var $_cell_lengths = array();
  84.  
  85.     /**
  86.      * Heights of the rows.
  87.      *
  88.      * @var array
  89.      */
  90.     var $_row_heights = array();
  91.  
  92.     /**
  93.      * How many spaces to use to pad the table.
  94.      *
  95.      * @var integer
  96.      */
  97.     var $_padding = 1;
  98.  
  99.     /**
  100.      * Column filters.
  101.      *
  102.      * @var array
  103.      */
  104.     var $_filters = array();
  105.  
  106.     /**
  107.      * Columns to calculate totals for.
  108.      *
  109.      * @var array
  110.      */
  111.     var $_calculateTotals;
  112.  
  113.     /**
  114.      * Alignment of the columns.
  115.      *
  116.      * @var array
  117.      */
  118.     var $_col_align = array();
  119.  
  120.     /**
  121.      * Default alignment of columns.
  122.      *
  123.      * @var integer
  124.      */
  125.     var $_defaultAlign;
  126.  
  127.     /**
  128.      * Character set of the data.
  129.      *
  130.      * @var string
  131.      */
  132.     var $_charset = 'utf-8';
  133.  
  134.     /**
  135.      * Border character
  136.      *
  137.      * @var string
  138.      */
  139.     var $_border = CONSOLE_TABLE_BORDER_ASCII;
  140.  
  141.     /**
  142.      * Constructor.
  143.      *
  144.      * @param integer $align    Default alignment. One of
  145.      *                          CONSOLE_TABLE_ALIGN_LEFT,
  146.      *                          CONSOLE_TABLE_ALIGN_CENTER or
  147.      *                          CONSOLE_TABLE_ALIGN_RIGHT.
  148.      * @param string $border    The character used for table borders or
  149.      *                          CONSOLE_TABLE_BORDER_ASCII.
  150.      * @param integer $padding  How many spaces to use to pad the table.
  151.      * @param string $charset   A charset supported by the mbstring PHP
  152.      *                          extension.
  153.      */
  154.     function Console_Table($align = CONSOLE_TABLE_ALIGN_LEFT,
  155.                            $border = CONSOLE_TABLE_BORDER_ASCII, $padding = 1,
  156.                            $charset = null)
  157.     {
  158.         $this->_defaultAlign = $align;
  159.         $this->_border = $border;
  160.         $this->_padding = $padding;
  161.         if (!empty($charset)) {
  162.             $this->setCharset($charset);
  163.         }
  164.     }
  165.  
  166.     /**
  167.      * Converts an array to a table.
  168.      *
  169.      * @static
  170.      *
  171.      * @param array $headers         Headers for the table.
  172.      * @param array $data            A two dimensional array with the table
  173.      *                               data.
  174.      * @param boolean $returnObject  Whether to return the Console_Table object
  175.      *                               instead of the rendered table.
  176.      *
  177.      * @return Console_Table|string  A Console_Table object or the generated
  178.      *                               table.
  179.      */
  180.     function fromArray($headers, $data, $returnObject = false)
  181.     {
  182.         if (!is_array($headers) || !is_array($data)) {
  183.             return false;
  184.         }
  185.  
  186.         $table = new Console_Table();
  187.         $table->setHeaders($headers);
  188.  
  189.         foreach ($data as $row) {
  190.             $table->addRow($row);
  191.         }
  192.  
  193.         return $returnObject ? $table : $table->getTable();
  194.     }
  195.  
  196.     /**
  197.      * Adds a filter to a column.
  198.      *
  199.      * Filters are standard PHP callbacks which are run on the data before
  200.      * table generation is performed. Filters are applied in the order they
  201.      * are added. The callback function must accept a single argument, which
  202.      * is a single table cell.
  203.      *
  204.      * @param integer $col     Column to apply filter to.
  205.      * @param mixed $callback  PHP callback to apply.
  206.      */
  207.     function addFilter($col, &$callback)
  208.     {
  209.         $this->_filters[] = array($col, &$callback);
  210.     }
  211.  
  212.     /**
  213.      * Sets the charset of the provided table data.
  214.      *
  215.      * @param string $charset  A charset supported by the mbstring PHP
  216.      *                         extension.
  217.      */
  218.     function setCharset($charset)
  219.     {
  220.         $locale = setlocale(LC_CTYPE, 0);
  221.         setlocale(LC_CTYPE, 'en_US');
  222.         $this->_charset = strtolower($charset);
  223.         setlocale(LC_CTYPE, $locale);
  224.     }
  225.  
  226.     /**
  227.      * Sets the alignment for the columns.
  228.      *
  229.      * @param integer $col_id  The column number.
  230.      * @param integer $align   Alignment to set for this column. One of
  231.      *                         CONSOLE_TABLE_ALIGN_LEFT
  232.      *                         CONSOLE_TABLE_ALIGN_CENTER
  233.      *                         CONSOLE_TABLE_ALIGN_RIGHT.
  234.      */
  235.     function setAlign($col_id, $align = CONSOLE_TABLE_ALIGN_LEFT)
  236.     {
  237.         switch ($align) {
  238.         case CONSOLE_TABLE_ALIGN_CENTER:
  239.             $pad = STR_PAD_BOTH;
  240.             break;
  241.         case CONSOLE_TABLE_ALIGN_RIGHT:
  242.             $pad = STR_PAD_LEFT;
  243.             break;
  244.         default:
  245.             $pad = STR_PAD_RIGHT;
  246.             break;
  247.         }
  248.         $this->_col_align[$col_id] = $pad;
  249.     }
  250.  
  251.     /**
  252.      * Specifies which columns are to have totals calculated for them and
  253.      * added as a new row at the bottom.
  254.      *
  255.      * @param array $cols  Array of column numbers (starting with 0).
  256.      */
  257.     function calculateTotalsFor($cols)
  258.     {
  259.         $this->_calculateTotals = $cols;
  260.     }
  261.  
  262.     /**
  263.      * Sets the headers for the columns.
  264.      *
  265.      * @param array $headers  The column headers.
  266.      */
  267.     function setHeaders($headers)
  268.     {
  269.         $this->_headers = array(array_values($headers));
  270.         $this->_updateRowsCols($headers);
  271.     }
  272.  
  273.     /**
  274.      * Adds a row to the table.
  275.      *
  276.      * @param array $row       The row data to add.
  277.      * @param boolean $append  Whether to append or prepend the row.
  278.      */
  279.     function addRow($row, $append = true)
  280.     {
  281.         if ($append) {
  282.             $this->_data[] = array_values($row);
  283.         } else {
  284.             array_unshift($this->_data, array_values($row));
  285.         }
  286.  
  287.         $this->_updateRowsCols($row);
  288.     }
  289.  
  290.     /**
  291.      * Inserts a row after a given row number in the table.
  292.      *
  293.      * If $row_id is not given it will prepend the row.
  294.      *
  295.      * @param array $row       The data to insert.
  296.      * @param integer $row_id  Row number to insert before.
  297.      */
  298.     function insertRow($row, $row_id = 0)
  299.     {
  300.         array_splice($this->_data, $row_id, 0, array($row));
  301.  
  302.         $this->_updateRowsCols($row);
  303.     }
  304.  
  305.     /**
  306.      * Adds a column to the table.
  307.      *
  308.      * @param array $col_data  The data of the column.
  309.      * @param integer $col_id  The column index to populate.
  310.      * @param integer $row_id  If starting row is not zero, specify it here.
  311.      */
  312.     function addCol($col_data, $col_id = 0, $row_id = 0)
  313.     {
  314.         foreach ($col_data as $col_cell) {
  315.             $this->_data[$row_id++][$col_id] = $col_cell;
  316.         }
  317.  
  318.         $this->_updateRowsCols();
  319.         $this->_max_cols = max($this->_max_cols, $col_id + 1);
  320.     }
  321.  
  322.     /**
  323.      * Adds data to the table.
  324.      *
  325.      * @param array $data      A two dimensional array with the table data.
  326.      * @param integer $col_id  Starting column number.
  327.      * @param integer $row_id  Starting row number.
  328.      */
  329.     function addData($data, $col_id = 0, $row_id = 0)
  330.     {
  331.         foreach ($data as $row) {
  332.             if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
  333.                 $this->_data[$row_id] = CONSOLE_TABLE_HORIZONTAL_RULE;
  334.                 $row_id++;
  335.                 continue;
  336.             }
  337.             $starting_col = $col_id;
  338.             foreach ($row as $cell) {
  339.                 $this->_data[$row_id][$starting_col++] = $cell;
  340.             }
  341.             $this->_updateRowsCols();
  342.             $this->_max_cols = max($this->_max_cols, $starting_col);
  343.             $row_id++;
  344.         }
  345.     }
  346.  
  347.     /**
  348.      * Adds a horizontal seperator to the table.
  349.      */
  350.     function addSeparator()
  351.     {
  352.         $this->_data[] = CONSOLE_TABLE_HORIZONTAL_RULE;
  353.     }
  354.  
  355.     /*
  356.      * Returns the generated table.
  357.      *
  358.      * @return string  The generated table.
  359.      */
  360.     function getTable()
  361.     {
  362.         $this->_applyFilters();
  363.         $this->_calculateTotals();
  364.         $this->_validateTable();
  365.  
  366.         return $this->_buildTable();
  367.     }
  368.  
  369.     /**
  370.      * Calculates totals for columns.
  371.      */
  372.     function _calculateTotals()
  373.     {
  374.         if (empty($this->_calculateTotals)) {
  375.             return;
  376.         }
  377.  
  378.         $this->addSeparator();
  379.  
  380.         $totals = array();
  381.         foreach ($this->_data as $row) {
  382.             if (is_array($row)) {
  383.                 foreach ($this->_calculateTotals as $columnID) {
  384.                     $totals[$columnID] += $row[$columnID];
  385.                 }
  386.             }
  387.         }
  388.  
  389.         $this->_data[] = $totals;
  390.         $this->_updateRowsCols();
  391.     }
  392.  
  393.     /**
  394.      * Applies any column filters to the data.
  395.      */
  396.     function _applyFilters()
  397.     {
  398.         if (empty($this->_filters)) {
  399.             return;
  400.         }
  401.  
  402.         foreach ($this->_filters as $filter) {
  403.             $column   = $filter[0];
  404.             $callback = $filter[1];
  405.  
  406.             foreach ($this->_data as $row_id => $row_data) {
  407.                 if ($row_data !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  408.                     $this->_data[$row_id][$column] = call_user_func(
  409.                         $callback, $row_data[$column]);
  410.                 }
  411.             }
  412.         }
  413.     }
  414.  
  415.     /**
  416.      * Ensures that column and row counts are correct.
  417.      */
  418.     function _validateTable()
  419.     {
  420.         if (!empty($this->_headers)) {
  421.             $this->_calculateRowHeight(-1, $this->_headers[0]);
  422.         }
  423.  
  424.         for ($i = 0; $i < $this->_max_rows; $i++) {
  425.             for ($j = 0; $j < $this->_max_cols; $j++) {
  426.                 if (!isset($this->_data[$i][$j]) &&
  427.                     (!isset($this->_data[$i]) ||
  428.                      $this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE)) {
  429.                     $this->_data[$i][$j] = '';
  430.                 }
  431.  
  432.             }
  433.             $this->_calculateRowHeight($i, $this->_data[$i]);
  434.  
  435.             if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  436.                  ksort($this->_data[$i]);
  437.             }
  438.  
  439.         }
  440.  
  441.         $this->_splitMultilineRows();
  442.  
  443.         // Update cell lengths.
  444.         for ($i = 0; $i < count($this->_headers); $i++) {
  445.             $this->_calculateCellLengths($this->_headers[$i]);
  446.         }
  447.         for ($i = 0; $i < $this->_max_rows; $i++) {
  448.             $this->_calculateCellLengths($this->_data[$i]);
  449.         }
  450.  
  451.         ksort($this->_data);
  452.     }
  453.  
  454.     /**
  455.      * Splits multiline rows into many smaller one-line rows.
  456.      */
  457.     function _splitMultilineRows()
  458.     {
  459.         ksort($this->_data);
  460.         $sections = array(&$this->_headers, &$this->_data);
  461.         $max_rows = array(count($this->_headers), $this->_max_rows);
  462.         $row_height_offset = array(-1, 0);
  463.  
  464.         for ($s = 0; $s <= 1; $s++) {
  465.             $inserted = 0;
  466.             $new_data = $sections[$s];
  467.  
  468.             for ($i = 0; $i < $max_rows[$s]; $i++) {
  469.                 // Process only rows that have many lines.
  470.                 $height = $this->_row_heights[$i + $row_height_offset[$s]];
  471.                 if ($height > 1) {
  472.                     // Split column data into one-liners.
  473.                     $split = array();
  474.                     for ($j = 0; $j < $this->_max_cols; $j++) {
  475.                         $split[$j] = preg_split('/\r?\n|\r/',
  476.                                                 $sections[$s][$i][$j]);
  477.                     }
  478.  
  479.                     $new_rows = array();
  480.                     // Construct new 'virtual' rows - insert empty strings for
  481.                     // columns that have less lines that the highest one.
  482.                     for ($i2 = 0; $i2 < $height; $i2++) {
  483.                         for ($j = 0; $j < $this->_max_cols; $j++) {
  484.                             $new_rows[$i2][$j] = !isset($split[$j][$i2])
  485.                                 ? ''
  486.                                 : $split[$j][$i2];
  487.                         }
  488.                     }
  489.  
  490.                     // Replace current row with smaller rows.  $inserted is
  491.                     // used to take account of bigger array because of already
  492.                     // inserted rows.
  493.                     array_splice($new_data, $i + $inserted, 1, $new_rows);
  494.                     $inserted += count($new_rows) - 1;
  495.                 }
  496.             }
  497.  
  498.             // Has the data been modified?
  499.             if ($inserted > 0) {
  500.                 $sections[$s] = $new_data;
  501.                 $this->_updateRowsCols();
  502.             }
  503.         }
  504.     }
  505.  
  506.     /**
  507.      * Builds the table.
  508.      */
  509.     function _buildTable()
  510.     {
  511.         $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII
  512.             ? '|'
  513.             : $this->_border;
  514.         $separator = $this->_getSeparator();
  515.  
  516.         $return = array();
  517.         for ($i = 0; $i < count($this->_data); $i++) {
  518.             for ($j = 0; $j < count($this->_data[$i]); $j++) {
  519.                 if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE &&
  520.                     $this->_strlen($this->_data[$i][$j]) < $this->_cell_lengths[$j]) {
  521.                     $this->_data[$i][$j] = $this->_str_pad(
  522.                         $this->_data[$i][$j],
  523.                         $this->_cell_lengths[$j],
  524.                         ' ',
  525.                         $this->_col_align[$j]);
  526.                 }
  527.             }
  528.  
  529.             if ($this->_data[$i] !== CONSOLE_TABLE_HORIZONTAL_RULE) {
  530.                 $row_begin = $rule . str_repeat(' ', $this->_padding);
  531.                 $row_end = str_repeat(' ', $this->_padding) . $rule;
  532.                 $implode_char = str_repeat(' ', $this->_padding) . $rule
  533.                     . str_repeat(' ', $this->_padding);
  534.                 $return[] = $row_begin
  535.                     . implode($implode_char, $this->_data[$i]) . $row_end;
  536.             } elseif (!empty($separator)) {
  537.                 $return[] = $separator;
  538.             }
  539.  
  540.         }
  541.  
  542.         $return = implode("\r\n", $return);
  543.         if (!empty($separator)) {
  544.             $return = $separator . "\r\n" . $return . "\r\n" . $separator;
  545.         }
  546.         $return .= "\r\n";
  547.  
  548.         if (!empty($this->_headers)) {
  549.             $return = $this->_getHeaderLine() .  "\r\n" . $return;
  550.         }
  551.  
  552.         return $return;
  553.     }
  554.  
  555.     /**
  556.      * Creates a horizontal separator for header separation and table
  557.      * start/end etc.
  558.      */
  559.     function _getSeparator()
  560.     {
  561.         if (!$this->_border) {
  562.             return;
  563.         }
  564.  
  565.         if ($this->_border == CONSOLE_TABLE_BORDER_ASCII) {
  566.             $rule = '-';
  567.             $sect = '+';
  568.         } else {
  569.             $rule = $sect = $this->_border;
  570.         }
  571.  
  572.         foreach ($this->_cell_lengths as $cl) {
  573.             $return[] = str_repeat($rule, $cl);
  574.         }
  575.  
  576.         $row_begin = $sect . str_repeat($rule, $this->_padding);
  577.         $row_end = str_repeat($rule, $this->_padding) . $sect;
  578.         $implode_char = str_repeat($rule, $this->_padding) . $sect
  579.             . str_repeat($rule, $this->_padding);
  580.  
  581.         return $row_begin . implode($implode_char, $return) . $row_end;
  582.     }
  583.  
  584.     /**
  585.      * Returns the header line for the table.
  586.      */
  587.     function _getHeaderLine()
  588.     {
  589.         // Make sure column count is correct
  590.         for ($j = 0; $j < count($this->_headers); $j++) {
  591.             for ($i = 0; $i < $this->_max_cols; $i++) {
  592.                 if (!isset($this->_headers[$j][$i])) {
  593.                     $this->_headers[$j][$i] = '';
  594.                 }
  595.             }
  596.         }
  597.  
  598.         for ($j = 0; $j < count($this->_headers); $j++) {
  599.             for ($i = 0; $i < count($this->_headers[$j]); $i++) {
  600.                 if ($this->_strlen($this->_headers[$j][$i]) < $this->_cell_lengths[$i]) {
  601.                     $this->_headers[$j][$i] = $this->_str_pad(
  602.                         $this->_headers[$j][$i],
  603.                         $this->_cell_lengths[$i],
  604.                         ' ',
  605.                         $this->_col_align[$i]);
  606.                 }
  607.             }
  608.         }
  609.  
  610.         $rule = $this->_border == CONSOLE_TABLE_BORDER_ASCII
  611.             ? '|'
  612.             : $this->_border;
  613.         $row_begin = $rule . str_repeat(' ', $this->_padding);
  614.         $row_end = str_repeat(' ', $this->_padding) . $rule;
  615.         $implode_char = str_repeat(' ', $this->_padding) . $rule
  616.             . str_repeat(' ', $this->_padding);
  617.  
  618.         $separator = $this->_getSeparator();
  619.         if (!empty($separator)) {
  620.             $return[] = $separator;
  621.         }
  622.         for ($j = 0; $j < count($this->_headers); $j++) {
  623.             $return[] = $row_begin
  624.                 . implode($implode_char, $this->_headers[$j]) . $row_end;
  625.         }
  626.  
  627.         return implode("\r\n", $return);
  628.     }
  629.  
  630.     /**
  631.      * Updates values for maximum columns and rows.
  632.      */
  633.     function _updateRowsCols($rowdata = null)
  634.     {
  635.         // Update maximum columns.
  636.         $this->_max_cols = max($this->_max_cols, count($rowdata));
  637.  
  638.         // Update maximum rows.
  639.         ksort($this->_data);
  640.         $keys = array_keys($this->_data);
  641.         $this->_max_rows = end($keys) + 1;
  642.  
  643.         switch ($this->_defaultAlign) {
  644.         case CONSOLE_TABLE_ALIGN_CENTER:
  645.             $pad = STR_PAD_BOTH;
  646.             break;
  647.         case CONSOLE_TABLE_ALIGN_RIGHT:
  648.             $pad = STR_PAD_LEFT;
  649.             break;
  650.         default:
  651.             $pad = STR_PAD_RIGHT;
  652.             break;
  653.         }
  654.  
  655.         // Set default column alignments
  656.         for ($i = count($this->_col_align); $i < $this->_max_cols; $i++) {
  657.             $this->_col_align[$i] = $pad;
  658.         }
  659.     }
  660.  
  661.     /**
  662.      * Calculates the maximum length for each column of a row.
  663.      *
  664.      * @param array $row  The row data.
  665.      */
  666.     function _calculateCellLengths($row)
  667.     {
  668.         for ($i = 0; $i < count($row); $i++) {
  669.             if (!isset($this->_cell_lengths[$i])) {
  670.                 $this->_cell_lengths[$i] = 0;
  671.             }
  672.             $this->_cell_lengths[$i] = max($this->_cell_lengths[$i],
  673.                                            $this->_strlen($row[$i]));
  674.         }
  675.     }
  676.  
  677.     /**
  678.      * Calculates the maximum height for all columns of a row.
  679.      *
  680.      * @param integer $row_number  The row number.
  681.      * @param array $row           The row data.
  682.      */
  683.     function _calculateRowHeight($row_number, $row)
  684.     {
  685.         if (!isset($this->_row_heights[$row_number])) {
  686.             $this->_row_heights[$row_number] = 1;
  687.         }
  688.  
  689.         // Do not process horizontal rule rows.
  690.         if ($row === CONSOLE_TABLE_HORIZONTAL_RULE) {
  691.             return;
  692.         }
  693.  
  694.         for ($i = 0, $c = count($row); $i < $c; ++$i) {
  695.             $lines = preg_split('/\r?\n|\r/', $row[$i]);
  696.             $this->_row_heights[$row_number] = max($this->_row_heights[$row_number],
  697.                                                    count($lines));
  698.         }
  699.     }
  700.  
  701.     /**
  702.      * Returns the character length of a string.
  703.      *
  704.      * @param string $str  A multibyte or singlebyte string.
  705.      *
  706.      * @return integer  The string length.
  707.      */
  708.     function _strlen($str)
  709.     {
  710.         static $mbstring, $utf8;
  711.  
  712.         // Cache expensive function_exists() calls.
  713.         if (!isset($mbstring)) {
  714.             $mbstring = function_exists('mb_strlen');
  715.         }
  716.         if (!isset($utf8)) {
  717.             $utf8 = function_exists('utf8_decode');
  718.         }
  719.  
  720.         if ($utf8 &&
  721.             ($this->_charset == strtolower('utf-8') ||
  722.              $this->_charset == strtolower('utf8'))) {
  723.             return strlen(utf8_decode($str));
  724.         }
  725.         if ($mbstring) {
  726.             return mb_strlen($str, $this->_charset);
  727.         }
  728.  
  729.         return strlen($str);
  730.     }
  731.  
  732.     /**
  733.      * Returns part of a string.
  734.      *
  735.      * @param string $string   The string to be converted.
  736.      * @param integer $start   The part's start position, zero based.
  737.      * @param integer $length  The part's length.
  738.      *
  739.      * @return string  The string's part.
  740.      */
  741.     function _substr($string, $start, $length = null)
  742.     {
  743.         static $mbstring;
  744.  
  745.         // Cache expensive function_exists() calls.
  746.         if (!isset($mbstring)) {
  747.             $mbstring = function_exists('mb_substr');
  748.         }
  749.  
  750.         if (is_null($length)) {
  751.             $length = $this->_strlen($string);
  752.         }
  753.         if ($mbstring) {
  754.             $ret = @mb_substr($string, $start, $length, $this->_charset);
  755.             if (!empty($ret)) {
  756.                 return $ret;
  757.             }
  758.         }
  759.         return substr($string, $start, $length);
  760.     }
  761.  
  762.     /**
  763.      * Returns a string padded to a certain length with another string.
  764.      *
  765.      * This method behaves exactly like str_pad but is multibyte safe.
  766.      *
  767.      * @param string $input    The string to be padded.
  768.      * @param integer $length  The length of the resulting string.
  769.      * @param string $pad      The string to pad the input string with. Must
  770.      *                         be in the same charset like the input string.
  771.      * @param const $type      The padding type. One of STR_PAD_LEFT,
  772.      *                         STR_PAD_RIGHT, or STR_PAD_BOTH.
  773.      *
  774.      * @return string  The padded string.
  775.      */
  776.     function _str_pad($input, $length, $pad = ' ', $type = STR_PAD_RIGHT)
  777.     {
  778.         $mb_length = $this->_strlen($input);
  779.         $sb_length = strlen($input);
  780.         $pad_length = $this->_strlen($pad);
  781.  
  782.         /* Return if we already have the length. */
  783.         if ($mb_length >= $length) {
  784.             return $input;
  785.         }
  786.  
  787.         /* Shortcut for single byte strings. */
  788.         if ($mb_length == $sb_length && $pad_length == strlen($pad)) {
  789.             return str_pad($input, $length, $pad, $type);
  790.         }
  791.  
  792.         switch ($type) {
  793.         case STR_PAD_LEFT:
  794.             $left = $length - $mb_length;
  795.             $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $this->_charset) . $input;
  796.             break;
  797.         case STR_PAD_BOTH:
  798.             $left = floor(($length - $mb_length) / 2);
  799.             $right = ceil(($length - $mb_length) / 2);
  800.             $output = $this->_substr(str_repeat($pad, ceil($left / $pad_length)), 0, $left, $this->_charset) .
  801.                 $input .
  802.                 $this->_substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $this->_charset);
  803.             break;
  804.         case STR_PAD_RIGHT:
  805.             $right = $length - $mb_length;
  806.             $output = $input . $this->_substr(str_repeat($pad, ceil($right / $pad_length)), 0, $right, $this->_charset);
  807.             break;
  808.         }
  809.  
  810.         return $output;
  811.     }
  812.  
  813. }
  814.