class ANSI_SGR_To_HTML
ANSI SGR code to HTML converter¶ ↑
This gem converts text with ANSI SGR codes to HTML. SGR? Yes, fonts, bold, colors, etc. are ANSI SGR sequences, other ANSI escape codes are not supported. In other words, the only codes that actually make sense to convert are supported, as moving the cursor would not be convertible.
Overview¶ ↑
-
supports all standardized SGR codes except ideograms or codes 50-59
-
this means it supports alternative fonts (user specified)
-
blink codes supported! Slow and fast!
-
full support for all SGR color codes, meaning 4bit, 8bit and 24bit colors, for both, foreground and background (most other gems do not, especially not 24bit colors)
-
supports SGR codes 7 and 27, for reversing back- and foreground colors (however, does only work when having explicitly set using SGR color code, before)
-
-
supports sequences of SGR codes separated by ‘;’ (some other gems do not)
-
unicode strings supported
-
configurable options by user
-
custom set all 10 alternative fonts (default: all inherit default font)
-
is bold only bold, or also bright (default: both)
-
fully user defineable basic 16 color tables, or pick from preset (default: xterm)
-
-
flat output, no nested spans like some other gems (smaller output than most other gems)
Attributes
Public Class Methods
Create new instance of converter
Example¶ ↑
>> ANSI_SGR_To_HTML.new(false, :osx_term, ['serif','Courier New']+[nil]*7+['Fraktur'], true, true)
Arguments¶ ↑
- k_turn_eol_into_br
-
pass true if converter should turn newlines into <br/> on the fly (redundant if output is wrapped in <pre>)
- col_tab_16
-
specify either array with 16 css colors (first 8 are low, last 8 are bright colors), or preset name (:xterm, :osx_term, :cmd, :vga)
- font_styles
-
Array with ten font-names (or nil, to use default), to be used for SGR codes 11-20
- k_1_is_bold_and_bright
-
true renders bold text (SGR code 1) also in bright colors (if color is from basic 16-color table)
- k_21_is_double_underline
-
specify, whether SGR code 21 disables bold text, or enable double-underline
# File lib/ansi_sgr_to_html.rb, line 74 def initialize(k_turn_eol_into_br = false, col_tab_16 = :xterm, font_styles = [nil]*10, k_1_is_bold_and_bright = true, k_21_is_double_underline = true) @k_21_is_double_underline = k_21_is_double_underline; @k_1_is_bold_and_bright = k_1_is_bold_and_bright; @k_turn_eol_into_br = k_turn_eol_into_br; col_tab_16 = @@Col_tabs_16[col_tab_16] if(col_tab_16.class == Symbol) @locol = col_tab_16[0,8] @hicol = col_tab_16[8,8] @curcol = @locol @font_styles = font_styles @html_escapes = @@HTML_escapes.clone @html_escapes["\r"], @html_escapes["\n"] = '', '<br/>' if(@k_turn_eol_into_br) @prv_css = {} @cur_css = {} @inv = false # Inverted mode on? @putblnk = false # Wrote blink tag keyframes? Guard to write them only once, when needed. @fsm_stack = [] @fsm_top = 0 class << self attr_accessor :_ansi_sgr_to_html_actions private :_ansi_sgr_to_html_actions, :_ansi_sgr_to_html_actions= end self._ansi_sgr_to_html_actions = [ 0, 1, 0, 1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, 11, 1, 12, 1, 13, 1, 14, 1, 15, 1, 16, 1, 17, 1, 18, 1, 19, 1, 20, 1, 21, 1, 22, 1, 23, 1, 24, 1, 25, 1, 26, 1, 27, 1, 28, 1, 29, 1, 30, 1, 31, 1, 32, 2, 2, 1, 2, 4, 1, 2, 6, 1, 2, 8, 1, 2, 10, 1, 2, 11, 1, 2, 13, 1, 2, 15, 1, 2, 17, 1, 2, 29, 1 ] class << self attr_accessor :_ansi_sgr_to_html_key_offsets private :_ansi_sgr_to_html_key_offsets, :_ansi_sgr_to_html_key_offsets= end self._ansi_sgr_to_html_key_offsets = [ 0, 0, 7, 8, 20, 22, 26, 37, 49, 55, 56, 58, 59, 63, 66, 67, 71, 74, 75, 79, 83, 87, 94, 102, 110, 121, 127, 128, 130, 131, 135, 138, 144, 147, 148, 152, 156, 167, 178, 181, 187, 190, 191, 195 ] class << self attr_accessor :_ansi_sgr_to_html_trans_keys private :_ansi_sgr_to_html_trans_keys, :_ansi_sgr_to_html_trans_keys= end self._ansi_sgr_to_html_trans_keys = [ 10, 13, 27, 34, 38, 60, 62, 91, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 109, 59, 109, 59, 109, 48, 57, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 109, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 109, 56, 57, 59, 109, 48, 55, 59, 50, 53, 59, 49, 50, 48, 57, 59, 48, 57, 59, 49, 50, 48, 57, 59, 48, 57, 59, 49, 50, 48, 57, 59, 109, 48, 57, 59, 109, 48, 57, 53, 59, 109, 48, 52, 54, 57, 54, 55, 56, 57, 59, 109, 48, 53, 54, 55, 56, 57, 59, 109, 48, 53, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 109, 56, 57, 59, 109, 48, 55, 59, 50, 53, 59, 49, 50, 48, 57, 59, 48, 57, 53, 59, 48, 52, 54, 57, 59, 48, 53, 59, 49, 50, 48, 57, 59, 109, 48, 57, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 109, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 109, 59, 48, 57, 53, 59, 48, 52, 54, 57, 59, 48, 53, 59, 49, 50, 48, 57, 10, 13, 27, 34, 38, 60, 62, 0 ] class << self attr_accessor :_ansi_sgr_to_html_single_lengths private :_ansi_sgr_to_html_single_lengths, :_ansi_sgr_to_html_single_lengths= end self._ansi_sgr_to_html_single_lengths = [ 0, 7, 1, 12, 2, 2, 11, 12, 4, 1, 2, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 3, 6, 6, 11, 4, 1, 2, 1, 2, 1, 2, 1, 1, 2, 2, 11, 11, 1, 2, 1, 1, 2, 7 ] class << self attr_accessor :_ansi_sgr_to_html_range_lengths private :_ansi_sgr_to_html_range_lengths, :_ansi_sgr_to_html_range_lengths= end self._ansi_sgr_to_html_range_lengths = [ 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 2, 1, 1, 0, 1, 0, 0, 0, 1, 1, 2, 1, 0, 1, 1, 0, 0, 1, 2, 1, 0, 1, 0 ] class << self attr_accessor :_ansi_sgr_to_html_index_offsets private :_ansi_sgr_to_html_index_offsets, :_ansi_sgr_to_html_index_offsets= end self._ansi_sgr_to_html_index_offsets = [ 0, 0, 8, 10, 23, 26, 30, 42, 55, 61, 63, 66, 68, 72, 75, 77, 81, 84, 86, 90, 94, 98, 104, 112, 120, 132, 138, 140, 143, 145, 149, 152, 157, 160, 162, 166, 170, 182, 194, 197, 202, 205, 207, 211 ] class << self attr_accessor :_ansi_sgr_to_html_trans_targs private :_ansi_sgr_to_html_trans_targs, :_ansi_sgr_to_html_trans_targs= end self._ansi_sgr_to_html_trans_targs = [ 43, 43, 2, 43, 43, 43, 43, 1, 3, 0, 4, 5, 7, 8, 25, 35, 23, 24, 36, 37, 3, 43, 0, 3, 43, 0, 3, 43, 6, 0, 5, 7, 8, 25, 35, 23, 24, 36, 37, 3, 43, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 3, 43, 0, 9, 6, 3, 43, 6, 0, 10, 0, 11, 41, 0, 12, 0, 30, 31, 13, 0, 15, 14, 0, 15, 0, 38, 39, 16, 0, 18, 17, 0, 18, 0, 20, 21, 19, 0, 3, 43, 6, 0, 3, 43, 19, 0, 22, 3, 43, 19, 6, 0, 23, 24, 36, 37, 3, 43, 6, 0, 23, 24, 36, 37, 3, 43, 6, 0, 5, 7, 8, 25, 35, 23, 24, 36, 37, 3, 43, 0, 26, 6, 3, 43, 6, 0, 27, 0, 28, 33, 0, 29, 0, 30, 31, 13, 0, 15, 13, 0, 32, 15, 13, 14, 0, 15, 14, 0, 34, 0, 20, 21, 19, 0, 3, 43, 6, 0, 5, 7, 8, 25, 35, 23, 24, 36, 37, 3, 43, 0, 5, 7, 8, 25, 35, 23, 24, 36, 37, 3, 43, 0, 18, 16, 0, 40, 18, 16, 17, 0, 18, 17, 0, 42, 0, 20, 21, 19, 0, 43, 43, 2, 43, 43, 43, 43, 1, 0 ] class << self attr_accessor :_ansi_sgr_to_html_trans_actions private :_ansi_sgr_to_html_trans_actions, :_ansi_sgr_to_html_trans_actions= end self._ansi_sgr_to_html_trans_actions = [ 61, 61, 1, 61, 61, 61, 61, 63, 0, 0, 59, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 94, 0, 0, 3, 0, 5, 67, 55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 57, 7, 11, 15, 19, 25, 65, 29, 33, 37, 9, 70, 0, 0, 51, 13, 73, 39, 0, 0, 0, 0, 0, 0, 0, 0, 47, 47, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 3, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 23, 23, 23, 23, 23, 82, 65, 0, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 85, 0, 0, 53, 17, 76, 41, 0, 0, 0, 0, 0, 0, 0, 0, 49, 49, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 45, 45, 0, 21, 79, 65, 0, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 88, 0, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 91, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 43, 43, 43, 0, 61, 61, 1, 61, 61, 61, 61, 63, 0 ] class << self attr_accessor :ansi_sgr_to_html_start end self.ansi_sgr_to_html_start = 43; class << self attr_accessor :ansi_sgr_to_html_first_final end self.ansi_sgr_to_html_first_final = 43; class << self attr_accessor :ansi_sgr_to_html_error end self.ansi_sgr_to_html_error = 0; class << self attr_accessor :ansi_sgr_to_html_en_main end self.ansi_sgr_to_html_en_main = 43; end
Public Instance Methods
Convert string
Arguments:¶ ↑
- i
-
Input (string at the moment, future versions are planned to accept streams)
- o
-
Output, anything with << to append to (e.g. String, $stdout, etc.) or nil to get a string returned after complete conversion
# File lib/ansi_sgr_to_html.rb, line 280 def convert(i, o=nil) # i is string, o can be anything with << o = '' unless o begin p ||= 0 pe ||= i.length @fsm_cs = ansi_sgr_to_html_start end begin _klen, _trans, _keys, _acts, _nacts = nil _goto_level = 0 _resume = 10 _eof_trans = 15 _again = 20 _test_eof = 30 _out = 40 while true _trigger_goto = false if _goto_level <= 0 if p == pe _goto_level = _test_eof next end if @fsm_cs == 0 _goto_level = _out next end end if _goto_level <= _resume _keys = _ansi_sgr_to_html_key_offsets[ @fsm_cs] _trans = _ansi_sgr_to_html_index_offsets[ @fsm_cs] _klen = _ansi_sgr_to_html_single_lengths[ @fsm_cs] _break_match = false begin if _klen > 0 _lower = _keys _upper = _keys + _klen - 1 loop do break if _upper < _lower _mid = _lower + ( (_upper - _lower) >> 1 ) if i[p].ord < _ansi_sgr_to_html_trans_keys[_mid] _upper = _mid - 1 elsif i[p].ord > _ansi_sgr_to_html_trans_keys[_mid] _lower = _mid + 1 else _trans += (_mid - _keys) _break_match = true break end end # loop break if _break_match _keys += _klen _trans += _klen end _klen = _ansi_sgr_to_html_range_lengths[ @fsm_cs] if _klen > 0 _lower = _keys _upper = _keys + (_klen << 1) - 2 loop do break if _upper < _lower _mid = _lower + (((_upper-_lower) >> 1) & ~1) if i[p].ord < _ansi_sgr_to_html_trans_keys[_mid] _upper = _mid - 2 elsif i[p].ord > _ansi_sgr_to_html_trans_keys[_mid+1] _lower = _mid + 2 else _trans += ((_mid - _keys) >> 1) _break_match = true break end end # loop break if _break_match _trans += _klen end end while false @fsm_cs = _ansi_sgr_to_html_trans_targs[_trans] if _ansi_sgr_to_html_trans_actions[_trans] != 0 _acts = _ansi_sgr_to_html_trans_actions[_trans] _nacts = _ansi_sgr_to_html_actions[_acts] _acts += 1 while _nacts > 0 _nacts -= 1 _acts += 1 case _ansi_sgr_to_html_actions[_acts - 1] when 0 then begin end when 1 then begin if(@cur_css != @prv_css) then o<<('</span>') if(@prv_css != {}); if(!@putblnk && @cur_css['animation']) then @putblnk=true; o<<@@CSS_blink_keyframes; end; o<<('<span style="'+@cur_css.map{|k,v|"#{k}:#{v}" if(v)}.compact.join(';')+'">') if(@cur_css != {}); end; @prv_css = @cur_css.clone; end when 2 then begin @cur_css['font-weight'] = 'bold'; if(@k_1_is_bold_and_bright) then @curcol=@hicol; if(@cur_css['color']) then k = @locol.index(@cur_css['color'][1,6]); @cur_css['color'] = '#'+@curcol[k] if(k); end; end; end when 3 then begin if(@k_21_is_double_underline) then @cur_css.delete('text-decoration'); @cur_css['border-bottom'] = '3px double currentColor'; else @cur_css.delete('font-weight'); @curcol = @locol; end; end when 4 then begin @cur_css['font-weight'] = 'lighter'; end when 5 then begin @cur_css.delete('font-weight'); end when 6 then begin @cur_css['font-style'] = 'italic'; end when 7 then begin @cur_css.delete('font-style'); end when 8 then begin @cur_css.delete('border-bottom'); @cur_css['text-decoration'] = 'underline'; end when 9 then begin @cur_css.delete('text-decoration'); @cur_css.delete('border-bottom'); end when 10 then begin @cur_css['animation'] = 'term_blink normal 1.2s infinite ease-in-out'; end when 11 then begin @cur_css['animation'] = 'term_blink normal 0.7s infinite ease-in-out'; end when 12 then begin @cur_css.delete('animation'); end when 13 then begin @cur_css['color'],@cur_css['background-color'] = @cur_css['background-color'],@cur_css['color'] unless(@inv); @inv = true; end when 14 then begin @cur_css['color'],@cur_css['background-color'] = @cur_css['background-color'],@cur_css['color'] if(@inv); @inv = false; end when 15 then begin end when 16 then begin end when 17 then begin @cur_css['text-decoration-line'] = 'line-through'; end when 18 then begin @cur_css.delete('text-decoration-line'); end when 19 then begin @cur_css['color'] = '#'+@curcol[ i[p].ord-0x30]; end when 20 then begin @cur_css['background-color'] = '#'+@locol[ i[p].ord-0x30]; end when 21 then begin @cur_css['color'] = '#'+@@Col_tab_256[i[p,3].to_i]; end when 22 then begin @cur_css['background-color'] = '#'+@@Col_tab_256[i[p,3].to_i]; end when 23 then begin @cur_css['color'] = 'rgb('+i[p,11].match(/(\d+);(\d+);(\d+)/)[1,3].join(',')+')'; end when 24 then begin @cur_css['background-color'] = 'rgb('+i[p,11].match(/(\d+);(\d+);(\d+)/)[1,3].join(',')+')'; end when 25 then begin @cur_css.delete('color'); end when 26 then begin @cur_css.delete('background-color'); end when 27 then begin if( i[p].ord == 0x30) then @cur_css.delete('font-family'); else @cur_css['font-family'] = @font_styles[( i[p].ord-0x30)-1]; end; end when 28 then begin @cur_css['font-family'] = @font_styles[9] end when 29 then begin @cur_css = {}; @curcol = @locol end when 30 then begin o<<(@html_escapes[ i[p].ord]); end when 31 then begin o<< i[p].ord.chr(Encoding::UTF_8); end when 32 then begin end end # action switch end end if _trigger_goto next end end if _goto_level <= _again if @fsm_cs == 0 _goto_level = _out next end p += 1 if p != pe _goto_level = _resume next end end if _goto_level <= _test_eof end if _goto_level <= _out break end end end o << '</span>' if(@prv_css != {}) o end