| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 # Use of this source code is governed by a BSD-style license that can be | |
| 3 # found in the LICENSE file. | |
| 4 | |
| 5 import cStringIO | |
| 6 import re | |
| 7 | |
| 8 DEFAULTS = { | |
| 9 'color' : 'white', | |
| 10 'background-color' : 'black', | |
| 11 'font-weight' : 'normal', | |
| 12 'text-decoration' : 'none', | |
| 13 } | |
| 14 | |
| 15 | |
| 16 class Ansi2HTML: | |
| 17 """Class for converting text streams with ANSI codes into html""" | |
| 18 | |
| 19 ANSIEscape = '[' | |
| 20 | |
| 21 ANSIAttributes = { | |
| 22 0 : ['color:' + DEFAULTS['color'], | |
| 23 'font-weight:' + DEFAULTS['font-weight'], | |
| 24 'text-decoration:' + DEFAULTS['text-decoration'], | |
| 25 'background-color:' + DEFAULTS['background-color']], # reset | |
| 26 1 : ['font-weight:bold'], | |
| 27 2 : ['font-weight:lighter'], | |
| 28 4 : ['text-decoration:underline'], | |
| 29 5 : ['text-decoration:blink'], | |
| 30 7 : [], # invert attribute? | |
| 31 8 : [], # invisible attribute? | |
| 32 30 : ['color:black'], | |
| 33 31 : ['color:red'], | |
| 34 32 : ['color:green'], | |
| 35 33 : ['color:yellow'], | |
| 36 34 : ['color:blue'], | |
| 37 35 : ['color:magenta'], | |
| 38 36 : ['color:cyan'], | |
| 39 37 : ['color:white'], | |
| 40 39 : ['color:' + DEFAULTS['color']], | |
| 41 40 : ['background-color:black'], | |
| 42 41 : ['background-color:red'], | |
| 43 42 : ['background-color:green'], | |
| 44 43 : ['background-color:yellow'], | |
| 45 44 : ['background-color:blue'], | |
| 46 45 : ['background-color:magenta'], | |
| 47 46 : ['background-color:cyan'], | |
| 48 47 : ['background-color:white'], | |
| 49 49 : ['background-color:' + DEFAULTS['background-color']], | |
| 50 } | |
| 51 | |
| 52 def __init__(self): | |
| 53 self.ctx = {} | |
| 54 # Send a 0 code, resetting ctx to defaults. | |
| 55 self.attrib('0') | |
| 56 # Prepare a regexp recognizing all ANSI codes. | |
| 57 code_src = '|'.join(self.ANSICodes) | |
| 58 # This captures non-greedy code argument and code itself, both grouped. | |
| 59 self.code_re = re.compile("(.*?)(" + code_src + ")") | |
| 60 | |
| 61 def noop(self, arg): | |
| 62 """Noop code, for ANSI codes that have no html equivalent.""" | |
| 63 return '' | |
| 64 | |
| 65 def attrib(self, arg): | |
| 66 """Text atribute code""" | |
| 67 if arg == '': | |
| 68 # Apparently, empty code argument means reset (0). | |
| 69 arg = '0' | |
| 70 for attr in arg.split(";"): | |
| 71 try: | |
| 72 for change in self.ANSIAttributes[int(attr)]: | |
| 73 pieces = change.split(":") | |
| 74 self.ctx[pieces[0]] = pieces[1] | |
| 75 except KeyError: | |
| 76 # Invalid key? Hmmm. | |
| 77 return 'color:red">ANSI code not found: ' + \ | |
| 78 arg + '<font style="color:' + self.ctx['color'] | |
| 79 return self.printStyle() | |
| 80 | |
| 81 ANSICodes = { | |
| 82 'H' : noop, # cursor_pos, # ESC[y,xH - Cursor position y,x | |
| 83 'A' : noop, # cursor_up, # ESC[nA - Cursor Up n lines | |
| 84 'B' : noop, # cursor_down, # ESC[nB - Cursor Down n lines | |
| 85 'C' : noop, # cursor_forward, # ESC[nC - Cursor Forward n characters | |
| 86 'D' : noop, # cursor_backward, # ESC[nD - Cursor Backward n characters | |
| 87 'f' : noop, # cursor_xy, # ESC[y;xf - Cursor pos y,x (infrequent) | |
| 88 'R' : noop, # cursor_report, # ESC[y;xR - Cursor position report y,x | |
| 89 'n' : noop, # device_status, # ESC[6n - Dev status report (cursor pos) | |
| 90 's' : noop, # save_cursor, # ESC[s - Save cursor position | |
| 91 'u' : noop, # restore_cursor, # ESC[u - Restore cursor position | |
| 92 'J' : noop, # clrscr, # ESC[2J - Erase display | |
| 93 'K' : noop, # erase2eol, # ESC[K - Erase to end of line | |
| 94 'L' : noop, # insertlines, # ESC[nL - Inserts n blank lines at cursor | |
| 95 'M' : noop, # deletelines, # ESC[nM - Deletes n lines including cursor | |
| 96 '@' : noop, # insertchars, # ESC[n@ - Inserts n blank chars at cursor | |
| 97 'P' : noop, # deletechars, # ESC[nP - Deletes n chars including cursor | |
| 98 'y' : noop, # translate, # ESC[n;ny - Output char translate | |
| 99 'p' : noop, # key_reassign, #ESC["str"p - Keyboard Key Reassignment | |
| 100 'm' : attrib, # ESC[n;n;...nm - Set attributes | |
| 101 } | |
| 102 | |
| 103 def printStyle(self, showDefaults=False): | |
| 104 """Returns a text representing the style of the current context.""" | |
| 105 style = '' | |
| 106 for attr in DEFAULTS: | |
| 107 if self.ctx[attr] != DEFAULTS[attr] or showDefaults: | |
| 108 style += attr + ':' + self.ctx[attr] + ';' | |
| 109 return style | |
| 110 | |
| 111 def printHtmlHeader(self, title): | |
| 112 text = '<html><head><title>%s</title></head>' % title | |
| 113 text += '<body bgcolor="%s"><pre>' % DEFAULTS['background-color'] | |
| 114 return text | |
| 115 | |
| 116 def printHtmlFooter(self): | |
| 117 return '</pre></body></html>' | |
| 118 | |
| 119 def printHeader(self): | |
| 120 """Envelopes everything into defaults <font> tag and opens a stub.""" | |
| 121 self.attrib("0") # this means reset to default | |
| 122 return '<font style="%s"><font>' % self.printStyle(showDefaults=True) | |
| 123 | |
| 124 def printFooter(self): | |
| 125 """Closes both stub and envelope font tags.""" | |
| 126 return '</font></font>' | |
| 127 | |
| 128 def parseBlock(self, string): | |
| 129 """Takes a block of text and transform into html""" | |
| 130 output = cStringIO.StringIO() | |
| 131 skipfirst = True | |
| 132 # Splitting by ANSIEscape turns the line into following elements: | |
| 133 # arg,code,text | |
| 134 # First two change the context, text is carried. | |
| 135 for block in string.split(self.ANSIEscape): | |
| 136 if not block: | |
| 137 # First block is empty -> the line starts with escape code. | |
| 138 skipfirst = False | |
| 139 continue | |
| 140 | |
| 141 if skipfirst: | |
| 142 # The line doesn't start with escape code -> skip first block. | |
| 143 output.write(block) | |
| 144 skipfirst = False | |
| 145 continue | |
| 146 | |
| 147 match = self.code_re.match(block) | |
| 148 if not match: | |
| 149 # If there's no match, it is the line end. Don't parse it. | |
| 150 output.write(block) | |
| 151 continue | |
| 152 | |
| 153 parseFunc = self.ANSICodes[match.group(2)] | |
| 154 # Replace ANSI codes with </font><font> sequence | |
| 155 output.write('</font><font style="') | |
| 156 output.write(parseFunc(self, match.group(1))) | |
| 157 output.write('">') | |
| 158 # Output the text | |
| 159 output.write(block.split(match.group(2),1)[1]) | |
| 160 | |
| 161 return output.getvalue() | |
| OLD | NEW |