ANSI.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <?php
  2. /**
  3. * Pure-PHP ANSI Decoder
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * If you call read() in Net_SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.
  8. * They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC). They tell a
  9. * {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what
  10. * color to display them in, etc. File_ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.
  11. *
  12. * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
  13. * of this software and associated documentation files (the "Software"), to deal
  14. * in the Software without restriction, including without limitation the rights
  15. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  16. * copies of the Software, and to permit persons to whom the Software is
  17. * furnished to do so, subject to the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be included in
  20. * all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  23. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  24. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  25. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  26. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  27. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  28. * THE SOFTWARE.
  29. *
  30. * @category File
  31. * @package File_ANSI
  32. * @author Jim Wigginton <terrafrost@php.net>
  33. * @copyright 2012 Jim Wigginton
  34. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  35. * @link http://phpseclib.sourceforge.net
  36. */
  37. /**
  38. * Pure-PHP ANSI Decoder
  39. *
  40. * @package File_ANSI
  41. * @author Jim Wigginton <terrafrost@php.net>
  42. * @access public
  43. */
  44. class File_ANSI
  45. {
  46. /**
  47. * Max Width
  48. *
  49. * @var int
  50. * @access private
  51. */
  52. var $max_x;
  53. /**
  54. * Max Height
  55. *
  56. * @var int
  57. * @access private
  58. */
  59. var $max_y;
  60. /**
  61. * Max History
  62. *
  63. * @var int
  64. * @access private
  65. */
  66. var $max_history;
  67. /**
  68. * History
  69. *
  70. * @var array
  71. * @access private
  72. */
  73. var $history;
  74. /**
  75. * History Attributes
  76. *
  77. * @var array
  78. * @access private
  79. */
  80. var $history_attrs;
  81. /**
  82. * Current Column
  83. *
  84. * @var int
  85. * @access private
  86. */
  87. var $x;
  88. /**
  89. * Current Row
  90. *
  91. * @var int
  92. * @access private
  93. */
  94. var $y;
  95. /**
  96. * Old Column
  97. *
  98. * @var int
  99. * @access private
  100. */
  101. var $old_x;
  102. /**
  103. * Old Row
  104. *
  105. * @var int
  106. * @access private
  107. */
  108. var $old_y;
  109. /**
  110. * An empty attribute cell
  111. *
  112. * @var object
  113. * @access private
  114. */
  115. var $base_attr_cell;
  116. /**
  117. * The current attribute cell
  118. *
  119. * @var object
  120. * @access private
  121. */
  122. var $attr_cell;
  123. /**
  124. * An empty attribute row
  125. *
  126. * @var array
  127. * @access private
  128. */
  129. var $attr_row;
  130. /**
  131. * The current screen text
  132. *
  133. * @var array
  134. * @access private
  135. */
  136. var $screen;
  137. /**
  138. * The current screen attributes
  139. *
  140. * @var array
  141. * @access private
  142. */
  143. var $attrs;
  144. /**
  145. * Current ANSI code
  146. *
  147. * @var string
  148. * @access private
  149. */
  150. var $ansi;
  151. /**
  152. * Tokenization
  153. *
  154. * @var array
  155. * @access private
  156. */
  157. var $tokenization;
  158. /**
  159. * Default Constructor.
  160. *
  161. * @return File_ANSI
  162. * @access public
  163. */
  164. function __construct()
  165. {
  166. $attr_cell = new stdClass();
  167. $attr_cell->bold = false;
  168. $attr_cell->underline = false;
  169. $attr_cell->blink = false;
  170. $attr_cell->background = 'black';
  171. $attr_cell->foreground = 'white';
  172. $attr_cell->reverse = false;
  173. $this->base_attr_cell = clone($attr_cell);
  174. $this->attr_cell = clone($attr_cell);
  175. $this->setHistory(200);
  176. $this->setDimensions(80, 24);
  177. }
  178. /**
  179. * PHP4 compatible Default Constructor.
  180. *
  181. * @see self::__construct()
  182. * @access public
  183. */
  184. function File_ANSI()
  185. {
  186. $this->__construct($mode);
  187. }
  188. /**
  189. * Set terminal width and height
  190. *
  191. * Resets the screen as well
  192. *
  193. * @param int $x
  194. * @param int $y
  195. * @access public
  196. */
  197. function setDimensions($x, $y)
  198. {
  199. $this->max_x = $x - 1;
  200. $this->max_y = $y - 1;
  201. $this->x = $this->y = 0;
  202. $this->history = $this->history_attrs = array();
  203. $this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);
  204. $this->screen = array_fill(0, $this->max_y + 1, '');
  205. $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
  206. $this->ansi = '';
  207. }
  208. /**
  209. * Set the number of lines that should be logged past the terminal height
  210. *
  211. * @param int $x
  212. * @param int $y
  213. * @access public
  214. */
  215. function setHistory($history)
  216. {
  217. $this->max_history = $history;
  218. }
  219. /**
  220. * Load a string
  221. *
  222. * @param string $source
  223. * @access public
  224. */
  225. function loadString($source)
  226. {
  227. $this->setDimensions($this->max_x + 1, $this->max_y + 1);
  228. $this->appendString($source);
  229. }
  230. /**
  231. * Appdend a string
  232. *
  233. * @param string $source
  234. * @access public
  235. */
  236. function appendString($source)
  237. {
  238. $this->tokenization = array('');
  239. for ($i = 0; $i < strlen($source); $i++) {
  240. if (strlen($this->ansi)) {
  241. $this->ansi.= $source[$i];
  242. $chr = ord($source[$i]);
  243. // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
  244. // single character CSI's not currently supported
  245. switch (true) {
  246. case $this->ansi == "\x1B=":
  247. $this->ansi = '';
  248. continue 2;
  249. case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['):
  250. case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126:
  251. break;
  252. default:
  253. continue 2;
  254. }
  255. $this->tokenization[] = $this->ansi;
  256. $this->tokenization[] = '';
  257. // http://ascii-table.com/ansi-escape-sequences-vt-100.php
  258. switch ($this->ansi) {
  259. case "\x1B[H": // Move cursor to upper left corner
  260. $this->old_x = $this->x;
  261. $this->old_y = $this->y;
  262. $this->x = $this->y = 0;
  263. break;
  264. case "\x1B[J": // Clear screen from cursor down
  265. $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y));
  266. $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, ''));
  267. $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y));
  268. $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row));
  269. if (count($this->history) == $this->max_history) {
  270. array_shift($this->history);
  271. array_shift($this->history_attrs);
  272. }
  273. case "\x1B[K": // Clear screen from cursor right
  274. $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
  275. 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));
  276. break;
  277. case "\x1B[2K": // Clear entire line
  278. $this->screen[$this->y] = str_repeat(' ', $this->x);
  279. $this->attrs[$this->y] = $this->attr_row;
  280. break;
  281. case "\x1B[?1h": // set cursor key to application
  282. case "\x1B[?25h": // show the cursor
  283. case "\x1B(B": // set united states g0 character set
  284. break;
  285. case "\x1BE": // Move to next line
  286. $this->_newLine();
  287. $this->x = 0;
  288. break;
  289. default:
  290. switch (true) {
  291. case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines
  292. $this->old_y = $this->y;
  293. $this->y+= $match[1];
  294. break;
  295. case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
  296. $this->old_x = $this->x;
  297. $this->old_y = $this->y;
  298. $this->x = $match[2] - 1;
  299. $this->y = $match[1] - 1;
  300. break;
  301. case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
  302. $this->old_x = $this->x;
  303. $this->x+= $match[1];
  304. break;
  305. case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines
  306. $this->old_x = $this->x;
  307. $this->x-= $match[1];
  308. if ($this->x < 0) {
  309. $this->x = 0;
  310. }
  311. break;
  312. case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
  313. break;
  314. case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
  315. $attr_cell = &$this->attr_cell;
  316. $mods = explode(';', $match[1]);
  317. foreach ($mods as $mod) {
  318. switch ($mod) {
  319. case 0: // Turn off character attributes
  320. $attr_cell = clone($this->base_attr_cell);
  321. break;
  322. case 1: // Turn bold mode on
  323. $attr_cell->bold = true;
  324. break;
  325. case 4: // Turn underline mode on
  326. $attr_cell->underline = true;
  327. break;
  328. case 5: // Turn blinking mode on
  329. $attr_cell->blink = true;
  330. break;
  331. case 7: // Turn reverse video on
  332. $attr_cell->reverse = !$attr_cell->reverse;
  333. $temp = $attr_cell->background;
  334. $attr_cell->background = $attr_cell->foreground;
  335. $attr_cell->foreground = $temp;
  336. break;
  337. default: // set colors
  338. //$front = $attr_cell->reverse ? &$attr_cell->background : &$attr_cell->foreground;
  339. $front = &$attr_cell->{ $attr_cell->reverse ? 'background' : 'foreground' };
  340. //$back = $attr_cell->reverse ? &$attr_cell->foreground : &$attr_cell->background;
  341. $back = &$attr_cell->{ $attr_cell->reverse ? 'foreground' : 'background' };
  342. switch ($mod) {
  343. // @codingStandardsIgnoreStart
  344. case 30: $front = 'black'; break;
  345. case 31: $front = 'red'; break;
  346. case 32: $front = 'green'; break;
  347. case 33: $front = 'yellow'; break;
  348. case 34: $front = 'blue'; break;
  349. case 35: $front = 'magenta'; break;
  350. case 36: $front = 'cyan'; break;
  351. case 37: $front = 'white'; break;
  352. case 40: $back = 'black'; break;
  353. case 41: $back = 'red'; break;
  354. case 42: $back = 'green'; break;
  355. case 43: $back = 'yellow'; break;
  356. case 44: $back = 'blue'; break;
  357. case 45: $back = 'magenta'; break;
  358. case 46: $back = 'cyan'; break;
  359. case 47: $back = 'white'; break;
  360. // @codingStandardsIgnoreEnd
  361. default:
  362. //user_error('Unsupported attribute: ' . $mod);
  363. $this->ansi = '';
  364. break 2;
  365. }
  366. }
  367. }
  368. break;
  369. default:
  370. //user_error("{$this->ansi} is unsupported\r\n");
  371. }
  372. }
  373. $this->ansi = '';
  374. continue;
  375. }
  376. $this->tokenization[count($this->tokenization) - 1].= $source[$i];
  377. switch ($source[$i]) {
  378. case "\r":
  379. $this->x = 0;
  380. break;
  381. case "\n":
  382. $this->_newLine();
  383. break;
  384. case "\x08": // backspace
  385. if ($this->x) {
  386. $this->x--;
  387. $this->attrs[$this->y][$this->x] = clone($this->base_attr_cell);
  388. $this->screen[$this->y] = substr_replace(
  389. $this->screen[$this->y],
  390. $source[$i],
  391. $this->x,
  392. 1
  393. );
  394. }
  395. break;
  396. case "\x0F": // shift
  397. break;
  398. case "\x1B": // start ANSI escape code
  399. $this->tokenization[count($this->tokenization) - 1] = substr($this->tokenization[count($this->tokenization) - 1], 0, -1);
  400. //if (!strlen($this->tokenization[count($this->tokenization) - 1])) {
  401. // array_pop($this->tokenization);
  402. //}
  403. $this->ansi.= "\x1B";
  404. break;
  405. default:
  406. $this->attrs[$this->y][$this->x] = clone($this->attr_cell);
  407. if ($this->x > strlen($this->screen[$this->y])) {
  408. $this->screen[$this->y] = str_repeat(' ', $this->x);
  409. }
  410. $this->screen[$this->y] = substr_replace(
  411. $this->screen[$this->y],
  412. $source[$i],
  413. $this->x,
  414. 1
  415. );
  416. if ($this->x > $this->max_x) {
  417. $this->x = 0;
  418. $this->_newLine();
  419. } else {
  420. $this->x++;
  421. }
  422. }
  423. }
  424. }
  425. /**
  426. * Add a new line
  427. *
  428. * Also update the $this->screen and $this->history buffers
  429. *
  430. * @access private
  431. */
  432. function _newLine()
  433. {
  434. //if ($this->y < $this->max_y) {
  435. // $this->y++;
  436. //}
  437. while ($this->y >= $this->max_y) {
  438. $this->history = array_merge($this->history, array(array_shift($this->screen)));
  439. $this->screen[] = '';
  440. $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs)));
  441. $this->attrs[] = $this->attr_row;
  442. if (count($this->history) >= $this->max_history) {
  443. array_shift($this->history);
  444. array_shift($this->history_attrs);
  445. }
  446. $this->y--;
  447. }
  448. $this->y++;
  449. }
  450. /**
  451. * Returns the current coordinate without preformating
  452. *
  453. * @access private
  454. * @return string
  455. */
  456. function _processCoordinate($last_attr, $cur_attr, $char)
  457. {
  458. $output = '';
  459. if ($last_attr != $cur_attr) {
  460. $close = $open = '';
  461. if ($last_attr->foreground != $cur_attr->foreground) {
  462. if ($cur_attr->foreground != 'white') {
  463. $open.= '<span style="color: ' . $cur_attr->foreground . '">';
  464. }
  465. if ($last_attr->foreground != 'white') {
  466. $close = '</span>' . $close;
  467. }
  468. }
  469. if ($last_attr->background != $cur_attr->background) {
  470. if ($cur_attr->background != 'black') {
  471. $open.= '<span style="background: ' . $cur_attr->background . '">';
  472. }
  473. if ($last_attr->background != 'black') {
  474. $close = '</span>' . $close;
  475. }
  476. }
  477. if ($last_attr->bold != $cur_attr->bold) {
  478. if ($cur_attr->bold) {
  479. $open.= '<b>';
  480. } else {
  481. $close = '</b>' . $close;
  482. }
  483. }
  484. if ($last_attr->underline != $cur_attr->underline) {
  485. if ($cur_attr->underline) {
  486. $open.= '<u>';
  487. } else {
  488. $close = '</u>' . $close;
  489. }
  490. }
  491. if ($last_attr->blink != $cur_attr->blink) {
  492. if ($cur_attr->blink) {
  493. $open.= '<blink>';
  494. } else {
  495. $close = '</blink>' . $close;
  496. }
  497. }
  498. $output.= $close . $open;
  499. }
  500. $output.= htmlspecialchars($char);
  501. return $output;
  502. }
  503. /**
  504. * Returns the current screen without preformating
  505. *
  506. * @access private
  507. * @return string
  508. */
  509. function _getScreen()
  510. {
  511. $output = '';
  512. $last_attr = $this->base_attr_cell;
  513. for ($i = 0; $i <= $this->max_y; $i++) {
  514. for ($j = 0; $j <= $this->max_x; $j++) {
  515. $cur_attr = $this->attrs[$i][$j];
  516. $output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
  517. $last_attr = $this->attrs[$i][$j];
  518. }
  519. $output.= "\r\n";
  520. }
  521. $output = substr($output, 0, -2);
  522. // close any remaining open tags
  523. $output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, '');
  524. return rtrim($output);
  525. }
  526. /**
  527. * Returns the current screen
  528. *
  529. * @access public
  530. * @return string
  531. */
  532. function getScreen()
  533. {
  534. return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->_getScreen() . '</pre>';
  535. }
  536. /**
  537. * Returns the current screen and the x previous lines
  538. *
  539. * @access public
  540. * @return string
  541. */
  542. function getHistory()
  543. {
  544. $scrollback = '';
  545. $last_attr = $this->base_attr_cell;
  546. for ($i = 0; $i < count($this->history); $i++) {
  547. for ($j = 0; $j <= $this->max_x + 1; $j++) {
  548. $cur_attr = $this->history_attrs[$i][$j];
  549. $scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
  550. $last_attr = $this->history_attrs[$i][$j];
  551. }
  552. $scrollback.= "\r\n";
  553. }
  554. $base_attr_cell = $this->base_attr_cell;
  555. $this->base_attr_cell = $last_attr;
  556. $scrollback.= $this->_getScreen();
  557. $this->base_attr_cell = $base_attr_cell;
  558. return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $scrollback . '</span></pre>';
  559. }
  560. }