Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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