2 """Doxygen XML to SWIG docstring converter.
6 doxy2swig.py [options] input.xml output.i
8 Converts Doxygen generated XML files into a file containing docstrings
9 that can be used by SWIG-1.3.x. Note that you need to get SWIG
10 version > 1.3.23 or use Robin Dunn's docstring patch to be able to use
13 input.xml is your doxygen generated XML file and output.i is where the
14 output will be written (the file will be clobbered).
17 ######################################################################
19 # This code is implemented using Mark Pilgrim's code as a guideline:
20 # http://www.faqs.org/docs/diveintopython/kgp_divein.html
22 # Author: Prabhu Ramachandran
26 # Johan Hake: the include_function_definition feature
27 # Bill Spotz: bug reports and testing.
28 # Sebastian Henschel: Misc. enhancements.
30 ######################################################################
33 from xml.dom import minidom
42 def my_open_read(source):
43 if hasattr(source, "read"):
48 def my_open_write(dest):
49 if hasattr(dest, "write"):
52 return open(dest, 'w')
56 """Converts Doxygen generated XML files into a file containing
57 docstrings that can be used by SWIG-1.3.x that have support for
58 feature("docstring"). Once the data is parsed it is stored in
63 def __init__(self, src, include_function_definition=True, quiet=False):
64 """Initialize the instance given a source object. `src` can
65 be a file or filename. If you do not want to include function
66 definitions from doxygen then set
67 `include_function_definition` to `False`. This is handy since
68 this allows you to use the swig generated function definition
69 using %feature("autodoc", [0,1]).
73 self.my_dir = os.path.dirname(f.name)
74 self.xmldoc = minidom.parse(f).documentElement
78 self.pieces.append('\n// File: %s\n'%\
79 os.path.basename(f.name))
81 self.space_re = re.compile(r'\s+')
82 self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
84 self.ignores = ['inheritancegraph', 'param', 'listofallmembers',
85 'innerclass', 'name', 'declname', 'incdepgraph',
86 'invincdepgraph', 'type',
87 'references', 'referencedby', 'location',
88 'collaborationgraph', 'reimplements',
89 'reimplementedby', 'derivedcompoundref',
92 self.include_function_definition = include_function_definition
93 if not include_function_definition:
94 self.ignores.append('argsstring')
97 self.list_ctr = 1 #counts the number of spaces to be displayed before displaying a list item
98 self.simplesect_kind = ''
102 """Parses the file set in the initialization. The resulting
103 data is stored in `self.pieces`.
106 self.parse(self.xmldoc)
108 def parse(self, node):
109 """Parse a given node. This function in turn calls the
110 `parse_<nodeType>` functions which handle the respective
114 pm = getattr(self, "parse_%s"%node.__class__.__name__)
117 def parse_Document(self, node):
118 #print("himanshu ::::::: parse Document... ")
119 self.parse(node.documentElement)
121 def parse_Text(self, node):
122 #print("himanshu ::::::: parse Text... ")
124 #txt = txt.replace('\\', r'\\\\')
125 txt = txt.replace('"', r'\"')
126 #print '--------------------------------------'
127 #print '--------------------------------------'
129 # ignore pure whitespace
130 m = self.space_re.match(txt)
131 if m and len(m.group()) == len(txt):
135 t = textwrap.fill(txt, 100, break_long_words=False)
136 #print 'HIMANSHU ---------- >>>>>>>>>>>>>>>>>>>>> '
138 t = t.replace('\n','\n* '+' '*(self.list_ctr+2))
139 #t = t.replace('1234',' '*self.list_ctr)
144 def parse_Element(self, node):
145 """Parse an `ELEMENT_NODE`. This calls specific
146 `do_<tagName>` handers for different elements. If no handler
147 is available the `generic_parse` method is called. All
148 tagNames specified in `self.ignores` are simply ignored.
151 #print("himanshu ::::::: parse Element... ")
153 ignores = self.ignores
156 attr = "do_%s" % name
157 if hasattr(self, attr):
158 handlerMethod = getattr(self, attr)
161 self.generic_parse(node)
162 #if name not in self.generics: self.generics.append(name)
164 def parse_Comment(self, node):
165 """Parse a `COMMENT_NODE`. This does nothing for now."""
168 def add_text(self, value):
169 """Adds text corresponding to `value` into `self.pieces`."""
171 if type(value) in (types.ListType, types.TupleType):
172 self.pieces.extend(value)
174 self.pieces.append(value)
176 def get_specific_nodes(self, node, names):
177 """Given a node and a sequence of strings in `names`, return a
178 dictionary containing the names as keys and child
179 `ELEMENT_NODEs`, that have a `tagName` equal to the name.
182 nodes = [(x.tagName, x) for x in node.childNodes \
183 if x.nodeType == x.ELEMENT_NODE and \
187 def generic_parse(self, node, pad=0):
188 """A Generic parser for arbitrary tags in a node.
192 - node: A node in the DOM.
193 - pad: `int` (default: 0)
195 If 0 the node data is not padded with newlines. If 1 it
196 appends a newline after parsing the childNodes. If 2 it
197 pads before and after the nodes are processed. Defaults to
203 npiece = len(self.pieces)
205 self.add_text('\n* ')
206 for n in node.childNodes:
209 #if len(self.pieces) > npiece:
210 self.add_text('\n* ')
212 def space_parse(self, node):
214 self.generic_parse(node)
216 def do_compoundname(self, node):
217 self.add_text('\n\n')
218 data = node.firstChild.data
219 #self.add_text('%feature("docstring") %s "\n'%data)
220 self.add_text('%typemap(csclassmodifiers) ')
221 self.add_text('%s\n"\n/**\n'%data)
223 def do_compounddef(self, node):
224 kind = node.attributes['kind'].value
225 if kind in ('class', 'struct'):
226 prot = node.attributes['prot'].value
229 names = ('compoundname', 'briefdescription',
230 'detaileddescription', 'includes')
231 first = self.get_specific_nodes(node, names)
235 #self.add_text(['";','\n'])
236 self.add_text(['*/','\n','%s %s ";'%(prot,'class'),'\n'])
237 for n in node.childNodes:
238 if n not in first.values():
240 elif kind in ('file', 'namespace'):
241 nodes = node.getElementsByTagName('sectiondef')
245 def do_includes(self, node):
246 self.add_text('\n* @include ')
247 self.generic_parse(node, pad=1)
249 def do_parameterlist(self, node):
251 #print("himanshu :::::::::: do_parameterlist")
253 for key, val in node.attributes.items():
255 if val == 'param': text = 'Parameters'
256 elif val == 'exception': text = 'Exceptions'
260 if val == 'param': text = '@param'
261 elif val == 'exception': text = '@exception'
264 #self.add_text(['\n', '\n', text, ':', '\n'])
265 #self.add_text(['\n', '* ', text])
266 self.para_kind = text
267 self.generic_parse(node, pad=0)
269 def do_para(self, node):
270 #print("himanshu :::::::: do_para ")
271 #self.add_text(['\n'])
272 self.generic_parse(node, pad=0)
274 def do_parametername(self, node):
275 #print("himanshu :::::::: do_parametername")
276 self.add_text(['\n', '* ', self.para_kind])
279 data=node.firstChild.data
280 except AttributeError: # perhaps a <ref> tag in it
281 data=node.firstChild.firstChild.data
282 if data.find('Exception') != -1:
283 #print("himanshu :::::::: Pronting DAta1")
287 #print("himanshu :::::::: Pronting DAta2")
289 for key, val in node.attributes.items():
290 if key == 'direction':
291 self.add_text('[%s] '%val)
292 self.add_text("%s "%data)
294 def do_parameterdefinition(self, node):
295 self.generic_parse(node, pad=1)
297 def do_detaileddescription(self, node):
299 self.generic_parse(node, pad=0)
301 def do_briefdescription(self, node):
302 self.add_text("* @brief ")
303 self.generic_parse(node, pad=1)
305 def do_memberdef(self, node):
306 prot = node.attributes['prot'].value
307 id = node.attributes['id'].value
308 kind = node.attributes['kind'].value
309 tmp = node.parentNode.parentNode.parentNode
310 compdef = tmp.getElementsByTagName('compounddef')[0]
311 cdef_kind = compdef.attributes['kind'].value
312 #print('Himanshu :: ...... Memberdef........')
313 #print('prot= %s ....., id= %s ....., kind= %s..... ,cdef_kind= %s'%(prot,id,kind,cdef_kind))
316 #print('Entering here')
317 first = self.get_specific_nodes(node, ('definition', 'name'))
319 name = first['name'].firstChild.data
321 if name[:8] == 'operator': # Don't handle operators yet.
323 #print('Entering here2')
326 """if kind == 'enum':
327 #print('himanshu is in enum now')
328 self.add_text('\n\n')
329 self.add_text('%typemap(csclassmodifiers) ')
330 self.add_text('%s\n"\n/**\n'%data)
331 self.generic_parse(node, pad=0)
333 ##################################################
334 # For Classes & Functions
335 if not first.has_key('definition') or \
336 kind in ['variable', 'typedef']:
338 #print('Entering here3')
340 if self.include_function_definition:
341 defn = first['definition'].firstChild.data
345 briefd = node.getElementsByTagName('briefdescription');
346 if kind == 'function' and briefd[0].firstChild.nodeValue == '\n': # first node value if briefdescription exists will be always \n
347 #print('Entering here4')
348 self.add_text('%csmethodmodifiers ')
350 anc = node.parentNode.parentNode
351 if cdef_kind in ('file', 'namespace'):
352 ns_node = anc.getElementsByTagName('innernamespace')
353 if not ns_node and cdef_kind == 'namespace':
354 ns_node = anc.getElementsByTagName('compoundname')
356 ns = ns_node[0].firstChild.data
357 #print("himanshu :::::: do_memberdef....ns_node")
358 self.add_text(' %s::%s "\n%s'%(ns, name, defn))
360 #print("himanshu :::::: do_memberdef....else")
362 #print("++++++++++++++++++++++++++++")
367 self.add_text('/**\n')
368 elif cdef_kind in ('class', 'struct'):
369 # Get the full function name.
370 anc_node = anc.getElementsByTagName('compoundname')
371 cname = anc_node[0].firstChild.data
372 #print("himanshu :::::: do_memberdef...class/struct")
373 self.add_text([' %s::%s'%(cname, name)])
374 self.add_text(['\n','"\n/**\n'])
376 for n in node.childNodes:
377 if n not in first.values():
379 self.add_text(['\n','*/','\n','%s ";'%prot,'\n'])
381 def do_definition(self, node):
382 #print("himanshu :::::: do_definition")
383 data = node.firstChild.data
384 self.add_text('%s "\n%s'%(data, data))
386 def do_sectiondef(self, node):
387 #print('Himanshu : ........SectionDef ........')
388 kind = node.attributes['kind'].value
389 #print('kind = %s'%kind)
390 if kind in ('public-func', 'func', 'user-defined', 'public-type', ''):
391 self.generic_parse(node)
393 def do_header(self, node):
394 """For a user defined section def a header field is present
395 which should not be printed as such, so we comment it in the
397 data = node.firstChild.data
398 self.add_text('\n/*\n %s \n*/\n'%data)
399 # If our immediate sibling is a 'description' node then we
400 # should comment that out also and remove it from the parent
402 parent = node.parentNode
403 idx = parent.childNodes.index(node)
404 if len(parent.childNodes) >= idx + 2:
405 nd = parent.childNodes[idx+2]
406 if nd.nodeName == 'description':
407 nd = parent.removeChild(nd)
408 self.add_text('\n/*')
409 self.generic_parse(nd)
410 self.add_text('\n*/\n')
412 def do_parse_sect(self, node, kind):
413 if kind in ('date', 'rcs', 'version'):
415 elif kind == 'warning':
416 self.add_text(['\n', '* @warning '])
417 self.generic_parse(node,pad=0)
420 self.add_text('* @see ')
421 self.generic_parse(node,pad=0)
422 elif kind == 'return':
424 self.add_text('* @return ')
425 self.generic_parse(node,pad=0)
428 self.add_text('* @pre ')
429 self.generic_parse(node,pad=0)
432 self.add_text('* @note ')
433 self.generic_parse(node,pad=0)
436 self.add_text('* @post ')
437 self.generic_parse(node,pad=0)
440 self.generic_parse(node,pad=0)
442 def do_simplesect(self, node):
443 kind = node.attributes['kind'].value
444 self.simplesect_kind = kind
445 self.do_parse_sect(node, kind)
446 self.simplesect_kind = ''
448 def do_simplesectsep(self, node):
449 #tmp = node.parentnode
450 self.do_parse_sect(node, self.simplesect_kind)
452 def do_argsstring(self, node):
453 #self.generic_parse(node, pad=1)
456 def do_member(self, node):
457 kind = node.attributes['kind'].value
458 refid = node.attributes['refid'].value
459 if kind == 'function' and refid[:9] == 'namespace':
460 self.generic_parse(node)
462 def do_doxygenindex(self, node):
464 comps = node.getElementsByTagName('compound')
466 refid = c.attributes['refid'].value
467 fname = refid + '.xml'
468 if not os.path.exists(fname):
469 fname = os.path.join(self.my_dir, fname)
471 #print "parsing file: %s"%fname
472 p = Doxy2SWIG(fname, self.include_function_definition, self.quiet)
474 self.pieces.extend(self.clean_pieces(p.pieces))
476 def do_emphasis(self,node):
477 self.add_text('\n* <i> ')
478 self.generic_parse(node,pad=0)
479 self.add_text(' </i>')
481 def do_heading(self,node):
482 level = node.attributes['level'].value
483 self.add_text('\n* <h%s> '%level)
484 self.generic_parse(node,pad=0)
485 self.add_text(' </h%s>\n* '%level)
487 def do_itemizedlist(self, node):
488 self.add_text(['\n* '])
489 self.list_ctr = self.list_ctr + 2
490 #self.firstListItem = self.firstListItem + 1
491 self.generic_parse(node, pad=0)
492 self.list_ctr = self.list_ctr - 2
494 def do_listitem(self, node):
495 #self.add_text('\n'* (self.firstListItem-1))
496 #self.firstlistItem = self.firstListItem - 1
497 self.add_text(' ' * self.list_ctr)
499 self.generic_parse(node, pad=0)
501 def do_programlisting(self, node):
502 self.add_text(['\n* '])
503 self.add_text(' ' * (self.list_ctr+2))
504 self.add_text('@code\n*')
505 self.generic_parse(node, pad=0)
506 self.add_text(' ' * (self.list_ctr+2))
507 self.add_text('@endcode\n*')
509 def do_codeline(self, node):
510 self.add_text(' ' * (self.list_ctr+2))
511 self.generic_parse(node, pad=1)
513 def do_highlight(self, node):
514 cl = node.attributes['class'].value
517 self.generic_parse(node, pad=0)
519 def do_sp(self, node):
522 """def do_table(self, node);
523 rows = node.attributes['rows'].value
524 cols = node.attributes['cols'].value"""
526 def do_enumvalue(self, node):
527 self.generic_parse(node, pad=0)
529 def write(self, fname):
530 o = my_open_write(fname)
532 o.write(u"".join(self.pieces).encode('utf-8'))
534 o.write("".join(self.clean_pieces(self.pieces)))
537 def remove_trailing_spaces(self, fname):
539 with open(fname) as o:
541 clean_lines = [l.strip() for l in line if l.strip()]
543 with open('temp','w+') as f:
544 f.writelines('\n'.join(clean_lines))
547 """with open('temp','r+') as f:
550 t = textwrap.fill(text, 100, break_long_words=False)
551 t = t.replace('\n','\n* '+' '*(self.list_ctr+2))
552 #t = t.replace('1234',' '*self.list_ctr)
553 with open('temp','w+') as f:
556 os.rename('temp',fname)
559 def clean_pieces(self, pieces):
560 """Cleans the list of strings given as `pieces`. It replaces
561 multiple newlines by a maximum of 2 and returns a new list.
562 It also wraps the paragraphs nicely.
576 ret.append('\n'*count)
582 for i in _data.split('\n\n'):
583 if i == 'Parameters:' or i == 'Exceptions:':
584 ret.extend([i, '\n-----------', '\n\n'])
585 elif i.find('// File:') > -1: # leave comments alone.
586 ret.extend([i, '\n'])
588 _tmp = textwrap.fill(i.strip(), break_long_words=False)
589 _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
590 ret.extend([_tmp, '\n\n'])
594 def convert(input, output, include_function_definition=True, quiet=False):
595 p = Doxy2SWIG(input, include_function_definition, quiet)
597 #p.pieces=[str(i) for i in p.pieces]
600 p.remove_trailing_spaces(output)
604 parser = optparse.OptionParser(usage)
605 parser.add_option("-n", '--no-function-definition',
609 help='do not include doxygen function definitions')
610 parser.add_option("-q", '--quiet',
614 help='be quiet and minimize output')
616 options, args = parser.parse_args()
618 parser.error("error: no input and output specified")
620 convert(args[0], args[1], not options.func_def, options.quiet)
623 if __name__ == '__main__':