Blame | Last modification | View Log | RSS feed
<?php/*** Pure-PHP ANSI Decoder** PHP version 5** If you call read() in \phpseclib\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.* They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC). They tell a* {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what* color to display them in, etc. \phpseclib\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.** @category File* @package ANSI* @author Jim Wigginton <terrafrost@php.net>* @copyright 2012 Jim Wigginton* @license http://www.opensource.org/licenses/mit-license.html MIT License* @link http://phpseclib.sourceforge.net*/namespace phpseclib\File;/*** Pure-PHP ANSI Decoder** @package ANSI* @author Jim Wigginton <terrafrost@php.net>* @access public*/class ANSI{/*** Max Width** @var int* @access private*/var $max_x;/*** Max Height** @var int* @access private*/var $max_y;/*** Max History** @var int* @access private*/var $max_history;/*** History** @var array* @access private*/var $history;/*** History Attributes** @var array* @access private*/var $history_attrs;/*** Current Column** @var int* @access private*/var $x;/*** Current Row** @var int* @access private*/var $y;/*** Old Column** @var int* @access private*/var $old_x;/*** Old Row** @var int* @access private*/var $old_y;/*** An empty attribute cell** @var object* @access private*/var $base_attr_cell;/*** The current attribute cell** @var object* @access private*/var $attr_cell;/*** An empty attribute row** @var array* @access private*/var $attr_row;/*** The current screen text** @var array* @access private*/var $screen;/*** The current screen attributes** @var array* @access private*/var $attrs;/*** Current ANSI code** @var string* @access private*/var $ansi;/*** Tokenization** @var array* @access private*/var $tokenization;/*** Default Constructor.** @return \phpseclib\File\ANSI* @access public*/function __construct(){$attr_cell = new \stdClass();$attr_cell->bold = false;$attr_cell->underline = false;$attr_cell->blink = false;$attr_cell->background = 'black';$attr_cell->foreground = 'white';$attr_cell->reverse = false;$this->base_attr_cell = clone $attr_cell;$this->attr_cell = clone $attr_cell;$this->setHistory(200);$this->setDimensions(80, 24);}/*** Set terminal width and height** Resets the screen as well** @param int $x* @param int $y* @access public*/function setDimensions($x, $y){$this->max_x = $x - 1;$this->max_y = $y - 1;$this->x = $this->y = 0;$this->history = $this->history_attrs = array();$this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);$this->screen = array_fill(0, $this->max_y + 1, '');$this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);$this->ansi = '';}/*** Set the number of lines that should be logged past the terminal height** @param int $x* @param int $y* @access public*/function setHistory($history){$this->max_history = $history;}/*** Load a string** @param string $source* @access public*/function loadString($source){$this->setDimensions($this->max_x + 1, $this->max_y + 1);$this->appendString($source);}/*** Appdend a string** @param string $source* @access public*/function appendString($source){$this->tokenization = array('');for ($i = 0; $i < strlen($source); $i++) {if (strlen($this->ansi)) {$this->ansi.= $source[$i];$chr = ord($source[$i]);// http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements// single character CSI's not currently supportedswitch (true) {case $this->ansi == "\x1B=":$this->ansi = '';continue 2;case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['):case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126:break;default:continue 2;}$this->tokenization[] = $this->ansi;$this->tokenization[] = '';// http://ascii-table.com/ansi-escape-sequences-vt-100.phpswitch ($this->ansi) {case "\x1B[H": // Move cursor to upper left corner$this->old_x = $this->x;$this->old_y = $this->y;$this->x = $this->y = 0;break;case "\x1B[J": // Clear screen from cursor down$this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y));$this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, ''));$this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y));$this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row));if (count($this->history) == $this->max_history) {array_shift($this->history);array_shift($this->history_attrs);}case "\x1B[K": // Clear screen from cursor right$this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);array_splice($this->attrs[$this->y], $this->x + 1, $this->max_x - $this->x, array_fill($this->x, $this->max_x - $this->x - 1, $this->base_attr_cell));break;case "\x1B[2K": // Clear entire line$this->screen[$this->y] = str_repeat(' ', $this->x);$this->attrs[$this->y] = $this->attr_row;break;case "\x1B[?1h": // set cursor key to applicationcase "\x1B[?25h": // show the cursorcase "\x1B(B": // set united states g0 character setbreak;case "\x1BE": // Move to next line$this->_newLine();$this->x = 0;break;default:switch (true) {case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines$this->old_y = $this->y;$this->y+= $match[1];break;case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h$this->old_x = $this->x;$this->old_y = $this->y;$this->x = $match[2] - 1;$this->y = $match[1] - 1;break;case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines$this->old_x = $this->x;$this->x+= $match[1];break;case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines$this->old_x = $this->x;$this->x-= $match[1];break;case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a windowbreak;case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes$attr_cell = &$this->attr_cell;$mods = explode(';', $match[1]);foreach ($mods as $mod) {switch ($mod) {case 0: // Turn off character attributes$attr_cell = clone $this->base_attr_cell;break;case 1: // Turn bold mode on$attr_cell->bold = true;break;case 4: // Turn underline mode on$attr_cell->underline = true;break;case 5: // Turn blinking mode on$attr_cell->blink = true;break;case 7: // Turn reverse video on$attr_cell->reverse = !$attr_cell->reverse;$temp = $attr_cell->background;$attr_cell->background = $attr_cell->foreground;$attr_cell->foreground = $temp;break;default: // set colors//$front = $attr_cell->reverse ? &$attr_cell->background : &$attr_cell->foreground;$front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' };//$back = $attr_cell->reverse ? &$attr_cell->foreground : &$attr_cell->background;$back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' };switch ($mod) {// @codingStandardsIgnoreStartcase 30: $front = 'black'; break;case 31: $front = 'red'; break;case 32: $front = 'green'; break;case 33: $front = 'yellow'; break;case 34: $front = 'blue'; break;case 35: $front = 'magenta'; break;case 36: $front = 'cyan'; break;case 37: $front = 'white'; break;case 40: $back = 'black'; break;case 41: $back = 'red'; break;case 42: $back = 'green'; break;case 43: $back = 'yellow'; break;case 44: $back = 'blue'; break;case 45: $back = 'magenta'; break;case 46: $back = 'cyan'; break;case 47: $back = 'white'; break;// @codingStandardsIgnoreEnddefault://user_error('Unsupported attribute: ' . $mod);$this->ansi = '';break 2;}}}break;default://user_error("{$this->ansi} is unsupported\r\n");}}$this->ansi = '';continue;}$this->tokenization[count($this->tokenization) - 1].= $source[$i];switch ($source[$i]) {case "\r":$this->x = 0;break;case "\n":$this->_newLine();break;case "\x08": // backspaceif ($this->x) {$this->x--;$this->attrs[$this->y][$this->x] = clone $this->base_attr_cell;$this->screen[$this->y] = substr_replace($this->screen[$this->y],$source[$i],$this->x,1);}break;case "\x0F": // shiftbreak;case "\x1B": // start ANSI escape code$this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1);//if (!strlen($this->tokenization[count($this->tokenization) - 1])) {// array_pop($this->tokenization);//}$this->ansi.= "\x1B";break;default:$this->attrs[$this->y][$this->x] = clone $this->attr_cell;if ($this->x > strlen($this->screen[$this->y])) {$this->screen[$this->y] = str_repeat(' ', $this->x);}$this->screen[$this->y] = substr_replace($this->screen[$this->y],$source[$i],$this->x,1);if ($this->x > $this->max_x) {$this->x = 0;$this->y++;} else {$this->x++;}}}}/*** Add a new line** Also update the $this->screen and $this->history buffers** @access private*/function _newLine(){//if ($this->y < $this->max_y) {// $this->y++;//}while ($this->y >= $this->max_y) {$this->history = array_merge($this->history, array(array_shift($this->screen)));$this->screen[] = '';$this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs)));$this->attrs[] = $this->attr_row;if (count($this->history) >= $this->max_history) {array_shift($this->history);array_shift($this->history_attrs);}$this->y--;}$this->y++;}/*** Returns the current coordinate without preformating** @access private* @return string*/function _processCoordinate($last_attr, $cur_attr, $char){$output = '';if ($last_attr != $cur_attr) {$close = $open = '';if ($last_attr->foreground != $cur_attr->foreground) {if ($cur_attr->foreground != 'white') {$open.= '<span style="color: ' . $cur_attr->foreground . '">';}if ($last_attr->foreground != 'white') {$close = '</span>' . $close;}}if ($last_attr->background != $cur_attr->background) {if ($cur_attr->background != 'black') {$open.= '<span style="background: ' . $cur_attr->background . '">';}if ($last_attr->background != 'black') {$close = '</span>' . $close;}}if ($last_attr->bold != $cur_attr->bold) {if ($cur_attr->bold) {$open.= '<b>';} else {$close = '</b>' . $close;}}if ($last_attr->underline != $cur_attr->underline) {if ($cur_attr->underline) {$open.= '<u>';} else {$close = '</u>' . $close;}}if ($last_attr->blink != $cur_attr->blink) {if ($cur_attr->blink) {$open.= '<blink>';} else {$close = '</blink>' . $close;}}$output.= $close . $open;}$output.= htmlspecialchars($char);return $output;}/*** Returns the current screen without preformating** @access private* @return string*/function _getScreen(){$output = '';$last_attr = $this->base_attr_cell;for ($i = 0; $i <= $this->max_y; $i++) {for ($j = 0; $j <= $this->max_x; $j++) {$cur_attr = $this->attrs[$i][$j];$output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');$last_attr = $this->attrs[$i][$j];}$output.= "\r\n";}$output = substr($output, 0, -2);// close any remaining open tags$output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, '');return rtrim($output);}/*** Returns the current screen** @access public* @return string*/function getScreen(){return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->_getScreen() . '</pre>';}/*** Returns the current screen and the x previous lines** @access public* @return string*/function getHistory(){$scrollback = '';$last_attr = $this->base_attr_cell;for ($i = 0; $i < count($this->history); $i++) {for ($j = 0; $j <= $this->max_x + 1; $j++) {$cur_attr = $this->history_attrs[$i][$j];$scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');$last_attr = $this->history_attrs[$i][$j];}$scrollback.= "\r\n";}$base_attr_cell = $this->base_attr_cell;$this->base_attr_cell = $last_attr;$scrollback.= $this->_getScreen();$this->base_attr_cell = $base_attr_cell;return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $scrollback . '</span></pre>';}}