AnsiToHtmlConverter.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. <?php
  2. /**
  3. * Created by IntelliJ IDEA.
  4. * User: philippe-adrien
  5. * Date: 2019-01-19
  6. * Time: 12:27
  7. */
  8. namespace OCA\TestNextcloudApp\Controller;
  9. /**
  10. * Converts an ANSI text to HTML5.
  11. */
  12. class AnsiToHtmlConverter
  13. {
  14. protected $charset;
  15. protected $inlineStyles;
  16. protected $inlineColors;
  17. protected $colorNames;
  18. public function __construct($inlineStyles = true, $charset = 'UTF-8')
  19. {
  20. $this->inlineStyles = $inlineStyles;
  21. $this->charset = $charset;
  22. $this->colorNames = array(
  23. 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white',
  24. '', '',
  25. 'brblack', 'brred', 'brgreen', 'bryellow', 'brblue', 'brmagenta', 'brcyan', 'brwhite',
  26. );
  27. }
  28. public function convert($text)
  29. {
  30. // remove cursor movement sequences
  31. $text = preg_replace('#\e\[(K|s|u|2J|2K|\d+(A|B|C|D|E|F|G|J|K|S|T)|\d+;\d+(H|f))#', '', $text);
  32. // remove character set sequences
  33. $text = preg_replace('#\e(\(|\))(A|B|[0-2])#', '', $text);
  34. $text = htmlspecialchars($text, PHP_VERSION_ID >= 50400 ? ENT_QUOTES | ENT_SUBSTITUTE : ENT_QUOTES, $this->charset);
  35. // carriage return
  36. $text = preg_replace('#^.*\r(?!\n)#m', '', $text);
  37. $tokens = $this->tokenize($text);
  38. // a backspace remove the previous character but only from a text token
  39. foreach ($tokens as $i => $token) {
  40. if ('backspace' == $token[0]) {
  41. $j = $i;
  42. while (--$j >= 0) {
  43. if ('text' == $tokens[$j][0] && strlen($tokens[$j][1]) > 0) {
  44. $tokens[$j][1] = substr($tokens[$j][1], 0, -1);
  45. break;
  46. }
  47. }
  48. }
  49. }
  50. $html = '';
  51. foreach ($tokens as $token) {
  52. if ('text' == $token[0]) {
  53. $html .= $token[1];
  54. } elseif ('color' == $token[0]) {
  55. $html .= $this->convertAnsiToColor($token[1]);
  56. }
  57. }
  58. if ($this->inlineStyles) {
  59. $html = sprintf('<span style="background-color: %s; color: %s">%s</span>', $this->inlineColors['black'], $this->inlineColors['white'], $html);
  60. } else {
  61. $html = sprintf('<span class="ansi_color_bg_black ansi_color_fg_white">%s</span>', $html);
  62. }
  63. // remove empty span
  64. $html = preg_replace('#<span[^>]*></span>#', '', $html);
  65. return $html;
  66. }
  67. protected function convertAnsiToColor($ansi)
  68. {
  69. $bg = 0;
  70. $fg = 7;
  71. $as = '';
  72. if ('0' != $ansi && '' != $ansi) {
  73. $options = explode(';', $ansi);
  74. foreach ($options as $option) {
  75. if ($option >= 30 && $option < 38) {
  76. $fg = $option - 30;
  77. } elseif ($option >= 40 && $option < 48) {
  78. $bg = $option - 40;
  79. } elseif (39 == $option) {
  80. $fg = 7;
  81. } elseif (49 == $option) {
  82. $bg = 0;
  83. }
  84. }
  85. // options: bold => 1, underscore => 4, blink => 5, reverse => 7, conceal => 8
  86. if (in_array(1, $options)) {
  87. $fg += 10;
  88. $bg += 10;
  89. }
  90. if (in_array(4, $options)) {
  91. $as = '; text-decoration: underline';
  92. }
  93. if (in_array(7, $options)) {
  94. $tmp = $fg;
  95. $fg = $bg;
  96. $bg = $tmp;
  97. }
  98. }
  99. if ($this->inlineStyles) {
  100. return sprintf('</span><span style="background-color: %s; color: %s%s">', $this->inlineColors[$this->colorNames[$bg]], $this->inlineColors[$this->colorNames[$fg]], $as);
  101. } else {
  102. return sprintf('</span><span class="ansi_color_bg_%s ansi_color_fg_%s">', $this->colorNames[$bg], $this->colorNames[$fg]);
  103. }
  104. }
  105. protected function tokenize($text)
  106. {
  107. $tokens = array();
  108. preg_match_all("/(?:\e\[(.*?)m|(\x08))/", $text, $matches, PREG_OFFSET_CAPTURE);
  109. $offset = 0;
  110. foreach ($matches[0] as $i => $match) {
  111. if ($match[1] - $offset > 0) {
  112. $tokens[] = array('text', substr($text, $offset, $match[1] - $offset));
  113. }
  114. $tokens[] = array("\x08" == $match[0] ? 'backspace' : 'color', $matches[1][$i][0]);
  115. $offset = $match[1] + strlen($match[0]);
  116. }
  117. if ($offset < strlen($text)) {
  118. $tokens[] = array('text', substr($text, $offset));
  119. }
  120. return $tokens;
  121. }
  122. }