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 ######################################################################
32 from xml.dom import minidom
41 def my_open_read(source):
42 if hasattr(source, "read"):
47 def my_open_write(dest):
48 if hasattr(dest, "write"):
51 return open(dest, 'w')
55 """Converts Doxygen generated XML files into a file containing
56 docstrings that can be used by SWIG-1.3.x that have support for
57 feature("docstring"). Once the data is parsed it is stored in
62 def __init__(self, src, include_function_definition=True, quiet=False):
63 """Initialize the instance given a source object. `src` can
64 be a file or filename. If you do not want to include function
65 definitions from doxygen then set
66 `include_function_definition` to `False`. This is handy since
67 this allows you to use the swig generated function definition
68 using %feature("autodoc", [0,1]).
72 self.my_dir = os.path.dirname(f.name)
73 self.xmldoc = minidom.parse(f).documentElement
77 self.pieces.append('\n// File: %s\n'%\
78 os.path.basename(f.name))
80 self.space_re = re.compile(r'\s+')
81 self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
83 self.ignores = ['inheritancegraph', 'param', 'listofallmembers',
84 'innerclass', 'name', 'declname', 'incdepgraph',
85 'invincdepgraph', 'type',
86 'references', 'referencedby', 'location',
87 'collaborationgraph', 'reimplements',
88 'reimplementedby', 'derivedcompoundref',
89 'basecompoundref', 'initializer']
91 self.include_function_definition = include_function_definition
92 if not include_function_definition:
93 self.ignores.append('argsstring')
96 self.list_ctr = 1 #counts the number of spaces to be displayed before displaying a list item
97 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)
222 self.cdef_data = data
224 def do_compounddef(self, node):
225 kind = node.attributes['kind'].value
226 if kind in ('class', 'struct'):
227 prot = node.attributes['prot'].value
230 names = ('compoundname', 'briefdescription',
231 'detaileddescription', 'includes')
232 first = self.get_specific_nodes(node, names)
236 #self.add_text(['";','\n'])
237 self.add_text(['*/','\n','%s %s ";'%(prot,'class'),'\n'])
238 for n in node.childNodes:
239 if n not in first.values():
241 elif kind in ('file', 'namespace'):
242 nodes = node.getElementsByTagName('sectiondef')
246 def do_includes(self, node):
247 self.add_text('\n* @include ')
248 self.generic_parse(node, pad=1)
250 def do_parameterlist(self, node):
252 print("himanshu :::::::::: do_parameterlist")
254 for key, val in node.attributes.items():
256 if val == 'param': text = 'Parameters'
257 elif val == 'exception': text = 'Exceptions'
261 if val == 'param': text = '@param'
262 elif val == 'exception': text = '@exception'
265 #self.add_text(['\n', '\n', text, ':', '\n'])
266 #self.add_text(['\n', '* ', text])
267 self.para_kind = text
268 self.generic_parse(node, pad=0)
270 def do_para(self, node):
271 print("himanshu :::::::: do_para ")
272 #self.add_text(['\n'])
273 self.generic_parse(node, pad=0)
275 def do_parametername(self, node):
276 print("himanshu :::::::: do_parametername")
277 self.add_text(['\n', '* ', self.para_kind])
280 data=node.firstChild.data
281 except AttributeError: # perhaps a <ref> tag in it
282 data=node.firstChild.firstChild.data
283 if data.find('Exception') != -1:
284 print("himanshu :::::::: Pronting DAta1")
288 print("himanshu :::::::: Pronting DAta2")
290 for key, val in node.attributes.items():
291 if key == 'direction':
292 self.add_text('[%s] '%val)
293 self.add_text("%s "%data)
295 def do_parameterdefinition(self, node):
296 self.generic_parse(node, pad=1)
298 def do_detaileddescription(self, node):
300 self.generic_parse(node, pad=0)
302 def do_briefdescription(self, node):
303 #self.add_text("* @brief ")
304 self.generic_parse(node, pad=0)
306 def do_memberdef(self, node):
307 prot = node.attributes['prot'].value
308 id = node.attributes['id'].value
309 kind = node.attributes['kind'].value
310 tmp = node.parentNode.parentNode.parentNode
311 compdef = tmp.getElementsByTagName('compounddef')[0]
312 cdef_kind = compdef.attributes['kind'].value
313 print('Himanshu :: ...... Memberdef........')
314 print('prot= %s ....., id= %s ....., kind= %s..... ,cdef_kind= %s'%(prot,id,kind,cdef_kind))
317 print('Entering here')
318 first = self.get_specific_nodes(node, ('definition', 'name'))
320 name = first['name'].firstChild.data
322 if name[:8] == 'operator': # Don't handle operators yet.
324 print('Entering here2')
328 #print('himanshu is in enum now')
329 """self.add_text('\n\n')
330 data = node.firstChild.data
331 self.add_text('%csattributes ')
332 self.add_text('%s\n"\n/**\n'%data)"""
333 self.generic_parse(node, pad=0)
335 ##################################################
336 # For Classes & Functions
337 if not first.has_key('definition') or \
338 kind in ['variable', 'typedef']:
340 print('Entering here3')
342 if self.include_function_definition:
343 defn = first['definition'].firstChild.data
347 briefd = node.getElementsByTagName('briefdescription');
348 if kind == 'function' and briefd[0].firstChild.nodeValue == '\n': # first node value if briefdescription exists will be always \n
349 print('Entering here4')
350 self.add_text('%csmethodmodifiers ')
352 anc = node.parentNode.parentNode
353 if cdef_kind in ('file', 'namespace'):
354 ns_node = anc.getElementsByTagName('innernamespace')
355 if not ns_node and cdef_kind == 'namespace':
356 ns_node = anc.getElementsByTagName('compoundname')
358 ns = ns_node[0].firstChild.data
359 print("himanshu :::::: do_memberdef....ns_node")
360 self.add_text(' %s::%s "\n%s'%(ns, name, defn))
362 print("himanshu :::::: do_memberdef....else")
364 print("++++++++++++++++++++++++++++")
369 self.add_text('/**\n')
370 elif cdef_kind in ('class', 'struct'):
371 # Get the full function name.
372 anc_node = anc.getElementsByTagName('compoundname')
373 cname = anc_node[0].firstChild.data
374 print("himanshu :::::: do_memberdef...class/struct")
375 self.add_text([' %s::%s'%(cname, name)])
376 self.add_text(['\n','"\n/**\n'])
378 for n in node.childNodes:
379 if n not in first.values():
381 self.add_text(['\n','*/','\n','%s ";'%prot,'\n'])
383 def do_definition(self, node):
384 print("himanshu :::::: do_definition")
385 data = node.firstChild.data
386 self.add_text('%s "\n%s'%(data, data))
388 def do_sectiondef(self, node):
389 print('Himanshu : ........SectionDef ........')
390 kind = node.attributes['kind'].value
391 print('kind = %s'%kind)
392 if kind in ('public-func', 'func', 'user-defined', 'public-type', ''):
393 self.generic_parse(node)
395 def do_header(self, node):
396 """For a user defined section def a header field is present
397 which should not be printed as such, so we comment it in the
399 data = node.firstChild.data
400 self.add_text('\n/*\n %s \n*/\n'%data)
401 # If our immediate sibling is a 'description' node then we
402 # should comment that out also and remove it from the parent
404 parent = node.parentNode
405 idx = parent.childNodes.index(node)
406 if len(parent.childNodes) >= idx + 2:
407 nd = parent.childNodes[idx+2]
408 if nd.nodeName == 'description':
409 nd = parent.removeChild(nd)
410 self.add_text('\n/*')
411 self.generic_parse(nd)
412 self.add_text('\n*/\n')
414 def do_parse_sect(self, node, kind):
415 if kind in ('date', 'rcs', 'version'):
417 elif kind == 'warning':
418 self.add_text(['\n', '* @warning '])
419 self.generic_parse(node,pad=0)
422 self.add_text('* @see ')
423 self.generic_parse(node,pad=0)
424 elif kind == 'return':
426 self.add_text('* @return ')
427 self.generic_parse(node,pad=0)
430 self.add_text('* @pre ')
431 self.generic_parse(node,pad=0)
434 self.add_text('* @note ')
435 self.generic_parse(node,pad=0)
438 self.add_text('* @post ')
439 self.generic_parse(node,pad=0)
442 self.generic_parse(node,pad=0)
444 def do_simplesect(self, node):
445 kind = node.attributes['kind'].value
446 self.simplesect_kind = kind
447 self.do_parse_sect(node, kind)
448 self.simplesect_kind = ''
450 def do_simplesectsep(self, node):
451 #tmp = node.parentnode
452 self.do_parse_sect(node, self.simplesect_kind)
454 def do_argsstring(self, node):
455 #self.generic_parse(node, pad=1)
458 def do_member(self, node):
459 kind = node.attributes['kind'].value
460 refid = node.attributes['refid'].value
461 if kind == 'function' and refid[:9] == 'namespace':
462 self.generic_parse(node)
464 def do_doxygenindex(self, node):
466 comps = node.getElementsByTagName('compound')
468 refid = c.attributes['refid'].value
469 fname = refid + '.xml'
470 if not os.path.exists(fname):
471 fname = os.path.join(self.my_dir, fname)
473 print "parsing file: %s"%fname
474 p = Doxy2SWIG(fname, self.include_function_definition, self.quiet)
476 self.pieces.extend(self.clean_pieces(p.pieces))
478 def do_emphasis(self,node):
479 self.add_text('\n* <i> ')
480 self.generic_parse(node,pad=0)
481 self.add_text(' </i>')
483 def do_heading(self,node):
484 level = node.attributes['level'].value
485 self.add_text('\n* <h%s> '%level)
486 self.generic_parse(node,pad=0)
487 self.add_text(' </h%s>\n* '%level)
489 def do_itemizedlist(self, node):
490 self.add_text(['\n* '])
491 self.list_ctr = self.list_ctr + 2
492 #self.firstListItem = self.firstListItem + 1
493 self.generic_parse(node, pad=0)
494 self.list_ctr = self.list_ctr - 2
496 def do_listitem(self, node):
497 #self.add_text('\n'* (self.firstListItem-1))
498 #self.firstlistItem = self.firstListItem - 1
499 self.add_text(' ' * self.list_ctr)
501 self.generic_parse(node, pad=0)
503 def do_programlisting(self, node):
504 self.add_text(['\n* '])
505 self.add_text(' ' * (self.list_ctr+2))
506 self.add_text('@code\n*')
507 self.generic_parse(node, pad=0)
508 self.add_text(' ' * (self.list_ctr+2))
509 self.add_text('@endcode\n*')
511 def do_codeline(self, node):
512 self.add_text(' ' * (self.list_ctr+2))
513 self.generic_parse(node, pad=1)
515 def do_highlight(self, node):
516 cl = node.attributes['class'].value
519 self.generic_parse(node, pad=0)
521 def do_sp(self, node):
524 """def do_table(self, node);
525 rows = node.attributes['rows'].value
526 cols = node.attributes['cols'].value"""
528 def do_enumvalue(self, node):
529 self.add_text('\n\n')
530 #data = node.firstChild.nodeValue
531 name = node.getElementsByTagName('name')
532 data = name[0].firstChild.data
533 print('Entering ENUM VALUE')
535 self.add_text('%csattributes ')
536 self.add_text('%s::%s\n"\n///< '%(self.cdef_data,data))
537 self.generic_parse(node, pad=0)
540 def write(self, fname):
541 o = my_open_write(fname)
543 o.write("".join(self.pieces))
545 o.write("".join(self.clean_pieces(self.pieces)))
548 def remove_trailing_spaces(self, fname):
550 with open(fname) as o:
552 clean_lines = [l.strip() for l in line if l.strip()]
554 with open('temp','w+') as f:
555 f.writelines('\n'.join(clean_lines))
558 """with open('temp','r+') as f:
561 t = textwrap.fill(text, 100, break_long_words=False)
562 t = t.replace('\n','\n* '+' '*(self.list_ctr+2))
563 #t = t.replace('1234',' '*self.list_ctr)
564 with open('temp','w+') as f:
567 os.rename('temp',fname)
570 def clean_pieces(self, pieces):
571 """Cleans the list of strings given as `pieces`. It replaces
572 multiple newlines by a maximum of 2 and returns a new list.
573 It also wraps the paragraphs nicely.
587 ret.append('\n'*count)
593 for i in _data.split('\n\n'):
594 if i == 'Parameters:' or i == 'Exceptions:':
595 ret.extend([i, '\n-----------', '\n\n'])
596 elif i.find('// File:') > -1: # leave comments alone.
597 ret.extend([i, '\n'])
599 _tmp = textwrap.fill(i.strip(), break_long_words=False)
600 _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
601 ret.extend([_tmp, '\n\n'])
605 def convert(input, output, include_function_definition=True, quiet=False):
606 p = Doxy2SWIG(input, include_function_definition, quiet)
608 p.pieces=[str(i) for i in p.pieces]
611 p.remove_trailing_spaces(output)
615 parser = optparse.OptionParser(usage)
616 parser.add_option("-n", '--no-function-definition',
620 help='do not include doxygen function definitions')
621 parser.add_option("-q", '--quiet',
625 help='be quiet and minimize output')
627 options, args = parser.parse_args()
629 parser.error("error: no input and output specified")
631 convert(args[0], args[1], not options.func_def, options.quiet)
634 if __name__ == '__main__':