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 |