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


gnsstk
Author(s):
autogenerated on Wed Oct 25 2023 02:40:38