]> git.cworth.org Git - apitrace/blob - xml2txt.py
Flush to ensure the file is in readable state if the process dies.
[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 xml.parsers.expat
24
25
26 ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF = range(4)
27
28
29 class XmlToken:
30
31     def __init__(self, type, name_or_data, attrs = None, line = None, column = None):
32         assert type in (ELEMENT_START, ELEMENT_END, CHARACTER_DATA, EOF)
33         self.type = type
34         self.name_or_data = name_or_data
35         self.attrs = attrs
36         self.line = line
37         self.column = column
38
39     def __str__(self):
40         if self.type == ELEMENT_START:
41             return '<' + self.name_or_data + ' ...>'
42         if self.type == ELEMENT_END:
43             return '</' + self.name_or_data + '>'
44         if self.type == CHARACTER_DATA:
45             return self.name_or_data
46         if self.type == EOF:
47             return 'end of file'
48         assert 0
49
50
51 class XmlTokenizer:
52     """Expat based XML tokenizer."""
53
54     def __init__(self, fp, skip_ws = True):
55         self.fp = fp
56         self.tokens = []
57         self.index = 0
58         self.final = False
59         self.skip_ws = skip_ws
60         
61         self.character_pos = 0, 0
62         self.character_data = ''
63         
64         self.parser = xml.parsers.expat.ParserCreate()
65         self.parser.StartElementHandler  = self.handle_element_start
66         self.parser.EndElementHandler    = self.handle_element_end
67         self.parser.CharacterDataHandler = self.handle_character_data
68     
69     def handle_element_start(self, name, attributes):
70         self.finish_character_data()
71         line, column = self.pos()
72         token = XmlToken(ELEMENT_START, name, attributes, line, column)
73         self.tokens.append(token)
74     
75     def handle_element_end(self, name):
76         self.finish_character_data()
77         line, column = self.pos()
78         token = XmlToken(ELEMENT_END, name, None, line, column)
79         self.tokens.append(token)
80
81     def handle_character_data(self, data):
82         if not self.character_data:
83             self.character_pos = self.pos()
84         self.character_data += data
85     
86     def finish_character_data(self):
87         if self.character_data:
88             if not self.skip_ws or not self.character_data.isspace(): 
89                 line, column = self.character_pos
90                 token = XmlToken(CHARACTER_DATA, self.character_data, None, line, column)
91                 self.tokens.append(token)
92             self.character_data = ''
93     
94     def next(self):
95         size = 16*1024
96         while self.index >= len(self.tokens) and not self.final:
97             self.tokens = []
98             self.index = 0
99             data = self.fp.read(size)
100             self.final = len(data) < size
101             try:
102                 self.parser.Parse(data, self.final)
103             except xml.parsers.expat.ExpatError, e:
104                 #if e.code == xml.parsers.expat.errors.XML_ERROR_NO_ELEMENTS:
105                 if e.code == 3:
106                     pass
107                 else:
108                     raise e
109         if self.index >= len(self.tokens):
110             line, column = self.pos()
111             token = XmlToken(EOF, None, None, line, column)
112         else:
113             token = self.tokens[self.index]
114             self.index += 1
115         return token
116
117     def pos(self):
118         return self.parser.CurrentLineNumber, self.parser.CurrentColumnNumber
119
120
121 class TokenMismatch(Exception):
122
123     def __init__(self, expected, found):
124         self.expected = expected
125         self.found = found
126
127     def __str__(self):
128         return '%u:%u: %s expected, %s found' % (self.found.line, self.found.column, str(self.expected), str(self.found))
129
130
131
132 class XmlParser:
133     """Base XML document parser."""
134
135     def __init__(self, fp):
136         self.tokenizer = XmlTokenizer(fp)
137         self.consume()
138     
139     def consume(self):
140         self.token = self.tokenizer.next()
141
142     def match_element_start(self, name):
143         return self.token.type == ELEMENT_START and self.token.name_or_data == name
144     
145     def match_element_end(self, name):
146         return self.token.type == ELEMENT_END and self.token.name_or_data == name
147
148     def element_start(self, name):
149         while self.token.type == CHARACTER_DATA:
150             self.consume()
151         if self.token.type != ELEMENT_START:
152             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
153         if self.token.name_or_data != name:
154             raise TokenMismatch(XmlToken(ELEMENT_START, name), self.token)
155         attrs = self.token.attrs
156         self.consume()
157         return attrs
158     
159     def element_end(self, name):
160         while self.token.type == CHARACTER_DATA:
161             self.consume()
162         if self.token.type != ELEMENT_END:
163             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
164         if self.token.name_or_data != name:
165             raise TokenMismatch(XmlToken(ELEMENT_END, name), self.token)
166         self.consume()
167
168     def character_data(self, strip = True):
169         data = ''
170         while self.token.type == CHARACTER_DATA:
171             data += self.token.name_or_data
172             self.consume()
173         if strip:
174             data = data.strip()
175         return data
176
177
178 class Formatter:
179     
180     def function(self, name):
181         return name
182         
183     def variable(self, name):
184         return name
185
186     def literal(self, value):
187         return str(value)
188     
189     def address(self, addr):
190         return addr
191
192
193 class AnsiFormatter(Formatter):
194     '''Formatter for plain-text files which outputs ANSI escape codes. See
195     http://en.wikipedia.org/wiki/ANSI_escape_code for more information
196     concerning ANSI escape codes.
197     '''
198
199     _csi = '\33['
200
201     _normal = '0m'
202     _bold = '1m'
203     _italic = '3m'
204     _red = '31m'
205     _green = '32m'
206     _blue = '34m'
207
208     def _escape(self, code, text):
209         return self._csi + code + text + self._csi + self._normal
210
211     def function(self, name):
212         text = Formatter.function(self, name)
213         return self._escape(self._bold, text)
214         
215     def variable(self, name):
216         text = Formatter.variable(self, name)
217         return self._escape(self._italic, text)
218
219     def literal(self, value):
220         text = Formatter.literal(self, value)
221         return self._escape(self._blue, text)
222     
223     def address(self, value):
224         text = Formatter.address(self, value)
225         return self._escape(self._green, text)
226
227
228 class TraceParser(XmlParser):
229
230     def __init__(self, stream, formatter):
231         XmlParser.__init__(self, stream)
232         self.formatter = formatter
233
234     def parse(self):
235         self.element_start('trace')
236         while self.token.type != ELEMENT_END:
237             self.parse_call()
238         self.element_end('trace')
239
240     def parse_call(self):
241         attrs = self.element_start('call')
242         name = attrs['name']
243         args = []
244         ret = None
245         while self.token.type == ELEMENT_START:
246             if self.token.name_or_data == 'arg':
247                 arg = self.parse_arg()
248                 args.append(arg)
249             elif self.token.name_or_data == 'ret':
250                 ret = self.parse_ret()
251             elif self.token.name_or_data == 'call':
252                 self.parse_call()
253             else:
254                 raise TokenMismatch("<arg ...> or <ret ...>", self.token)
255         self.element_end('call')
256         
257         call = self.formatter.function(name)
258         call += '(' + ', '.join([self.formatter.variable(name) + ' = ' + value for name, value in args]) + ')'
259         if ret is not None:
260             call += ' = ' + ret
261         call += '\n'
262         
263         try:
264             sys.stdout.write(call)
265         except IOError: 
266             # catch broken pipe
267             sys.exit(0)
268
269     def parse_arg(self):
270         attrs = self.element_start('arg')
271         name = attrs['name']
272         value = self.parse_value()
273         self.element_end('arg')
274
275         return name, value
276
277     def parse_ret(self):
278         attrs = self.element_start('ret')
279         value = self.parse_value()
280         self.element_end('ret')
281
282         return value
283
284     def parse_value(self):
285         if self.token.type == CHARACTER_DATA:
286             return self.formatter.literal(self.character_data())
287         if self.token.type == ELEMENT_START:
288             if self.token.name_or_data == 'elem':
289                 return self.parse_elems()
290             if self.token.name_or_data == 'ref':
291                 return self.parse_ref()
292         raise TokenMismatch("<elem ...>, <ref ...>, or text", self.token)
293
294     def parse_elems(self):
295         elems = [self.parse_elem()]
296         while self.token.type != ELEMENT_END:
297             elems.append(self.parse_elem())
298         return '{' + ', '.join([name + ' = ' + value for name, value in elems]) + '}'
299
300     def parse_elem(self):
301         attrs = self.element_start('elem')
302         name = attrs['name']
303         value = self.parse_value()
304         self.element_end('elem')
305
306         return name, value
307
308     def parse_ref(self):
309         attrs = self.element_start('ref')
310         if self.token.type != ELEMENT_END:
311             value = '&' + self.parse_value()
312         else:
313             value = self.formatter.address(attrs['addr'])
314         self.element_end('ref')
315
316         return value
317
318
319 def main():
320     formatter = AnsiFormatter()
321     
322     args = sys.argv[1:]
323     if args:
324         for arg in args:
325             if arg.endswith('.gz'):
326                 from gzip import GzipFile
327                 stream = GzipFile(arg, 'rt')
328             elif arg.endswith('.bz2'):
329                 from bz2 import BZ2File
330                 stream = BZ2File(arg, 'rt')
331             else:
332                 stream = open(arg, 'rt')
333             parser = TraceParser(stream, formatter)
334             parser.parse()
335     else:
336             parser = TraceParser(sys.stdin, formatter)
337             parser.parse()
338
339
340 if __name__ == '__main__':
341     main()