2 Copyright 2011 by the Massachusetts
3 Institute of Technology. All Rights Reserved.
5 Export of this software from the United States of America may
6 require a specific license from the United States Government.
7 It is the responsibility of any person or organization contemplating
8 export to obtain such a license before exporting.
10 WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
11 distribute this software and its documentation for any purpose and
12 without fee is hereby granted, provided that the above copyright
13 notice appear in all copies and that both that copyright notice and
14 this permission notice appear in supporting documentation, and that
15 the name of M.I.T. not be used in advertising or publicity pertaining
16 to distribution of the software without specific, written prior
17 permission. Furthermore if you modify this software you must label
18 your software as modified software and not distribute it in such a
19 fashion that it might be confused with the original M.I.T. software.
20 M.I.T. makes no representations about the suitability of
21 this software for any purpose. It is provided "as is" without express
27 from collections import defaultdict
28 from xml.sax import make_parser
29 from xml.sax.handler import ContentHandler
30 from docmodel import *
32 exclude_funcs = ['krb5_free_octet_data']
34 class DocNode(object):
36 Represents the structure of xml node.
38 def __init__(self, name):
40 @param node: name - the name of a node.
41 @param attributes: a dictionary populated with attributes of a node
42 @param children: a dictionary with lists of children nodes. Nodes
43 in lists are ordered as they appear in a document.
44 @param content: a content of xml node represented as a list of
45 tuples [(type,value)] with type = ['char'|'element'].
46 If type is 'char' then the value is a character string otherwise
47 it is a reference to a child node.
51 self.attributes = dict()
52 self.children = defaultdict(list)
54 def walk(self, decorators, sub_ws, stack=[]):
56 decorator = decorators.get(self.name, decorators['default'])
57 stack.append(decorators['default'])
58 decorators['default'] = decorator
60 for (obj_type,obj) in self.content:
61 if obj_type == 'char':
65 partial = obj.walk(decorators,1, stack)
66 if partial is not None:
67 result.append(' %s ' % partial)
68 decorators['default'] = stack.pop()
69 result = decorator(self, ''.join(result))
70 if result is not None:
72 result = re.sub(r'[ ]+', r' ', result)
74 result = result.strip()
79 decorators = {'default': lambda node,value: value}
80 result = self.walk(decorators, 1)
87 result = ['Content: %s' % self.content]
89 for (key,value) in self.attributes.iteritems():
90 result.append('Attr: %s = %s' % (key,value))
91 for (key,value) in self.children.iteritems():
92 result.append('Child: %s,%i' % (key,len(value)))
94 return '\n'.join(result)
96 class DoxyContenHandler(object, ContentHandler):
97 def __init__(self, builder):
98 self.builder = builder
99 self.counters = defaultdict(int)
103 def startDocument(self):
106 def endDocument(self):
109 def startElement(self, name, attrs):
110 if name == self.builder.toplevel:
113 if name == 'memberdef':
114 kind = attrs.get('kind')
116 raise ValueError('Kind is not defined')
117 self.counters[kind] += 1
119 if self._nodes is None:
123 for (key,value) in attrs.items():
124 node.attributes[key] = value
125 if self._current is not None:
126 self._current.children[name].append(node)
127 self._nodes.append(self._current)
130 def characters(self, content):
132 if self._current is not None:
133 self._current.content.append(('char',content.strip()))
135 def endElement(self, name):
136 if name == self.builder.toplevel:
137 assert(len(self._nodes) == 0)
139 self.builder.document.append(self._current)
142 if self._nodes is not None:
144 self._current = self._nodes.pop()
145 self._current.content.append(('element',node))
148 class XML2AST(object):
150 Translates XML document into Abstract Syntax Tree like representation
151 The content of document is stored in self.document
153 def __init__(self, xmlpath, toplevel='doxygen'):
154 self.document = list()
155 self.toplevel = toplevel
156 self.parser = make_parser()
157 handler = DoxyContenHandler(self)
158 self.parser.setContentHandler(handler)
159 filename = 'krb5_8hin.xml'
160 filepath = '%s/%s' % (xmlpath,filename)
161 self.parser.parse(open(filepath,'r'))
164 class DoxyFuncs(XML2AST):
165 def __init__(self, path):
166 super(DoxyFuncs, self).__init__(path,toplevel='memberdef')
167 self.objects = list()
170 for node in self.document:
173 def process(self, node):
174 node_type = node.attributes['kind']
175 if node_type == 'function':
176 data = self._process_function_node(node)
180 if 'name' in data and data['name'] in exclude_funcs:
182 self.objects.append(DocModel(**data))
184 def save(self, templates, target_dir):
185 for obj in self.objects:
186 template_path = templates[obj.category]
187 outpath = '%s/%s.rst' % (target_dir,obj.name)
188 obj.save(outpath, template_path)
191 def _process_function_node(self, node):
192 f_name = node.children['name'][0].getContent()
193 f_Id = node.attributes['id']
194 f_ret_type = self._process_type_node(node.children['type'][0])
195 f_brief = node.children['briefdescription'][0].getContent()
196 f_detailed = node.children['detaileddescription'][0]
197 detailed_description = self._process_description_node(f_detailed)
198 return_value_description = self._process_return_value_description(f_detailed)
199 retval_description = self._process_retval_description(f_detailed)
200 warning_description = self._process_warning_description(f_detailed)
201 seealso_description = self._process_seealso_description(f_detailed)
202 notes_description = self._process_notes_description(f_detailed)
203 f_version = self._process_version_description(f_detailed)
204 deprecated_description = self._process_deprecated_description(f_detailed)
205 param_description_map = self.process_parameter_description(f_detailed)
206 f_definition = node.children['definition'][0].getContent()
207 f_argsstring = node.children['argsstring'][0].getContent()
209 function_descr = {'category': 'function',
212 'return_type': f_ret_type[1],
213 'return_description': return_value_description,
214 'retval_description': retval_description,
215 'sa_description': seealso_description,
216 'warn_description': warning_description,
217 'notes_description': notes_description,
218 'short_description': f_brief,
219 'version_num': f_version,
220 'long_description': detailed_description,
221 'deprecated_description': deprecated_description,
222 'parameters': list()}
224 parameters = function_descr['parameters']
225 for (i,p) in enumerate(node.children['param']):
226 type_node = p.children['type'][0]
227 p_type = self._process_type_node(type_node)
228 if p_type[1].find('...') > -1 :
232 p_name_node = p.children.get('declname')
233 if p_name_node is not None:
234 p_name = p_name_node[0].getContent()
235 (p_direction,p_descr) = param_description_map.get(p_name,(None,None))
237 param_descr = {'seqno': i,
239 'direction': p_direction,
242 'description': p_descr}
243 parameters.append(param_descr)
244 result = Function(**function_descr)
245 print >> self.tmp, result
247 return function_descr
249 def _process_type_node(self, type_node):
252 <type>type_string</type>
253 for build in types and
255 <ref refid='reference',kindref='member|compound'>
258 postfix (ex. *, **m, etc.)
260 for user defined types.
262 type_ref_node = type_node.children.get('ref')
263 if type_ref_node is not None:
264 p_type_id = type_ref_node[0].attributes['refid']
267 p_type = type_node.getContent()
269 p_type = re.sub('KRB5_ATTR_DEPRECATED', '', p_type)
270 p_type = re.sub('KRB5_CALLCONV_C', '', p_type)
271 p_type = re.sub('KRB5_CALLCONV_WRONG', '', p_type)
272 p_type = re.sub('KRB5_CALLCONV', '', p_type)
273 p_type = p_type.strip()
275 return (p_type_id, p_type)
277 def _process_description_node(self, node):
279 Description node is comprised of <para>...</para> sections
281 para = node.children.get('para')
284 decorators = {'default': self.paragraph_content_decorator}
286 result.append(str(e.walk(decorators, 1)))
288 result = '\n'.join(result)
292 def return_value_description_decorator(self, node, value):
293 if node.name == 'simplesect':
294 if node.attributes['kind'] == 'return':
296 cont = node.getContent()
301 def paragraph_content_decorator(self, node, value):
302 if node.name == 'para':
304 elif node.name == 'simplesect':
305 if node.attributes['kind'] == 'return':
307 elif node.name == 'ref':
308 if value.find('()') >= 0:
310 return ':c:func:' + '`' + value + '`'
313 return ':data:' + '`' + value + '`'
314 elif node.name == 'emphasis':
315 return '*' + value + '*'
316 elif node.name == 'itemizedlist':
318 elif node.name == 'listitem':
319 return '\n\t - ' + value + '\n'
320 elif node.name == 'computeroutput':
321 return '**' + value + '**'
325 def parameter_name_decorator(self, node, value):
326 if node.name == 'parametername':
327 direction = node.attributes.get('direction')
328 if direction is not None:
329 value = '%s:%s' % (value,direction)
332 elif node.name == 'parameterdescription':
337 def parameter_description_decorator(self, node, value):
338 if node.name == 'parameterdescription':
340 elif node.name == 'parametername':
345 def process_parameter_description(self, node):
347 Parameter descriptions reside inside detailed description section.
349 para = node.children.get('para')
354 param_list = e.children.get('parameterlist')
355 if param_list is None:
357 param_items = param_list[0].children.get('parameteritem')
358 if param_items is None:
360 for it in param_items:
361 decorators = {'default': self.parameter_name_decorator}
363 name = it.walk(decorators,0).split(':')
367 decorators = {'default': self.parameter_description_decorator,
368 'para': self.paragraph_content_decorator}
369 description = it.walk(decorators, 0)
370 result[name[0]] = (direction,description)
374 def _process_return_value_description(self, node):
378 para = node.children.get('para')
381 simplesect_list = p.children.get('simplesect')
382 if simplesect_list is None:
384 for it in simplesect_list:
385 decorators = {'default': self.return_value_description_decorator,
386 'para': self.parameter_name_decorator}
387 result = it.walk(decorators, 1)
388 if result is not None:
393 def _process_retval_description(self, node):
395 retval descriptions reside inside detailed description section.
397 para = node.children.get('para')
404 param_list = e.children.get('parameterlist')
405 if param_list is None:
408 kind = p.attributes['kind']
411 param_items = p.children.get('parameteritem')
412 if param_items is None:
416 for it in param_items:
417 param_descr = it.children.get('parameterdescription')
418 if param_descr is not None:
419 val = param_descr[0].children.get('para')
422 val_descr = val[0].getContent()
427 decorators = {'default': self.parameter_name_decorator}
429 name = it.walk(decorators, 1).split(':')
432 result = " %s %s" % (val, val_descr)
436 def return_warning_decorator(self, node, value):
437 if node.name == 'simplesect':
438 if node.attributes['kind'] == 'warning':
443 def _process_warning_description(self, node):
445 para = node.children.get('para')
448 simplesect_list = p.children.get('simplesect')
449 if simplesect_list is None:
451 for it in simplesect_list:
452 decorators = {'default': self.return_warning_decorator,
453 'para': self.paragraph_content_decorator}
454 result = it.walk(decorators, 1)
455 # Assuming that only one Warning per function
456 if result is not None:
460 def return_seealso_decorator(self, node, value):
461 if node.name == 'simplesect':
462 if node.attributes['kind'] == 'see':
467 def _process_seealso_description(self, node):
469 para = node.children.get('para')
472 simplesect_list = p.children.get('simplesect')
473 if simplesect_list is None:
475 for it in simplesect_list:
476 decorators = {'default': self.return_seealso_decorator,
477 'para': self.paragraph_content_decorator}
478 result = it.walk(decorators, 1)
481 def return_version_decorator(self, node, value):
482 if node.name == 'simplesect':
483 if node.attributes['kind'] == 'version':
488 def _process_version_description(self, node):
490 para = node.children.get('para')
493 simplesect_list = p.children.get('simplesect')
494 if simplesect_list is None:
496 for it in simplesect_list:
497 decorators = {'default': self.return_version_decorator,
498 'para': self.paragraph_content_decorator}
499 result = it.walk(decorators, 1)
500 if result is not None:
504 def return_notes_decorator(self, node, value):
505 if node.name == 'simplesect':
506 if node.attributes['kind'] == 'note':
507 # We indent notes with an extra tab. Do it for all paragraphs.
508 return value.replace("\n ", "\n\n\t ");
512 def _process_notes_description(self, node):
514 para = node.children.get('para')
517 simplesect_list = p.children.get('simplesect')
518 if simplesect_list is None:
520 for it in simplesect_list:
521 decorators = {'default': self.return_notes_decorator,
522 'para': self.paragraph_content_decorator}
523 result = it.walk(decorators, 1)
524 if result is not None:
528 def return_deprecated_decorator(self, node, value):
529 if node.name == 'xrefsect':
530 if node.attributes['id'].find('deprecated_') > -1:
531 xreftitle = node.children.get('xreftitle')
532 if xreftitle[0] is not None:
533 xrefdescr = node.children.get('xrefdescription')
534 deprecated_descr = "DEPRECATED %s" % xrefdescr[0].getContent()
535 return deprecated_descr
539 def _process_deprecated_description(self, node):
541 para = node.children.get('para')
544 xrefsect_list = p.children.get('xrefsect')
545 if xrefsect_list is None:
547 for it in xrefsect_list:
548 decorators = {'default': self.return_deprecated_decorator,
549 'para': self.paragraph_content_decorator}
550 result = it.walk(decorators, 1)
551 if result is not None:
555 def break_into_lines(self, value, linelen=82):
556 breaks = range(0,len(value),linelen) + [len(value)]
558 for (start,end) in zip(breaks[:-1],breaks[1:]):
559 result.append(value[start:end])
560 result = '\n'.join(result)
564 def _save(self, table, path = None):
570 f.write('%s\n' % ','.join(l))
576 class DoxyBuilderFuncs(DoxyFuncs):
577 def __init__(self, xmlpath, rstpath):
578 super(DoxyBuilderFuncs,self).__init__(xmlpath)
579 self.target_dir = rstpath
580 outfile = '%s/%s' % (self.target_dir, 'out.txt')
581 self.tmp = open(outfile, 'w')
585 templates = {'function': 'func_document.tmpl'}
586 self.save(templates, self.target_dir)
591 if __name__ == '__main__':
592 builder = DoxyBuilderFuncs(xmlpath, rstpath)