]> git.cworth.org Git - apitrace/blob - xml2txt.py
527ab43f5a66f71789fa422509fd0c1f9398a1dc
[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 != ELEMENT_END:
238             self.parse_call()
239         self.element_end('trace')
240
241     def parse_call(self):
242         attrs = self.element_start('call')
243         name = attrs['name']
244         args = []
245         ret = None
246         while self.token.type == ELEMENT_START:
247             if self.token.name_or_data == 'arg':
248                 arg = self.parse_arg()
249                 args.append(arg)
250             elif self.token.name_or_data == 'ret':
251                 ret = self.parse_ret()
252             elif self.token.name_or_data == 'call':
253                 self.parse_call()
254             else:
255                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
256         self.element_end('call')
257         
258         call = self.formatter.function(name)
259         call += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
260         if ret is not None:
261             call += ' = ' + ret
262         call += '\n'
263         
264         try:
265             sys.stdout.write(call)
266         except IOError: 
267             # catch broken pipe
268             sys.exit(0)
269
270     def parse_arg(self):
271         attrs = self.element_start('arg')
272         name = attrs['name']
273         value = self.parse_value()
274         self.element_end('arg')
275
276         return name, value
277
278     def parse_ret(self):
279         attrs = self.element_start('ret')
280         value = self.parse_value()
281         self.element_end('ret')
282
283         return value
284
285     def parse_value(self):
286         if self.token.type == CHARACTER_DATA:
287             return self.formatter.literal(self.character_data())
288         if self.token.type == ELEMENT_START:
289             if self.token.name_or_data == 'elem':
290                 return self.parse_elems()
291             if self.token.name_or_data == 'ref':
292                 return self.parse_ref()
293         raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
294
295     def parse_elems(self):
296         elems = [self.parse_elem()]
297         while self.token.type != ELEMENT_END:
298             elems.append(self.parse_elem())
299         return '{' + ', '.join([name + ' = ' + value for name, value in elems]) + '}'
300
301     def parse_elem(self):
302         attrs = self.element_start('elem')
303         name = attrs['name']
304         value = self.parse_value()
305         self.element_end('elem')
306
307         return name, value
308
309     def parse_ref(self):
310         attrs = self.element_start('ref')
311         if self.token.type != ELEMENT_END:
312             value = '&' + self.parse_value()
313         else:
314             value = self.formatter.address(attrs['addr'])
315         self.element_end('ref')
316
317         return value
318
319
320 def main():
321     parser = optparse.OptionParser(
322         usage="\n\t%prog [options] [file] ...")
323     parser.add_option(
324         '--color', '--colour',
325         type="choice", choices=('never', 'always', 'auto'), metavar='WHEN',
326         dest="color", default="always",
327         help="coloring: never, always, or auto [default: %default]")
328     (options, args) = parser.parse_args(sys.argv[1:])
329
330     if options.color == 'always' or options.color == 'auto' and sys.stdout.isatty():
331         formatter = AnsiFormatter()
332     else:
333         formatter = Formatter()
334     
335     if args:
336         for arg in args:
337             if arg.endswith('.gz'):
338                 from gzip import GzipFile
339                 stream = GzipFile(arg, 'rt')
340             elif arg.endswith('.bz2'):
341                 from bz2 import BZ2File
342                 stream = BZ2File(arg, 'rt')
343             else:
344                 stream = open(arg, 'rt')
345             parser = TraceParser(stream, formatter)
346             parser.parse()
347     else:
348             parser = TraceParser(sys.stdin, formatter)
349             parser.parse()
350
351
352 if __name__ == '__main__':
353     main()