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).
32 from xml.dom
import minidom
41 if hasattr(source,
"read"):
47 if hasattr(dest,
"write"):
50 return open(dest,
'w')
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
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]).
71 self.
my_dir = os.path.dirname(f.name)
72 self.
xmldoc = minidom.parse(f).documentElement
76 self.
pieces.append(
'\n// File: %s\n'%\
77 os.path.basename(f.name))
80 self.
lead_spc = re.compile(
r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
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',
91 if not include_function_definition:
92 self.
ignores.append(
'argsstring')
98 """Parses the file set in the initialization. The resulting
99 data is stored in `self.pieces`.
105 """Parse a given node. This function in turn calls the
106 `parse_<nodeType>` functions which handle the respective
110 pm = getattr(self,
"parse_%s"%node.__class__.__name__)
111 pm(node, indent=indent)
114 self.
parse(node.documentElement, indent=indent)
118 txt = txt.replace(
'\\',
r'\\\\')
119 txt = txt.replace(
'"',
r'\"')
122 if m
and len(m.group()) == len(txt):
125 self.
add_text(textwrap.fill(txt, initial_indent=
' ' * indent, subsequent_indent=
' ' * indent, break_long_words=
False))
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.
138 attr =
"do_%s" % name
139 if hasattr(self, attr):
140 handlerMethod = getattr(self, attr)
141 handlerMethod(node, indent=indent)
147 """Parse a `COMMENT_NODE`. This does nothing for now."""
151 """Adds text corresponding to `value` into `self.pieces`."""
152 if isinstance(value, (list, tuple)):
153 self.pieces.extend(value)
155 self.pieces.append(value)
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.
163 nodes = [(x.tagName, x)
for x
in node.childNodes \
164 if x.nodeType == x.ELEMENT_NODE
and \
169 """A Generic parser for arbitrary tags in a node.
173 - node: A node in the DOM.
174 - pad: `int` (default: 0)
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
187 for n
in node.childNodes:
188 self.
parse(n, indent=indent)
190 if len(self.
pieces) > npiece:
198 do_emphasis = space_parse
199 do_bold = space_parse
200 do_computeroutput = space_parse
201 do_formula = space_parse
205 data = node.firstChild.data
206 self.
add_text(
'%%feature("docstring") %s "\n'%data)
209 kind = node.attributes[
'kind'].value
210 if kind
in (
'class',
'struct'):
211 prot = node.attributes[
'prot'].value
214 names = (
'compoundname',
'briefdescription',
215 'detaileddescription',
'includes')
221 for n
in node.childNodes:
222 if n
not in first.values():
224 elif kind
in (
'file',
'namespace'):
225 nodes = node.getElementsByTagName(
'sectiondef')
235 for key, val
in node.attributes.items():
237 if val ==
'param': text =
'Parameters'
238 elif val ==
'exception': text =
'Exceptions'
239 elif val ==
'retval': text =
'Returns'
242 self.
add_text([
'\n',
'\n', text,
':',
'\n'])
251 data=node.firstChild.data
252 except AttributeError:
253 data=node.firstChild.firstChild.data
254 if data.find(
'Exception') != -1:
258 self.
add_text([
" "*indent,
"%s:\n"%data])
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
282 name = first[
'name'].firstChild.data
283 if name[:8] ==
'operator':
288 if not 'definition' in first
or \
289 kind
in [
'variable',
'typedef']:
293 defn = first[
'definition'].firstChild.data
297 self.
add_text(
'%feature("docstring") ')
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')
305 ns = ns_node[0].firstChild.data
306 self.
add_text(
' %s::%s "\n%s'%(ns, name, defn))
308 self.
add_text(
' %s "\n%s'%(name, defn))
309 elif cdef_kind
in (
'class',
'struct'):
311 anc_node = anc.getElementsByTagName(
'compoundname')
312 cname = anc_node[0].firstChild.data
313 self.
add_text(
' %s::%s "\n%s'%(cname, name, defn))
315 for n
in node.childNodes:
316 if n
not in first.values():
321 data = node.firstChild.data
322 self.
add_text(
'%s "\n%s'%(data, data))
325 kind = node.attributes[
'kind'].value
326 if kind
in (
'public-func',
'func',
'user-defined',
''):
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
333 data = node.firstChild.data
334 self.
add_text(
'\n/*\n %s \n*/\n'%data)
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)
349 kind = node.attributes[
'kind'].value
350 if kind
in (
'date',
'rcs',
'version'):
352 elif kind ==
'warning':
359 elif kind ==
'return':
369 kind = node.attributes[
'kind'].value
370 refid = node.attributes[
'refid'].value
371 if kind ==
'function' and refid[:9] ==
'namespace':
376 comps = node.getElementsByTagName(
'compound')
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)
383 print(
"parsing file: %s"%fname )
391 o.write(
"".join(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.
414 ret.append(
'\n'*count)
420 for i
in _data.split(
'\n\n'):
421 if i.find(
'// File:') > -1:
422 ret.extend([i,
'\n'])
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'])
430 def convert(input, output, include_function_definition=True, quiet=False):
431 p =
Doxy2SWIG(input, include_function_definition, quiet)
437 parser = optparse.OptionParser(usage)
438 parser.add_option(
"-n",
'--no-function-definition',
442 help=
'do not include doxygen function definitions')
443 parser.add_option(
"-q",
'--quiet',
447 help=
'be quiet and minimize output')
449 options, args = parser.parse_args()
451 parser.error(
"error: no input and output specified")
453 convert(args[0], args[1],
not options.func_def, options.quiet)
456 if __name__ ==
'__main__':