PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/src/.doxy2swig.py

https://code.google.com/
Python | 450 lines | 413 code | 13 blank | 24 comment | 18 complexity | 0567296ef6f22768fb8b795c8a7a8c4e MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, BSD-3-Clause
  1. #!/usr/bin/env python
  2. """Doxygen XML to SWIG docstring converter.
  3. Usage:
  4. doxy2swig.py [options] input.xml output.i
  5. Converts Doxygen generated XML files into a file containing docstrings
  6. that can be used by SWIG-1.3.x. Note that you need to get SWIG
  7. version > 1.3.23 or use Robin Dunn's docstring patch to be able to use
  8. the resulting output.
  9. input.xml is your doxygen generated XML file and output.i is where the
  10. output will be written (the file will be clobbered).
  11. """
  12. ######################################################################
  13. #
  14. # This code is implemented using Mark Pilgrim's code as a guideline:
  15. # http://www.faqs.org/docs/diveintopython/kgp_divein.html
  16. #
  17. # Author: Prabhu Ramachandran
  18. # License: BSD style
  19. #
  20. # Thanks:
  21. # Johan Hake: the include_function_definition feature
  22. # Bill Spotz: bug reports and testing.
  23. #
  24. ######################################################################
  25. from xml.dom import minidom
  26. import re
  27. import textwrap
  28. import sys
  29. import types
  30. import os.path
  31. import optparse
  32. def my_open_read(source):
  33. if hasattr(source, "read"):
  34. return source
  35. else:
  36. return open(source)
  37. def my_open_write(dest):
  38. if hasattr(dest, "write"):
  39. return dest
  40. else:
  41. return open(dest, 'w')
  42. class Doxy2SWIG:
  43. """Converts Doxygen generated XML files into a file containing
  44. docstrings that can be used by SWIG-1.3.x that have support for
  45. feature("docstring"). Once the data is parsed it is stored in
  46. self.pieces.
  47. """
  48. def __init__(self, src, include_function_definition=True, quiet=False):
  49. """Initialize the instance given a source object. `src` can
  50. be a file or filename. If you do not want to include function
  51. definitions from doxygen then set
  52. `include_function_definition` to `False`. This is handy since
  53. this allows you to use the swig generated function definition
  54. using %feature("autodoc", [0,1]).
  55. """
  56. f = my_open_read(src)
  57. self.my_dir = os.path.dirname(f.name)
  58. self.xmldoc = minidom.parse(f).documentElement
  59. f.close()
  60. self.pieces = []
  61. self.pieces.append('\n// File: %s\n'%\
  62. os.path.basename(f.name))
  63. self.space_re = re.compile(r'\s+')
  64. self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
  65. self.multi = 0
  66. self.ignores = ['inheritancegraph', 'param', 'listofallmembers',
  67. 'innerclass', 'name', 'declname', 'incdepgraph',
  68. 'invincdepgraph', 'programlisting', 'type',
  69. 'references', 'referencedby', 'location',
  70. 'collaborationgraph', 'reimplements',
  71. 'reimplementedby', 'derivedcompoundref',
  72. 'basecompoundref']
  73. #self.generics = []
  74. self.include_function_definition = include_function_definition
  75. if not include_function_definition:
  76. self.ignores.append('argsstring')
  77. self.quiet = quiet
  78. def generate(self):
  79. """Parses the file set in the initialization. The resulting
  80. data is stored in `self.pieces`.
  81. """
  82. self.parse(self.xmldoc)
  83. def parse(self, node):
  84. """Parse a given node. This function in turn calls the
  85. `parse_<nodeType>` functions which handle the respective
  86. nodes.
  87. """
  88. pm = getattr(self, "parse_%s"%node.__class__.__name__)
  89. pm(node)
  90. def parse_Document(self, node):
  91. self.parse(node.documentElement)
  92. def parse_Text(self, node):
  93. txt = node.data
  94. txt = txt.replace('\\', r'\\\\')
  95. txt = txt.replace('"', r'\"')
  96. # ignore pure whitespace
  97. m = self.space_re.match(txt)
  98. if m and len(m.group()) == len(txt):
  99. pass
  100. else:
  101. self.add_text(textwrap.fill(txt, break_long_words=False))
  102. def parse_Element(self, node):
  103. """Parse an `ELEMENT_NODE`. This calls specific
  104. `do_<tagName>` handers for different elements. If no handler
  105. is available the `generic_parse` method is called. All
  106. tagNames specified in `self.ignores` are simply ignored.
  107. """
  108. name = node.tagName
  109. ignores = self.ignores
  110. if name in ignores:
  111. return
  112. attr = "do_%s" % name
  113. if hasattr(self, attr):
  114. handlerMethod = getattr(self, attr)
  115. handlerMethod(node)
  116. else:
  117. self.generic_parse(node)
  118. #if name not in self.generics: self.generics.append(name)
  119. def parse_Comment(self, node):
  120. """Parse a `COMMENT_NODE`. This does nothing for now."""
  121. return
  122. def add_text(self, value):
  123. """Adds text corresponding to `value` into `self.pieces`."""
  124. if type(value) in (types.ListType, types.TupleType):
  125. self.pieces.extend(value)
  126. else:
  127. self.pieces.append(value)
  128. def get_specific_nodes(self, node, names):
  129. """Given a node and a sequence of strings in `names`, return a
  130. dictionary containing the names as keys and child
  131. `ELEMENT_NODEs`, that have a `tagName` equal to the name.
  132. """
  133. nodes = [(x.tagName, x) for x in node.childNodes \
  134. if x.nodeType == x.ELEMENT_NODE and \
  135. x.tagName in names]
  136. return dict(nodes)
  137. def generic_parse(self, node, pad=0):
  138. """A Generic parser for arbitrary tags in a node.
  139. Parameters:
  140. - node: A node in the DOM.
  141. - pad: `int` (default: 0)
  142. If 0 the node data is not padded with newlines. If 1 it
  143. appends a newline after parsing the childNodes. If 2 it
  144. pads before and after the nodes are processed. Defaults to
  145. 0.
  146. """
  147. npiece = 0
  148. if pad:
  149. npiece = len(self.pieces)
  150. if pad == 2:
  151. self.add_text('\n')
  152. for n in node.childNodes:
  153. self.parse(n)
  154. if pad:
  155. if len(self.pieces) > npiece:
  156. self.add_text('\n')
  157. def space_parse(self, node):
  158. self.add_text(' ')
  159. self.generic_parse(node)
  160. do_ref = space_parse
  161. do_emphasis = space_parse
  162. do_bold = space_parse
  163. do_computeroutput = space_parse
  164. do_formula = space_parse
  165. def do_compoundname(self, node):
  166. self.add_text('\n\n')
  167. data = node.firstChild.data
  168. self.add_text('%%feature("docstring") %s "\n'%data)
  169. def do_compounddef(self, node):
  170. kind = node.attributes['kind'].value
  171. if kind in ('class', 'struct'):
  172. prot = node.attributes['prot'].value
  173. if prot <> 'public':
  174. return
  175. names = ('compoundname', 'briefdescription',
  176. 'detaileddescription', 'includes')
  177. first = self.get_specific_nodes(node, names)
  178. for n in names:
  179. if first.has_key(n):
  180. self.parse(first[n])
  181. self.add_text(['";','\n'])
  182. for n in node.childNodes:
  183. if n not in first.values():
  184. self.parse(n)
  185. elif kind in ('file', 'namespace'):
  186. nodes = node.getElementsByTagName('sectiondef')
  187. for n in nodes:
  188. self.parse(n)
  189. def do_includes(self, node):
  190. self.add_text('C++ includes: ')
  191. self.generic_parse(node, pad=1)
  192. def do_parameterlist(self, node):
  193. text='unknown'
  194. for key, val in node.attributes.items():
  195. if key == 'kind':
  196. if val == 'param': text = 'Parameters'
  197. elif val == 'exception': text = 'Exceptions'
  198. else: text = val
  199. break
  200. self.add_text(['\n', '\n', text, ':', '\n'])
  201. self.generic_parse(node, pad=1)
  202. def do_para(self, node):
  203. self.add_text('\n')
  204. self.generic_parse(node, pad=1)
  205. def do_parametername(self, node):
  206. self.add_text('\n')
  207. try:
  208. data=node.firstChild.data
  209. except AttributeError: # perhaps a <ref> tag in it
  210. data=node.firstChild.firstChild.data
  211. if data.find('Exception') != -1:
  212. self.add_text(data)
  213. else:
  214. self.add_text("%s: "%data)
  215. def do_parameterdefinition(self, node):
  216. self.generic_parse(node, pad=1)
  217. def do_detaileddescription(self, node):
  218. self.generic_parse(node, pad=1)
  219. def do_briefdescription(self, node):
  220. self.generic_parse(node, pad=1)
  221. def do_memberdef(self, node):
  222. prot = node.attributes['prot'].value
  223. id = node.attributes['id'].value
  224. kind = node.attributes['kind'].value
  225. tmp = node.parentNode.parentNode.parentNode
  226. compdef = tmp.getElementsByTagName('compounddef')[0]
  227. cdef_kind = compdef.attributes['kind'].value
  228. if prot == 'public':
  229. first = self.get_specific_nodes(node, ('definition', 'name'))
  230. name = first['name'].firstChild.data
  231. if name[:8] == 'operator': # Don't handle operators yet.
  232. return
  233. if not first.has_key('definition') or \
  234. kind in ['variable', 'typedef']:
  235. return
  236. if self.include_function_definition:
  237. defn = first['definition'].firstChild.data
  238. else:
  239. defn = ""
  240. self.add_text('\n')
  241. self.add_text('%feature("docstring") ')
  242. anc = node.parentNode.parentNode
  243. if cdef_kind in ('file', 'namespace'):
  244. ns_node = anc.getElementsByTagName('innernamespace')
  245. if not ns_node and cdef_kind == 'namespace':
  246. ns_node = anc.getElementsByTagName('compoundname')
  247. if ns_node:
  248. ns = ns_node[0].firstChild.data
  249. self.add_text(' %s::%s "\n%s'%(ns, name, defn))
  250. else:
  251. self.add_text(' %s "\n%s'%(name, defn))
  252. elif cdef_kind in ('class', 'struct'):
  253. # Get the full function name.
  254. anc_node = anc.getElementsByTagName('compoundname')
  255. cname = anc_node[0].firstChild.data
  256. self.add_text(' %s::%s "\n%s'%(cname, name, defn))
  257. for n in node.childNodes:
  258. if n not in first.values():
  259. self.parse(n)
  260. self.add_text(['";', '\n'])
  261. def do_definition(self, node):
  262. data = node.firstChild.data
  263. self.add_text('%s "\n%s'%(data, data))
  264. def do_sectiondef(self, node):
  265. kind = node.attributes['kind'].value
  266. if kind in ('public-func', 'func', 'user-defined', ''):
  267. self.generic_parse(node)
  268. def do_header(self, node):
  269. """For a user defined section def a header field is present
  270. which should not be printed as such, so we comment it in the
  271. output."""
  272. data = node.firstChild.data
  273. self.add_text('\n/*\n %s \n*/\n'%data)
  274. # If our immediate sibling is a 'description' node then we
  275. # should comment that out also and remove it from the parent
  276. # node's children.
  277. parent = node.parentNode
  278. idx = parent.childNodes.index(node)
  279. if len(parent.childNodes) >= idx + 2:
  280. nd = parent.childNodes[idx+2]
  281. if nd.nodeName == 'description':
  282. nd = parent.removeChild(nd)
  283. self.add_text('\n/*')
  284. self.generic_parse(nd)
  285. self.add_text('\n*/\n')
  286. def do_simplesect(self, node):
  287. kind = node.attributes['kind'].value
  288. if kind in ('date', 'rcs', 'version'):
  289. pass
  290. elif kind == 'warning':
  291. self.add_text(['\n', 'WARNING: '])
  292. self.generic_parse(node)
  293. elif kind == 'see':
  294. self.add_text('\n')
  295. self.add_text('See: ')
  296. self.generic_parse(node)
  297. else:
  298. self.generic_parse(node)
  299. def do_argsstring(self, node):
  300. self.generic_parse(node, pad=1)
  301. def do_member(self, node):
  302. kind = node.attributes['kind'].value
  303. refid = node.attributes['refid'].value
  304. if kind == 'function' and refid[:9] == 'namespace':
  305. self.generic_parse(node)
  306. def do_doxygenindex(self, node):
  307. self.multi = 1
  308. comps = node.getElementsByTagName('compound')
  309. for c in comps:
  310. refid = c.attributes['refid'].value
  311. fname = refid + '.xml'
  312. if not os.path.exists(fname):
  313. fname = os.path.join(self.my_dir, fname)
  314. if not self.quiet:
  315. print "parsing file: %s"%fname
  316. p = Doxy2SWIG(fname, self.include_function_definition, self.quiet)
  317. p.generate()
  318. self.pieces.extend(self.clean_pieces(p.pieces))
  319. def write(self, fname):
  320. o = my_open_write(fname)
  321. if self.multi:
  322. o.write("".join(self.pieces))
  323. else:
  324. o.write("".join(self.clean_pieces(self.pieces)))
  325. o.close()
  326. def clean_pieces(self, pieces):
  327. """Cleans the list of strings given as `pieces`. It replaces
  328. multiple newlines by a maximum of 2 and returns a new list.
  329. It also wraps the paragraphs nicely.
  330. """
  331. ret = []
  332. count = 0
  333. for i in pieces:
  334. if i == '\n':
  335. count = count + 1
  336. else:
  337. if i == '";':
  338. if count:
  339. ret.append('\n')
  340. elif count > 2:
  341. ret.append('\n\n')
  342. elif count:
  343. ret.append('\n'*count)
  344. count = 0
  345. ret.append(i)
  346. _data = "".join(ret)
  347. ret = []
  348. for i in _data.split('\n\n'):
  349. if i == 'Parameters:' or i == 'Exceptions:':
  350. ret.extend([i, '\n-----------', '\n\n'])
  351. elif i.find('// File:') > -1: # leave comments alone.
  352. ret.extend([i, '\n'])
  353. else:
  354. _tmp = textwrap.fill(i.strip(), break_long_words=False)
  355. _tmp = self.lead_spc.sub(r'\1"\2', _tmp)
  356. ret.extend([_tmp, '\n\n'])
  357. return ret
  358. def convert(input, output, include_function_definition=True, quiet=False):
  359. p = Doxy2SWIG(input, include_function_definition, quiet)
  360. p.generate()
  361. p.write(output)
  362. def main():
  363. usage = __doc__
  364. parser = optparse.OptionParser(usage)
  365. parser.add_option("-n", '--no-function-definition',
  366. action='store_true',
  367. default=False,
  368. dest='func_def',
  369. help='do not include doxygen function definitions')
  370. parser.add_option("-q", '--quiet',
  371. action='store_true',
  372. default=False,
  373. dest='quiet',
  374. help='be quiet and minimise output')
  375. options, args = parser.parse_args()
  376. if len(args) != 2:
  377. parser.error("error: no input and output specified")
  378. convert(args[0], args[1], not options.func_def, options.quiet)
  379. if __name__ == '__main__':
  380. main()