]> git.cworth.org Git - apitrace/blob - xml2txt.py
Handle incomplete traces.
[apitrace] / xml2txt.py
1 #!/usr/bin/env python
2 #############################################################################
3 #
4 # Copyright 2008 Tungsten Graphics, Inc.
5 #
6 # This program is free software: you can redistribute it and/or modify it
7 # under the terms of the GNU Lesser General Public License as published
8 # by the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 #############################################################################
20
21
22 import sys
23 import optparse
24 import xml.parsers.expat
25
26
27 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
28
29
30 class XmlToken:
31
32     def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
33         assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
34         self.type = type
35         self.name_or_data = name_or_data
36         self.attrs = attrs
37         self.line = line
38         self.column = column
39
40     def __str__(self):
41         if self.type == ELEMENT_START:
42             return '<' + self.name_or_data + ' ...>'
43         if self.type == ELEMENT_END:
44             return '</' + self.name_or_data + '>'
45         if self.type == CHARACTER_DATA:
46             return self.name_or_data
47         if self.type == EOF:
48             return 'end of file'
49         assert 0
50
51
52 class XmlTokenizer:
53     """Expat based XML tokenizer."""
54
55     def __init__(self, fp, skip_ws = True):
56         self.fp = fp
57         self.tokens = []
58         self.index = 0
59         self.final = False
60         self.skip_ws = skip_ws
61         
62         self.character_pos = 0, 0
63         self.character_data = ''
64         
65         self.parser = xml.parsers.expat.ParserCreate()
66         self.parser.StartElementHandler  = self.handle_element_start
67         self.parser.EndElementHandler    = self.handle_element_end
68         self.parser.CharacterDataHandler = self.handle_character_data
69     
70     def handle_element_start(self, name, attributes):
71         self.finish_character_data()
72         line, column = self.pos()
73         token = XmlToken(ELEMENT_START, name, attributes, line, column)
74         self.tokens.append(token)
75     
76     def handle_element_end(self, name):
77         self.finish_character_data()
78         line, column = self.pos()
79         token = XmlToken(ELEMENT_END, name, None, line, column)
80         self.tokens.append(token)
81
82     def handle_character_data(self, data):
83         if not self.character_data:
84             self.character_pos = self.pos()
85         self.character_data += data
86     
87     def finish_character_data(self):
88         if self.character_data:
89             if not self.skip_ws or not self.character_data.isspace(): 
90                 line, column = self.character_pos
91                 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
92                 self.tokens.append(token)
93             self.character_data = ''
94     
95     def next(self):
96         size = 16*1024
97         while self.index >= len(self.tokens) and not self.final:
98             self.tokens = []
99             self.index = 0
100             data = self.fp.read(size)
101             self.final = len(data) < size
102             try:
103                 self.parser.Parse(data, self.final)
104             except xml.parsers.expat.ExpatError, e:
105                 #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
106                 if e.code == 3:
107                     pass
108                 else:
109                     raise e
110         if self.index >= len(self.tokens):
111             line, column = self.pos()
112             token = XmlToken(EOF, None, None, line, column)
113         else:
114             token = self.tokens[self.index]
115             self.index += 1
116         return token
117
118     def pos(self):
119         return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
120
121
122 class TokenMismatch(Exception):
123
124     def __init__(self, expected, found):
125         self.expected = expected
126         self.found = found
127
128     def __str__(self):
129         return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
130
131
132
133 class XmlParser:
134     """Base XML document parser."""
135
136     def __init__(self, fp):
137         self.tokenizer = XmlTokenizer(fp)
138         self.consume()
139     
140     def consume(self):
141         self.token = self.tokenizer.next()
142
143     def match_element_start(self, name):
144         return self.token.type == ELEMENT_START and self.token.name_or_data == name
145     
146     def match_element_end(self, name):
147         return self.token.type == ELEMENT_END and self.token.name_or_data == name
148
149     def element_start(self, name):
150         while self.token.type == CHARACTER_DATA:
151             self.consume()
152         if self.token.type != ELEMENT_START:
153             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
154         if self.token.name_or_data != name:
155             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
156         attrs = self.token.attrs
157         self.consume()
158         return attrs
159     
160     def element_end(self, name):
161         while self.token.type == CHARACTER_DATA:
162             self.consume()
163         if self.token.type != ELEMENT_END:
164             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
165         if self.token.name_or_data != name:
166             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
167         self.consume()
168
169     def character_data(self, strip = True):
170         data = ''
171         while self.token.type == CHARACTER_DATA:
172             data += self.token.name_or_data
173             self.consume()
174         if strip:
175             data = data.strip()
176         return data
177
178
179 class Formatter:
180     
181     def function(self, name):
182         return name
183         
184     def variable(self, name):
185         return name
186
187     def literal(self, value):
188         return str(value)
189     
190     def address(self, addr):
191         return addr
192
193
194 class AnsiFormatter(Formatter):
195     '''Formatter for plain-text files which outputs ANSI escape codes. See
196     http://en.wikipedia.org/wiki/ANSI_escape_code for more information
197     concerning ANSI escape codes.
198     '''
199
200     _csi = '\33['
201
202     _normal = '0m'
203     _bold = '1m'
204     _italic = '3m'
205     _red = '31m'
206     _green = '32m'
207     _blue = '34m'
208
209     def _escape(self, code, text):
210         return self._csi + code + text + self._csi + self._normal
211
212     def function(self, name):
213         text = Formatter.function(self, name)
214         return self._escape(self._bold, text)
215         
216     def variable(self, name):
217         text = Formatter.variable(self, name)
218         return self._escape(self._italic, text)
219
220     def literal(self, value):
221         text = Formatter.literal(self, value)
222         return self._escape(self._blue, text)
223     
224     def address(self, value):
225         text = Formatter.address(self, value)
226         return self._escape(self._green, text)
227
228
229 class TraceParser(XmlParser):
230
231     def __init__(self, stream, formatter):
232         XmlParser.__init__(self, stream)
233         self.formatter = formatter
234
235     def parse(self):
236         self.element_start('trace')
237         while self.token.type not in (ELEMENT_END, EOF):
238             self.parse_call()
239         if self.token.type != EOF:
240             self.element_end('trace')
241
242     def parse_call(self):
243         attrs = self.element_start('call')
244         name = attrs['name']
245         args = []
246         ret = None
247         while self.token.type == ELEMENT_START:
248             if self.token.name_or_data == 'arg':
249                 arg = self.parse_arg()
250                 args.append(arg)
251             elif self.token.name_or_data == 'ret':
252                 ret = self.parse_ret()
253             elif self.token.name_or_data == 'call':
254                 # ignore nested function calls
255                 self.parse_call()
256             else:
257                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
258         self.element_end('call')
259         
260         call = self.formatter.function(name)
261         call += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
262         if ret is not None:
263             call += ' = ' + ret
264         call += '\n'
265         
266         try:
267             sys.stdout.write(call)
268         except IOError: 
269             # catch broken pipe
270             sys.exit(0)
271
272     def parse_arg(self):
273         attrs = self.element_start('arg')
274         name = attrs['name']
275         value = self.parse_value()
276         self.element_end('arg')
277
278         return name, value
279
280     def parse_ret(self):
281         attrs = self.element_start('ret')
282         value = self.parse_value()
283         self.element_end('ret')
284
285         return value
286
287     def parse_value(self):
288         if self.token.type == CHARACTER_DATA:
289             return self.formatter.literal(self.character_data())
290         if self.token.type == ELEMENT_START:
291             if self.token.name_or_data == 'elem':
292                 return self.parse_elems()
293             if self.token.name_or_data == 'ref':
294                 return self.parse_ref()
295         raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
296
297     def parse_elems(self):
298         elems = [self.parse_elem()]
299         while self.token.type != ELEMENT_END:
300             elems.append(self.parse_elem())
301         return '{' + ', '.join([name + ' = ' + value for name, value in elems]) + '}'
302
303     def parse_elem(self):
304         attrs = self.element_start('elem')
305         name = attrs['name']
306         value = self.parse_value()
307         self.element_end('elem')
308
309         return name, value
310
311     def parse_ref(self):
312         attrs = self.element_start('ref')
313         if self.token.type != ELEMENT_END:
314             value = '&' + self.parse_value()
315         else:
316             value = self.formatter.address(attrs['addr'])
317         self.element_end('ref')
318
319         return value
320
321
322 def main():
323     parser = optparse.OptionParser(
324         usage="\n\t%prog [options] [file] ...")
325     parser.add_option(
326         '--color', '--colour',
327         type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
328         dest="color", default="always",
329         help="coloring: never, always, or auto [default: %default]")
330     (options, args) = parser.parse_args(sys.argv[1:])
331
332     if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
333         formatter = AnsiFormatter()
334     else:
335         formatter = Formatter()
336     
337     if args:
338         for arg in args:
339             if arg.endswith('.gz'):
340                 from gzip import GzipFile
341                 stream = GzipFile(arg, 'rt')
342             elif arg.endswith('.bz2'):
343                 from bz2 import BZ2File
344                 stream = BZ2File(arg, 'rt')
345             else:
346                 stream = open(arg, 'rt')
347             parser = TraceParser(stream, formatter)
348             parser.parse()
349     else:
350             parser = TraceParser(sys.stdin, formatter)
351             parser.parse()
352
353
354 if __name__ == '__main__':
355     main()