ANSI.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  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 Integer
  50. * @access private
  51. */
  52. var $max_x;
  53. /**
  54. * Max Height
  55. *
  56. * @var Integer
  57. * @access private
  58. */
  59. var $max_y;
  60. /**
  61. * Max History
  62. *
  63. * @var Integer
  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 Integer
  85. * @access private
  86. */
  87. var $x;
  88. /**
  89. * Current Row
  90. *
  91. * @var Integer
  92. * @access private
  93. */
  94. var $y;
  95. /**
  96. * Old Column
  97. *
  98. * @var Integer
  99. * @access private
  100. */
  101. var $old_x;
  102. /**
  103. * Old Row
  104. *
  105. * @var Integer
  106. * @access private
  107. */
  108. var $old_y;
  109. /**
  110. * An empty attribute row
  111. *
  112. * @var Array
  113. * @access private
  114. */
  115. var $attr_row;
  116. /**
  117. * The current screen text
  118. *
  119. * @var Array
  120. * @access private
  121. */
  122. var $screen;
  123. /**
  124. * The current screen attributes
  125. *
  126. * @var Array
  127. * @access private
  128. */
  129. var $attrs;
  130. /**
  131. * The current foreground color
  132. *
  133. * @var String
  134. * @access private
  135. */
  136. var $foreground;
  137. /**
  138. * The current background color
  139. *
  140. * @var String
  141. * @access private
  142. */
  143. var $background;
  144. /**
  145. * Bold flag
  146. *
  147. * @var Boolean
  148. * @access private
  149. */
  150. var $bold;
  151. /**
  152. * Underline flag
  153. *
  154. * @var Boolean
  155. * @access private
  156. */
  157. var $underline;
  158. /**
  159. * Blink flag
  160. *
  161. * @var Boolean
  162. * @access private
  163. */
  164. var $blink;
  165. /**
  166. * Reverse flag
  167. *
  168. * @var Boolean
  169. * @access private
  170. */
  171. var $reverse;
  172. /**
  173. * Color flag
  174. *
  175. * @var Boolean
  176. * @access private
  177. */
  178. var $color;
  179. /**
  180. * Current ANSI code
  181. *
  182. * @var String
  183. * @access private
  184. */
  185. var $ansi;
  186. /**
  187. * Default Constructor.
  188. *
  189. * @return File_ANSI
  190. * @access public
  191. */
  192. function File_ANSI()
  193. {
  194. $this->setHistory(200);
  195. $this->setDimensions(80, 24);
  196. }
  197. /**
  198. * Set terminal width and height
  199. *
  200. * Resets the screen as well
  201. *
  202. * @param Integer $x
  203. * @param Integer $y
  204. * @access public
  205. */
  206. function setDimensions($x, $y)
  207. {
  208. $this->max_x = $x - 1;
  209. $this->max_y = $y - 1;
  210. $this->x = $this->y = 0;
  211. $this->history = $this->history_attrs = array();
  212. $this->attr_row = array_fill(0, $this->max_x + 1, '');
  213. $this->screen = array_fill(0, $this->max_y + 1, '');
  214. $this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
  215. $this->foreground = 'white';
  216. $this->background = 'black';
  217. $this->bold = false;
  218. $this->underline = false;
  219. $this->blink = false;
  220. $this->reverse = false;
  221. $this->color = false;
  222. $this->ansi = '';
  223. }
  224. /**
  225. * Set the number of lines that should be logged past the terminal height
  226. *
  227. * @param Integer $x
  228. * @param Integer $y
  229. * @access public
  230. */
  231. function setHistory($history)
  232. {
  233. $this->max_history = $history;
  234. }
  235. /**
  236. * Load a string
  237. *
  238. * @param String $source
  239. * @access public
  240. */
  241. function loadString($source)
  242. {
  243. $this->setDimensions($this->max_x + 1, $this->max_y + 1);
  244. $this->appendString($source);
  245. }
  246. /**
  247. * Appdend a string
  248. *
  249. * @param String $source
  250. * @access public
  251. */
  252. function appendString($source)
  253. {
  254. for ($i = 0; $i < strlen($source); $i++) {
  255. if (strlen($this->ansi)) {
  256. $this->ansi.= $source[$i];
  257. $chr = ord($source[$i]);
  258. // http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
  259. // single character CSI's not currently supported
  260. switch (true) {
  261. case $this->ansi == "\x1B=":
  262. $this->ansi = '';
  263. continue 2;
  264. case strlen($this->ansi) == 2 && $chr >= 64 && $chr <= 95 && $chr != ord('['):
  265. case strlen($this->ansi) > 2 && $chr >= 64 && $chr <= 126:
  266. break;
  267. default:
  268. continue 2;
  269. }
  270. // http://ascii-table.com/ansi-escape-sequences-vt-100.php
  271. switch ($this->ansi) {
  272. case "\x1B[H": // Move cursor to upper left corner
  273. $this->old_x = $this->x;
  274. $this->old_y = $this->y;
  275. $this->x = $this->y = 0;
  276. break;
  277. case "\x1B[J": // Clear screen from cursor down
  278. $this->history = array_merge($this->history, array_slice(array_splice($this->screen, $this->y + 1), 0, $this->old_y));
  279. $this->screen = array_merge($this->screen, array_fill($this->y, $this->max_y, ''));
  280. $this->history_attrs = array_merge($this->history_attrs, array_slice(array_splice($this->attrs, $this->y + 1), 0, $this->old_y));
  281. $this->attrs = array_merge($this->attrs, array_fill($this->y, $this->max_y, $this->attr_row));
  282. if (count($this->history) == $this->max_history) {
  283. array_shift($this->history);
  284. array_shift($this->history_attrs);
  285. }
  286. case "\x1B[K": // Clear screen from cursor right
  287. $this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
  288. array_splice($this->attrs[$this->y], $this->x + 1);
  289. break;
  290. case "\x1B[2K": // Clear entire line
  291. $this->screen[$this->y] = str_repeat(' ', $this->x);
  292. $this->attrs[$this->y] = $this->attr_row;
  293. break;
  294. case "\x1B[?1h": // set cursor key to application
  295. case "\x1B[?25h": // show the cursor
  296. break;
  297. case "\x1BE": // Move to next line
  298. $this->_newLine();
  299. $this->x = 0;
  300. break;
  301. default:
  302. switch (true) {
  303. case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
  304. $this->old_x = $this->x;
  305. $this->old_y = $this->y;
  306. $this->x = $match[2] - 1;
  307. $this->y = $match[1] - 1;
  308. break;
  309. case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
  310. $this->old_x = $this->x;
  311. $x = $match[1] - 1;
  312. break;
  313. case preg_match('#\x1B\[(\d+);(\d+)r#', $this->ansi, $match): // Set top and bottom lines of a window
  314. break;
  315. case preg_match('#\x1B\[(\d*(?:;\d*)*)m#', $this->ansi, $match): // character attributes
  316. $mods = explode(';', $match[1]);
  317. foreach ($mods as $mod) {
  318. switch ($mod) {
  319. case 0: // Turn off character attributes
  320. $this->attrs[$this->y][$this->x] = '';
  321. if ($this->bold) $this->attrs[$this->y][$this->x].= '</b>';
  322. if ($this->underline) $this->attrs[$this->y][$this->x].= '</u>';
  323. if ($this->blink) $this->attrs[$this->y][$this->x].= '</blink>';
  324. if ($this->color) $this->attrs[$this->y][$this->x].= '</span>';
  325. if ($this->reverse) {
  326. $temp = $this->background;
  327. $this->background = $this->foreground;
  328. $this->foreground = $temp;
  329. }
  330. $this->bold = $this->underline = $this->blink = $this->color = $this->reverse = false;
  331. break;
  332. case 1: // Turn bold mode on
  333. if (!$this->bold) {
  334. $this->attrs[$this->y][$this->x] = '<b>';
  335. $this->bold = true;
  336. }
  337. break;
  338. case 4: // Turn underline mode on
  339. if (!$this->underline) {
  340. $this->attrs[$this->y][$this->x] = '<u>';
  341. $this->underline = true;
  342. }
  343. break;
  344. case 5: // Turn blinking mode on
  345. if (!$this->blink) {
  346. $this->attrs[$this->y][$this->x] = '<blink>';
  347. $this->blink = true;
  348. }
  349. break;
  350. case 7: // Turn reverse video on
  351. $this->reverse = !$this->reverse;
  352. $temp = $this->background;
  353. $this->background = $this->foreground;
  354. $this->foreground = $temp;
  355. $this->attrs[$this->y][$this->x] = '<span style="color: ' . $this->foreground . '; background: ' . $this->background . '">';
  356. if ($this->color) {
  357. $this->attrs[$this->y][$this->x] = '</span>' . $this->attrs[$this->y][$this->x];
  358. }
  359. $this->color = true;
  360. break;
  361. default: // set colors
  362. //$front = $this->reverse ? &$this->background : &$this->foreground;
  363. $front = &$this->{ $this->reverse ? 'background' : 'foreground' };
  364. //$back = $this->reverse ? &$this->foreground : &$this->background;
  365. $back = &$this->{ $this->reverse ? 'foreground' : 'background' };
  366. switch ($mod) {
  367. case 30: $front = 'black'; break;
  368. case 31: $front = 'red'; break;
  369. case 32: $front = 'green'; break;
  370. case 33: $front = 'yellow'; break;
  371. case 34: $front = 'blue'; break;
  372. case 35: $front = 'magenta'; break;
  373. case 36: $front = 'cyan'; break;
  374. case 37: $front = 'white'; break;
  375. case 40: $back = 'black'; break;
  376. case 41: $back = 'red'; break;
  377. case 42: $back = 'green'; break;
  378. case 43: $back = 'yellow'; break;
  379. case 44: $back = 'blue'; break;
  380. case 45: $back = 'magenta'; break;
  381. case 46: $back = 'cyan'; break;
  382. case 47: $back = 'white'; break;
  383. default:
  384. user_error('Unsupported attribute: ' . $mod);
  385. $this->ansi = '';
  386. break 2;
  387. }
  388. unset($temp);
  389. $this->attrs[$this->y][$this->x] = '<span style="color: ' . $this->foreground . '; background: ' . $this->background . '">';
  390. if ($this->color) {
  391. $this->attrs[$this->y][$this->x] = '</span>' . $this->attrs[$this->y][$this->x];
  392. }
  393. $this->color = true;
  394. }
  395. }
  396. break;
  397. default:
  398. user_error("{$this->ansi} unsupported\r\n");
  399. }
  400. }
  401. $this->ansi = '';
  402. continue;
  403. }
  404. switch ($source[$i]) {
  405. case "\r":
  406. $this->x = 0;
  407. break;
  408. case "\n":
  409. $this->_newLine();
  410. break;
  411. case "\x0F": // shift
  412. break;
  413. case "\x1B": // start ANSI escape code
  414. $this->ansi.= "\x1B";
  415. break;
  416. default:
  417. $this->screen[$this->y] = substr_replace(
  418. $this->screen[$this->y],
  419. $source[$i],
  420. $this->x,
  421. 1
  422. );
  423. if ($this->x > $this->max_x) {
  424. $this->x = 0;
  425. $this->y++;
  426. } else {
  427. $this->x++;
  428. }
  429. }
  430. }
  431. }
  432. /**
  433. * Add a new line
  434. *
  435. * Also update the $this->screen and $this->history buffers
  436. *
  437. * @access private
  438. */
  439. function _newLine()
  440. {
  441. //if ($this->y < $this->max_y) {
  442. // $this->y++;
  443. //}
  444. while ($this->y >= $this->max_y) {
  445. $this->history = array_merge($this->history, array(array_shift($this->screen)));
  446. $this->screen[] = '';
  447. $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs)));
  448. $this->attrs[] = $this->attr_row;
  449. if (count($this->history) >= $this->max_history) {
  450. array_shift($this->history);
  451. array_shift($this->history_attrs);
  452. }
  453. $this->y--;
  454. }
  455. $this->y++;
  456. }
  457. /**
  458. * Returns the current screen without preformating
  459. *
  460. * @access private
  461. * @return String
  462. */
  463. function _getScreen()
  464. {
  465. $output = '';
  466. for ($i = 0; $i <= $this->max_y; $i++) {
  467. for ($j = 0; $j <= $this->max_x + 1; $j++) {
  468. if (isset($this->attrs[$i][$j])) {
  469. $output.= $this->attrs[$i][$j];
  470. }
  471. if (isset($this->screen[$i][$j])) {
  472. $output.= htmlspecialchars($this->screen[$i][$j]);
  473. }
  474. }
  475. $output.= "\r\n";
  476. }
  477. return rtrim($output);
  478. }
  479. /**
  480. * Returns the current screen
  481. *
  482. * @access public
  483. * @return String
  484. */
  485. function getScreen()
  486. {
  487. return '<pre style="color: white; background: black" width="' . ($this->max_x + 1) . '">' . $this->_getScreen() . '</pre>';
  488. }
  489. /**
  490. * Returns the current screen and the x previous lines
  491. *
  492. * @access public
  493. * @return String
  494. */
  495. function getHistory()
  496. {
  497. $scrollback = '';
  498. for ($i = 0; $i < count($this->history); $i++) {
  499. for ($j = 0; $j <= $this->max_x + 1; $j++) {
  500. if (isset($this->history_attrs[$i][$j])) {
  501. $scrollback.= $this->history_attrs[$i][$j];
  502. }
  503. if (isset($this->history[$i][$j])) {
  504. $scrollback.= htmlspecialchars($this->history[$i][$j]);
  505. }
  506. }
  507. $scrollback.= "\r\n";
  508. }
  509. $scrollback.= $this->_getScreen();
  510. return '<pre style="color: white; background: black" width="' . ($this->max_x + 1) . '">' . $scrollback . '</pre>';
  511. }
  512. }