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")
374 s = "Dali::Toolkit::"
378 #print "himanshu :::::: do_memberdef...class/struct %s" %b
379 if cname == s or cname == b:
380 #print("Inside %s "%s)
383 self.add_text('%csmethodmodifiers ')
384 self.add_text([' %s::%s'%(cname, name)])
385 self.add_text(['\n','"\n/**\n'])
387 for n in node.childNodes:
388 if n not in first.values():
390 self.add_text(['\n','*/','\n','%s ";'%prot,'\n'])
392 def do_definition(self, node):
393 #print("himanshu :::::: do_definition")
394 data = node.firstChild.data
395 self.add_text('%s "\n%s'%(data, data))
397 def do_sectiondef(self, node):
398 #print('Himanshu : ........SectionDef ........')
399 kind = node.attributes['kind'].value
400 #print('kind = %s'%kind)
401 if kind in ('public-func', 'func', 'user-defined', 'public-type', ''):
402 self.generic_parse(node)
404 def do_header(self, node):
405 """For a user defined section def a header field is present
406 which should not be printed as such, so we comment it in the
408 data = node.firstChild.data
409 self.add_text('\n/*\n %s \n*/\n'%data)
410 # If our immediate sibling is a 'description' node then we
411 # should comment that out also and remove it from the parent
413 parent = node.parentNode
414 idx = parent.childNodes.index(node)
415 if len(parent.childNodes) >= idx + 2:
416 nd = parent.childNodes[idx+2]
417 if nd.nodeName == 'description':
418 nd = parent.removeChild(nd)
419 self.add_text('\n/*')
420 self.generic_parse(nd)
421 self.add_text('\n*/\n')
423 def do_parse_sect(self, node, kind):
424 if kind in ('date', 'rcs', 'version'):
426 elif kind == 'warning':
427 self.add_text(['\n', '* @warning '])
428 self.generic_parse(node,pad=0)
431 self.add_text('* @see ')
432 self.generic_parse(node,pad=0)
433 elif kind == 'return':
435 self.add_text('* @return ')
436 self.generic_parse(node,pad=0)
439 self.add_text('* @pre ')
440 self.generic_parse(node,pad=0)
443 self.add_text('* @note ')
444 self.generic_parse(node,pad=0)
447 self.add_text('* @post ')
448 self.generic_parse(node,pad=0)
449 elif kind == 'since':
451 self.add_text('* @SINCE_')
452 self.generic_parse(node,pad=0)
455 self.generic_parse(node,pad=0)
457 def do_simplesect(self, node):
458 kind = node.attributes['kind'].value
459 self.simplesect_kind = kind
460 self.do_parse_sect(node, kind)
461 self.simplesect_kind = ''
463 def do_simplesectsep(self, node):
464 #tmp = node.parentnode
465 self.do_parse_sect(node, self.simplesect_kind)
467 def do_argsstring(self, node):
468 #self.generic_parse(node, pad=1)
471 def do_member(self, node):
472 kind = node.attributes['kind'].value
473 refid = node.attributes['refid'].value
474 if kind == 'function' and refid[:9] == 'namespace':
475 self.generic_parse(node)
477 def do_doxygenindex(self, node):
479 comps = node.getElementsByTagName('compound')
481 refid = c.attributes['refid'].value
482 fname = refid + '.xml'
483 if not os.path.exists(fname):
484 fname = os.path.join(self.my_dir, fname)
486 #print "parsing file: %s"%fname
487 p = Doxy2SWIG(fname, self.include_function_definition, self.quiet)
489 self.pieces.extend(self.clean_pieces(p.pieces))
491 def do_emphasis(self,node):
492 self.add_text('\n* <i> ')
493 self.generic_parse(node,pad=0)
494 self.add_text(' </i>')
496 def do_heading(self,node):
497 level = node.attributes['level'].value
498 self.add_text('\n* <h%s> '%level)
499 self.generic_parse(node,pad=0)
500 self.add_text(' </h%s>\n* '%level)
502 def do_itemizedlist(self, node):
503 self.add_text(['\n* '])
504 self.list_ctr = self.list_ctr + 2
505 #self.firstListItem = self.firstListItem + 1
506 self.generic_parse(node, pad=0)
507 self.list_ctr = self.list_ctr - 2
509 def do_listitem(self, node):
510 #self.add_text('\n'* (self.firstListItem-1))
511 #self.firstlistItem = self.firstListItem - 1
512 self.add_text(' ' * self.list_ctr)
514 self.generic_parse(node, pad=0)
516 def do_programlisting(self, node):
517 self.add_text(['\n* '])
518 self.add_text(' ' * (self.list_ctr+2))
519 self.add_text('@code\n*')
520 self.generic_parse(node, pad=0)
521 self.add_text(' ' * (self.list_ctr+2))
522 self.add_text('@endcode\n*')
524 def do_codeline(self, node):
525 self.add_text(' ' * (self.list_ctr+2))
526 self.generic_parse(node, pad=1)
528 def do_highlight(self, node):
529 cl = node.attributes['class'].value
532 self.generic_parse(node, pad=0)
534 def do_sp(self, node):
537 """def do_table(self, node);
538 rows = node.attributes['rows'].value
539 cols = node.attributes['cols'].value"""
541 def do_enumvalue(self, node):
542 self.generic_parse(node, pad=0)
544 def write(self, fname):
545 o = my_open_write(fname)
547 o.write(u"".join(self.pieces).encode('utf-8'))
549 o.write("".join(self.clean_pieces(self.pieces)))
552 def remove_trailing_spaces(self, fname):
554 with open(fname) as o:
556 clean_lines = [l.strip() for l in line if l.strip()]
558 with open('temp','w+') as f:
559 f.writelines('\n'.join(clean_lines))
562 """with open('temp','r+') as f:
565 t = textwrap.fill(text, 100, break_long_words=False)
566 t = t.replace('\n','\n* '+' '*(self.list_ctr+2))
567 #t = t.replace('1234',' '*self.list_ctr)
568 with open('temp','w+') as f:
571 os.rename('temp',fname)
574 def clean_pieces(self, pieces):
575 """Cleans the list of strings given as `pieces`. It replaces
576 multiple newlines by a maximum of 2 and returns a new list.
577 It also wraps the paragraphs nicely.
591 ret.append('\n'*count)
597 for i in _data.split('\n\n'):
598 if i == 'Parameters:' or i == 'Exceptions:':
599 ret.extend([i, '\n-----------', '\n\n'])
600 elif i.find('// File:') > -1: # leave comments alone.
601 ret.extend([i, '\n'])
603 _tmp = textwrap.fill(i.strip(), break_long_words=False)
604 _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
605 ret.extend([_tmp, '\n\n'])
609 def convert(input, output, include_function_definition=True, quiet=False):
610 p = Doxy2SWIG(input, include_function_definition, quiet)
612 #p.pieces=[str(i) for i in p.pieces]
615 p.remove_trailing_spaces(output)
619 parser = optparse.OptionParser(usage)
620 parser.add_option("-n", '--no-function-definition',
624 help='do not include doxygen function definitions')
625 parser.add_option("-q", '--quiet',
629 help='be quiet and minimize output')
631 options, args = parser.parse_args()
633 parser.error("error: no input and output specified")
635 convert(args[0], args[1], not options.func_def, options.quiet)
638 if __name__ == '__main__':