Salome HOME
Merge branch 'gni/evolution'
[modules/shaper.git] / scripts / doxy2swig.py
1 #!/usr/bin/env python3
2 """doxy2swig.py [options] index.xml output.i
3
4 Doxygen XML to SWIG docstring converter (improved version).
5
6 Converts Doxygen generated XML files into a file containing docstrings
7 for use by SWIG.
8
9 index.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 # The current version of this code is hosted on a github repository:
14 #   https://github.com/m7thon/doxy2swig
15 #
16 # This code is implemented using Mark Pilgrim's code as a guideline:
17 #   http://www.faqs.org/docs/diveintopython/kgp_divein.html
18 #
19 # Original Author: Prabhu Ramachandran
20 # Modified by:     Michael Thon (June 2015)
21 # License: BSD style
22 #
23 # Thanks:
24 #   Johan Hake:  the include_function_definition feature
25 #   Bill Spotz:  bug reports and testing.
26 #   Sebastian Henschel:   Misc. enhancements.
27 #
28 # Changes:
29 # June 2015 (Michael Thon):
30 #   - class documentation:
31 #     -c: add constructor call signatures and a "Constructors" section
32 #         collecting the respective docs (e.g. for python)
33 #     -a: add "Attributes" section collecting the documentation for member
34 #         variables (e.g. for python)
35 #   - overloaded functions:
36 #     -o: collect all documentation into one "Overloaded function" section
37 #   - option to include function definition / signature renamed to -f
38 #   - formatting:
39 #     + included function signatures slightly reformatted
40 #     + option (-t) to turn off/on type information for funciton signatures
41 #     + lists (incl. nested and ordered)
42 #     + attempt to produce docstrings that render nicely as markdown
43 #     + translate code, emphasis, bold, linebreak, hruler, blockquote,
44 #       verbatim, heading tags to markdown
45 #     + new text-wrapping and option -w to specify the text width
46 #
47
48 from xml.dom import minidom
49 import re
50 import textwrap
51 import sys
52 import os.path
53 import optparse
54
55
56 def my_open_read(source):
57     if hasattr(source, "read"):
58         return source
59     else:
60         try:
61             return open(source, encoding='utf-8')
62         except TypeError:
63             return open(source)
64
65 def my_open_write(dest):
66     if hasattr(dest, "write"):
67         return dest
68     else:
69         try:
70             return open(dest, 'w', encoding='utf-8')
71         except TypeError:
72             return open(dest, 'w')
73
74 # MARK: Text handling:
75 def shift(txt, indent = '    ', prepend = ''):
76     """Return a list corresponding to the lines of text in the `txt` list
77     indented by `indent`. Prepend instead the string given in `prepend` to the
78     beginning of the first line. Note that if len(prepend) > len(indent), then
79     `prepend` will be truncated (doing better is tricky!). This preserves a
80     special '' entry at the end of `txt` (see `do_para` for the meaning).
81     """
82     if type(indent) is int:
83         indent = indent * ' '
84     special_end = txt[-1:] == ['']
85     lines = ''.join(txt).splitlines(True)
86     for i in range(1,len(lines)):
87         if lines[i].strip() or indent.strip():
88             lines[i] = indent + lines[i]
89     if not lines:
90         return prepend
91     prepend = prepend[:len(indent)]
92     indent = indent[len(prepend):]
93     lines[0] = prepend + indent + lines[0]
94     ret = [''.join(lines)]
95     if special_end:
96         ret.append('')
97     return ret
98
99 class Doxy2SWIG:
100     """Converts Doxygen generated XML files into a file containing
101     docstrings that can be used by SWIG-1.3.x that have support for
102     feature("docstring").  Once the data is parsed it is stored in
103     self.pieces.
104
105     """
106
107     def __init__(self, src,
108                  with_function_signature = False,
109                  with_type_info = False,
110                  with_constructor_list = False,
111                  with_attribute_list = False,
112                  with_overloaded_functions = False,
113                  textwidth = 80,
114                  quiet = False):
115         """Initialize the instance given a source object.  `src` can
116         be a file or filename.  If you do not want to include function
117         definitions from doxygen then set
118         `include_function_definition` to `False`.  This is handy since
119         this allows you to use the swig generated function definition
120         using %feature("autodoc", [0,1]).
121
122         """
123         # options:
124         self.with_function_signature = with_function_signature
125         self.with_type_info = with_type_info
126         self.with_constructor_list = with_constructor_list
127         self.with_attribute_list = with_attribute_list
128         self.with_overloaded_functions = with_overloaded_functions
129         self.textwidth = textwidth
130         self.quiet = quiet
131
132         # state:
133         self.indent = 0
134         self.listitem = ''
135         self.pieces = []
136
137         f = my_open_read(src)
138         self.my_dir = os.path.dirname(f.name)
139         self.xmldoc = minidom.parse(f).documentElement
140         f.close()
141
142         self.pieces.append('\n// File: %s\n' %
143                            os.path.basename(f.name))
144
145         self.space_re = re.compile(r'\s+')
146         self.lead_spc = re.compile(r'^(%feature\S+\s+\S+\s*?)"\s+(\S)')
147         self.multi = 0
148         self.ignores = ['inheritancegraph', 'param', 'listofallmembers',
149                         'innerclass', 'name', 'declname', 'incdepgraph',
150                         'invincdepgraph', 'programlisting', 'type',
151                         'references', 'referencedby', 'location',
152                         'collaborationgraph', 'reimplements',
153                         'reimplementedby', 'derivedcompoundref',
154                         'basecompoundref',
155                         'argsstring', 'definition', 'exceptions']
156         #self.generics = []
157
158     def generate(self):
159         """Parses the file set in the initialization.  The resulting
160         data is stored in `self.pieces`.
161
162         """
163         self.parse(self.xmldoc)
164
165     def write(self, fname):
166         o = my_open_write(fname)
167         o.write(''.join(self.pieces))
168         o.write('\n')
169         o.close()
170
171     def parse(self, node):
172         """Parse a given node.  This function in turn calls the
173         `parse_<nodeType>` functions which handle the respective
174         nodes.
175
176         """
177         pm = getattr(self, "parse_%s" % node.__class__.__name__)
178         pm(node)
179
180     def parse_Document(self, node):
181         self.parse(node.documentElement)
182
183     def parse_Text(self, node):
184         txt = node.data
185         if txt == ' ':
186             # this can happen when two tags follow in a text, e.g.,
187             # " ...</emph> <formaula>$..." etc.
188             # here we want to keep the space.
189             self.add_text(txt)
190             return
191         txt = txt.replace('\\', r'\\')
192         txt = txt.replace('"', r'\"')
193         # ignore pure whitespace
194         m = self.space_re.match(txt)
195         if m and len(m.group()) == len(txt):
196             pass
197         else:
198             self.add_text(txt)
199
200     def parse_Comment(self, node):
201         """Parse a `COMMENT_NODE`.  This does nothing for now."""
202         return
203
204     def parse_Element(self, node):
205         """Parse an `ELEMENT_NODE`.  This calls specific
206         `do_<tagName>` handers for different elements.  If no handler
207         is available the `subnode_parse` method is called.  All
208         tagNames specified in `self.ignores` are simply ignored.
209
210         """
211         name = node.tagName
212         ignores = self.ignores
213         if name in ignores:
214             return
215         attr = "do_%s" % name
216         if hasattr(self, attr):
217             handlerMethod = getattr(self, attr)
218             handlerMethod(node)
219         else:
220             self.subnode_parse(node)
221             #if name not in self.generics: self.generics.append(name)
222
223 # MARK: Special format parsing
224     def subnode_parse(self, node, pieces=None, indent=0, ignore=[], restrict=None):
225         """Parse the subnodes of a given node. Subnodes with tags in the
226         `ignore` list are ignored. If pieces is given, use this as target for
227         the parse results instead of self.pieces. Indent all lines by the amount
228         given in `indent`. Note that the initial content in `pieces` is not
229         indented. The final result is in any case added to self.pieces."""
230         if pieces is not None:
231             old_pieces, self.pieces = self.pieces, pieces
232         else:
233             old_pieces = []
234         if type(indent) is int:
235             indent = indent * ' '
236         if len(indent) > 0:
237             pieces = ''.join(self.pieces)
238             i_piece = pieces[:len(indent)]
239             if self.pieces[-1:] == ['']:
240                 self.pieces = [pieces[len(indent):]] + ['']
241             elif self.pieces != []:
242                 self.pieces = [pieces[len(indent):]]
243         self.indent += len(indent)
244         for n in node.childNodes:
245             if restrict is not None:
246                 if n.nodeType == n.ELEMENT_NODE and n.tagName in restrict:
247                     self.parse(n)
248             elif n.nodeType != n.ELEMENT_NODE or n.tagName not in ignore:
249                 self.parse(n)
250         if len(indent) > 0:
251             self.pieces = shift(self.pieces, indent, i_piece)
252         self.indent -= len(indent)
253         old_pieces.extend(self.pieces)
254         self.pieces = old_pieces
255
256     def surround_parse(self, node, pre_char, post_char):
257         """Parse the subnodes of a given node. Subnodes with tags in the
258         `ignore` list are ignored. Prepend `pre_char` and append `post_char` to
259         the output in self.pieces."""
260         self.add_text(pre_char)
261         self.subnode_parse(node)
262         self.add_text(post_char)
263
264 # MARK: Helper functions
265     def get_specific_subnodes(self, node, name, recursive=0):
266         """Given a node and a name, return a list of child `ELEMENT_NODEs`, that
267         have a `tagName` matching the `name`. Search recursively for `recursive`
268         levels.
269         """
270         children = [x for x in node.childNodes if x.nodeType == x.ELEMENT_NODE]
271         ret = [x for x in children if x.tagName == name]
272         if recursive > 0:
273             for x in children:
274                 ret.extend(self.get_specific_subnodes(x, name, recursive-1))
275         return ret
276
277     def get_specific_nodes(self, node, names):
278         """Given a node and a sequence of strings in `names`, return a
279         dictionary containing the names as keys and child
280         `ELEMENT_NODEs`, that have a `tagName` equal to the name.
281
282         """
283         nodes = [(x.tagName, x) for x in node.childNodes
284                  if x.nodeType == x.ELEMENT_NODE and
285                  x.tagName in names]
286         return dict(nodes)
287
288     def add_text(self, value):
289         """Adds text corresponding to `value` into `self.pieces`."""
290         if isinstance(value, (list, tuple)):
291             self.pieces.extend(value)
292         else:
293             self.pieces.append(value)
294
295     def start_new_paragraph(self):
296         """Make sure to create an empty line. This is overridden, if the previous
297         text ends with the special marker ''. In that case, nothing is done.
298         """
299         if self.pieces[-1:] == ['']: # respect special marker
300             return
301         elif self.pieces == []: # first paragraph, add '\n', override with ''
302             self.pieces = ['\n']
303         elif self.pieces[-1][-1:] != '\n': # previous line not ended
304             self.pieces.extend(['  \n' ,'\n'])
305         else: #default
306             self.pieces.append('\n')
307
308     def add_line_with_subsequent_indent(self, line, indent=4):
309         """Add line of text and wrap such that subsequent lines are indented
310         by `indent` spaces.
311         """
312         if isinstance(line, (list, tuple)):
313             line = ''.join(line)
314         line = line.strip()
315         width = self.textwidth-self.indent-indent
316         wrapped_lines = textwrap.wrap(line[indent:], width=width)
317         for i in range(len(wrapped_lines)):
318             if wrapped_lines[i] != '':
319                 wrapped_lines[i] = indent * ' ' + wrapped_lines[i]
320         self.pieces.append(line[:indent] + '\n'.join(wrapped_lines)[indent:] + '  \n')
321
322     def extract_text(self, node):
323         """Return the string representation of the node or list of nodes by parsing the
324         subnodes, but returning the result as a string instead of adding it to `self.pieces`.
325         Note that this allows extracting text even if the node is in the ignore list.
326         """
327         if not isinstance(node, (list, tuple)):
328             node = [node]
329         pieces, self.pieces = self.pieces, ['']
330         for n in node:
331             for sn in n.childNodes:
332                 self.parse(sn)
333         ret = ''.join(self.pieces)
334         self.pieces = pieces
335         return ret
336
337     def get_function_signature(self, node):
338         """Returns the function signature string for memberdef nodes."""
339         name = self.extract_text(self.get_specific_subnodes(node, 'name'))
340         if self.with_type_info:
341             argsstring = self.extract_text(self.get_specific_subnodes(node, 'argsstring'))
342         else:
343             argsstring = []
344             param_id = 1
345             for n_param in self.get_specific_subnodes(node, 'param'):
346                 declname = self.extract_text(self.get_specific_subnodes(n_param, 'declname'))
347                 if not declname:
348                     declname = 'arg' + str(param_id)
349                 defval = self.extract_text(self.get_specific_subnodes(n_param, 'defval'))
350                 if defval:
351                     defval = '=' + defval
352                 argsstring.append(declname + defval)
353                 param_id = param_id + 1
354             argsstring = '(' + ', '.join(argsstring) + ')'
355         type = self.extract_text(self.get_specific_subnodes(node, 'type'))
356         function_definition = name + argsstring
357         if type != '' and type != 'void':
358             function_definition = function_definition + ' -> ' + type
359         return '`' + function_definition + '`  '
360
361 # MARK: Special parsing tasks (need to be called manually)
362     def make_constructor_list(self, constructor_nodes, classname):
363         """Produces the "Constructors" section and the constructor signatures
364         (since swig does not do so for classes) for class docstrings."""
365         if constructor_nodes == []:
366             return
367         self.add_text(['\n', 'Constructors',
368                        '\n', '------------'])
369         for n in constructor_nodes:
370             self.add_text('\n')
371             self.add_line_with_subsequent_indent('* ' + self.get_function_signature(n))
372             self.subnode_parse(n, pieces = [], indent=4, ignore=['definition', 'name'])
373
374     def make_attribute_list(self, node):
375         """Produces the "Attributes" section in class docstrings for public
376         member variables (attributes).
377         """
378         atr_nodes = []
379         for n in self.get_specific_subnodes(node, 'memberdef', recursive=2):
380             if n.attributes['kind'].value == 'variable' and n.attributes['prot'].value == 'public':
381                 atr_nodes.append(n)
382         if not atr_nodes:
383             return
384         self.add_text(['\n', 'Attributes',
385                        '\n', '----------'])
386         for n in atr_nodes:
387             name = self.extract_text(self.get_specific_subnodes(n, 'name'))
388             self.add_text(['\n* ', '`', name, '`', ' : '])
389             self.add_text(['`', self.extract_text(self.get_specific_subnodes(n, 'type')), '`'])
390             self.add_text('  \n')
391             restrict = ['briefdescription', 'detaileddescription']
392             self.subnode_parse(n, pieces=[''], indent=4, restrict=restrict)
393
394     def get_memberdef_nodes_and_signatures(self, node, kind):
395         """Collects the memberdef nodes and corresponding signatures that
396         correspond to public function entries that are at most depth 2 deeper
397         than the current (compounddef) node. Returns a dictionary with
398         function signatures (what swig expects after the %feature directive)
399         as keys, and a list of corresponding memberdef nodes as values."""
400         sig_dict = {}
401         sig_prefix = ''
402         if kind in ('file', 'namespace'):
403             ns_node = node.getElementsByTagName('innernamespace')
404             if not ns_node and kind == 'namespace':
405                 ns_node = node.getElementsByTagName('compoundname')
406             if ns_node:
407                 sig_prefix = self.extract_text(ns_node[0]) + '::'
408         elif kind in ('class', 'struct'):
409             # Get the full function name.
410             cn_node = node.getElementsByTagName('compoundname')
411             sig_prefix = self.extract_text(cn_node[0]) + '::'
412
413         md_nodes = self.get_specific_subnodes(node, 'memberdef', recursive=2)
414         for n in md_nodes:
415             if n.attributes['prot'].value != 'public':
416                 continue
417             if n.attributes['kind'].value in ['variable', 'typedef']:
418                 continue
419             if not self.get_specific_subnodes(n, 'definition'):
420                 continue
421             name = self.extract_text(self.get_specific_subnodes(n, 'name'))
422             if name[:8] == 'operator':
423                 continue
424             sig = sig_prefix + name
425             if sig in sig_dict:
426                 sig_dict[sig].append(n)
427             else:
428                 sig_dict[sig] = [n]
429         return sig_dict
430
431     def handle_typical_memberdefs_no_overload(self, signature, memberdef_nodes):
432         """Produce standard documentation for memberdef_nodes."""
433         for n in memberdef_nodes:
434             self.add_text(['\n', '%feature("docstring") ', signature, ' "', '\n'])
435             if self.with_function_signature:
436                 self.add_line_with_subsequent_indent(self.get_function_signature(n))
437             self.subnode_parse(n, pieces=[], ignore=['definition', 'name'])
438             self.add_text(['";', '\n'])
439
440     def handle_typical_memberdefs(self, signature, memberdef_nodes):
441         """Produces docstring entries containing an "Overloaded function"
442         section with the documentation for each overload, if the function is
443         overloaded and self.with_overloaded_functions is set. Else, produce
444         normal documentation.
445         """
446         if len(memberdef_nodes) == 1 or not self.with_overloaded_functions:
447             self.handle_typical_memberdefs_no_overload(signature, memberdef_nodes)
448             return
449
450         self.add_text(['\n', '%feature("docstring") ', signature, ' "', '\n'])
451         if self.with_function_signature:
452             for n in memberdef_nodes:
453                 self.add_line_with_subsequent_indent(self.get_function_signature(n))
454         self.add_text('\n')
455         self.add_text(['Overloaded function', '\n',
456                        '-------------------'])
457         for n in memberdef_nodes:
458             self.add_text('\n')
459             self.add_line_with_subsequent_indent('* ' + self.get_function_signature(n))
460             self.subnode_parse(n, pieces=[], indent=4, ignore=['definition', 'name'])
461         self.add_text(['";', '\n'])
462
463
464 # MARK: Tag handlers
465     def do_linebreak(self, node):
466         self.add_text('  ')
467
468     def do_ndash(self, node):
469         self.add_text('--')
470
471     def do_mdash(self, node):
472         self.add_text('---')
473
474     def do_emphasis(self, node):
475         self.surround_parse(node, '*', '*')
476
477     def do_bold(self, node):
478         self.surround_parse(node, '**', '**')
479
480     def do_computeroutput(self, node):
481         self.surround_parse(node, '`', '`')
482
483     def do_heading(self, node):
484         self.start_new_paragraph()
485         pieces, self.pieces = self.pieces, ['']
486         level = int(node.attributes['level'].value)
487         self.subnode_parse(node)
488         if level == 1:
489             self.pieces.insert(0, '\n')
490             self.add_text(['\n', len(''.join(self.pieces).strip()) * '='])
491         elif level == 2:
492             self.add_text(['\n', len(''.join(self.pieces).strip()) * '-'])
493         elif level >= 3:
494             self.pieces.insert(0, level * '#' + ' ')
495         # make following text have no gap to the heading:
496         pieces.extend([''.join(self.pieces) + '  \n', ''])
497         self.pieces = pieces
498
499     def do_verbatim(self, node):
500         self.start_new_paragraph()
501         self.subnode_parse(node, pieces=[''], indent=4)
502
503     def do_blockquote(self, node):
504         self.start_new_paragraph()
505         self.subnode_parse(node, pieces=[''], indent='> ')
506
507     def do_hruler(self, node):
508         self.start_new_paragraph()
509         self.add_text('* * * * *  \n')
510
511     def do_includes(self, node):
512         self.add_text('\nC++ includes: ')
513         self.subnode_parse(node)
514         self.add_text('\n')
515
516 # MARK: Para tag handler
517     def do_para(self, node):
518         """This is the only place where text wrapping is automatically performed.
519         Generally, this function parses the node (locally), wraps the text, and
520         then adds the result to self.pieces. However, it may be convenient to
521         allow the previous content of self.pieces to be included in the text
522         wrapping. For this, use the following *convention*:
523         If self.pieces ends with '', treat the _previous_ entry as part of the
524         current paragraph. Else, insert new-line and start a new paragraph
525         and "wrapping context".
526         Paragraphs always end with '  \n', but if the parsed content ends with
527         the special symbol '', this is passed on.
528         """
529         if self.pieces[-1:] == ['']:
530             pieces, self.pieces = self.pieces[:-2], self.pieces[-2:-1]
531         else:
532             self.add_text('\n')
533             pieces, self.pieces = self.pieces, ['']
534         self.subnode_parse(node)
535         dont_end_paragraph = self.pieces[-1:] == ['']
536         # Now do the text wrapping:
537         width = self.textwidth - self.indent
538         wrapped_para = []
539         for line in ''.join(self.pieces).splitlines():
540             keep_markdown_newline = line[-2:] == '  '
541             w_line = textwrap.wrap(line, width=width, break_long_words=False)
542             if w_line == []:
543                 w_line = ['']
544             if keep_markdown_newline:
545                 w_line[-1] = w_line[-1] + '  '
546             for wl in w_line:
547                 wrapped_para.append(wl + '\n')
548         if wrapped_para:
549             if wrapped_para[-1][-3:] != '  \n':
550                 wrapped_para[-1] = wrapped_para[-1][:-1] + '  \n'
551             if dont_end_paragraph:
552                 wrapped_para.append('')
553         pieces.extend(wrapped_para)
554         self.pieces = pieces
555
556 # MARK: List tag handlers
557     def do_itemizedlist(self, node):
558         if self.listitem == '':
559             self.start_new_paragraph()
560         elif self.pieces != [] and self.pieces[-1:] != ['']:
561             self.add_text('\n')
562         listitem = self.listitem
563         if self.listitem in ['*', '-']:
564             self.listitem = '-'
565         else:
566             self.listitem = '*'
567         self.subnode_parse(node)
568         self.listitem = listitem
569
570     def do_orderedlist(self, node):
571         if self.listitem == '':
572             self.start_new_paragraph()
573         elif self.pieces != [] and self.pieces[-1:] != ['']:
574             self.add_text('\n')
575         listitem = self.listitem
576         self.listitem = 0
577         self.subnode_parse(node)
578         self.listitem = listitem
579
580     def do_listitem(self, node):
581         try:
582             self.listitem = int(self.listitem) + 1
583             item = str(self.listitem) + '. '
584         except:
585             item = str(self.listitem) + ' '
586         self.subnode_parse(node, item, indent=4)
587
588 # MARK: Parameter list tag handlers
589     def do_parameterlist(self, node):
590         self.start_new_paragraph()
591         text = 'unknown'
592         for key, val in node.attributes.items():
593             if key == 'kind':
594                 if val == 'param':
595                     text = 'Parameters'
596                 elif val == 'exception':
597                     text = 'Exceptions'
598                 elif val == 'retval':
599                     text = 'Returns'
600                 else:
601                     text = val
602                 break
603         if self.indent == 0:
604             self.add_text([text, '\n', len(text) * '-', '\n'])
605         else:
606             self.add_text([text, ':  \n'])
607         self.subnode_parse(node)
608
609     def do_parameteritem(self, node):
610         self.subnode_parse(node, pieces=['* ', ''])
611
612     def do_parameternamelist(self, node):
613         self.subnode_parse(node)
614         self.add_text([' :', '  \n'])
615
616     def do_parametername(self, node):
617         if self.pieces != [] and self.pieces != ['* ', '']:
618             self.add_text(', ')
619         data = self.extract_text(node)
620         self.add_text(['`', data, '`'])
621
622     def do_parameterdescription(self, node):
623         self.subnode_parse(node, pieces=[''], indent=4)
624
625 # MARK: Section tag handler
626     def do_simplesect(self, node):
627         kind = node.attributes['kind'].value
628         if kind in ('date', 'rcs', 'version'):
629             return
630         self.start_new_paragraph()
631         if kind == 'warning':
632             self.subnode_parse(node, pieces=['**Warning**: ',''], indent=4)
633         elif kind == 'see':
634             self.subnode_parse(node, pieces=['See also: ',''], indent=4)
635         elif kind == 'return':
636             if self.indent == 0:
637                 pieces = ['Returns', '\n', len('Returns') * '-', '\n', '']
638             else:
639                 pieces = ['Returns:', '\n', '']
640             self.subnode_parse(node, pieces=pieces)
641         else:
642             self.subnode_parse(node, pieces=[kind + ': ',''], indent=4)
643
644 # MARK: %feature("docstring") producing tag handlers
645     def do_compounddef(self, node):
646         """This produces %feature("docstring") entries for classes, and handles
647         class, namespace and file memberdef entries specially to allow for
648         overloaded functions. For other cases, passes parsing on to standard
649         handlers (which may produce unexpected results).
650         """
651         kind = node.attributes['kind'].value
652         if kind in ('class', 'struct'):
653             prot = node.attributes['prot'].value
654             if prot != 'public':
655                 return
656             self.add_text('\n\n')
657             classdefn = self.extract_text(self.get_specific_subnodes(node, 'compoundname'))
658             classname = classdefn.split('::')[-1]
659             self.add_text('%%feature("docstring") %s "\n' % classdefn)
660
661             if self.with_constructor_list:
662                 constructor_nodes = []
663                 for n in self.get_specific_subnodes(node, 'memberdef', recursive=2):
664                     if n.attributes['prot'].value == 'public':
665                         if self.extract_text(self.get_specific_subnodes(n, 'definition')) == classdefn + '::' + classname:
666                             constructor_nodes.append(n)
667                 for n in constructor_nodes:
668                     self.add_line_with_subsequent_indent(self.get_function_signature(n))
669
670             names = ('briefdescription','detaileddescription')
671             sub_dict = self.get_specific_nodes(node, names)
672             for n in ('briefdescription','detaileddescription'):
673                 if n in sub_dict:
674                     self.parse(sub_dict[n])
675             if self.with_constructor_list:
676                 self.make_constructor_list(constructor_nodes, classname)
677             if self.with_attribute_list:
678                 self.make_attribute_list(node)
679
680             sub_list = self.get_specific_subnodes(node, 'includes')
681             if sub_list:
682                 self.parse(sub_list[0])
683             self.add_text(['";', '\n'])
684
685             names = ['compoundname', 'briefdescription','detaileddescription', 'includes']
686             self.subnode_parse(node, ignore = names)
687
688         elif kind in ('file', 'namespace'):
689             nodes = node.getElementsByTagName('sectiondef')
690             for n in nodes:
691                 self.parse(n)
692
693         # now explicitely handle possibly overloaded member functions.
694         if kind in ['class', 'struct','file', 'namespace']:
695             md_nodes = self.get_memberdef_nodes_and_signatures(node, kind)
696             for sig in md_nodes:
697                 self.handle_typical_memberdefs(sig, md_nodes[sig])
698
699     def do_memberdef(self, node):
700         """Handle cases outside of class, struct, file or namespace. These are
701         now dealt with by `handle_overloaded_memberfunction`.
702         Do these even exist???
703         """
704         prot = node.attributes['prot'].value
705         id = node.attributes['id'].value
706         kind = node.attributes['kind'].value
707         tmp = node.parentNode.parentNode.parentNode
708         compdef = tmp.getElementsByTagName('compounddef')[0]
709         cdef_kind = compdef.attributes['kind'].value
710         if cdef_kind in ('file', 'namespace', 'class', 'struct'):
711             # These cases are now handled by `handle_typical_memberdefs`
712             return
713         if prot != 'public':
714             return
715         first = self.get_specific_nodes(node, ('definition', 'name'))
716         name = self.extract_text(first['name'])
717         if name[:8] == 'operator':  # Don't handle operators yet.
718             return
719         if not 'definition' in first or kind in ['variable', 'typedef']:
720             return
721
722         data = self.extract_text(first['definition'])
723         self.add_text('\n')
724         self.add_text(['/* where did this entry come from??? */', '\n'])
725         self.add_text('%feature("docstring") %s "\n%s' % (data, data))
726
727         for n in node.childNodes:
728             if n not in first.values():
729                 self.parse(n)
730         self.add_text(['";', '\n'])
731
732 # MARK: Entry tag handlers (dont print anything meaningful)
733     def do_sectiondef(self, node):
734         kind = node.attributes['kind'].value
735         if kind in ('public-func', 'func', 'user-defined', ''):
736             self.subnode_parse(node)
737
738     def do_header(self, node):
739         """For a user defined section def a header field is present
740         which should not be printed as such, so we comment it in the
741         output."""
742         data = self.extract_text(node)
743         self.add_text('\n/*\n %s \n*/\n' % data)
744         # If our immediate sibling is a 'description' node then we
745         # should comment that out also and remove it from the parent
746         # node's children.
747         parent = node.parentNode
748         idx = parent.childNodes.index(node)
749         if len(parent.childNodes) >= idx + 2:
750             nd = parent.childNodes[idx + 2]
751             if nd.nodeName == 'description':
752                 nd = parent.removeChild(nd)
753                 self.add_text('\n/*')
754                 self.subnode_parse(nd)
755                 self.add_text('\n*/\n')
756
757     def do_member(self, node):
758         kind = node.attributes['kind'].value
759         refid = node.attributes['refid'].value
760         if kind == 'function' and refid[:9] == 'namespace':
761             self.subnode_parse(node)
762
763     def do_doxygenindex(self, node):
764         self.multi = 1
765         comps = node.getElementsByTagName('compound')
766         for c in comps:
767             refid = c.attributes['refid'].value
768             fname = refid + '.xml'
769             if not os.path.exists(fname):
770                 fname = os.path.join(self.my_dir,  fname)
771             if not self.quiet:
772                 print("parsing file: %s" % fname)
773             p = Doxy2SWIG(fname,
774                           with_function_signature = self.with_function_signature,
775                           with_type_info = self.with_type_info,
776                           with_constructor_list = self.with_constructor_list,
777                           with_attribute_list = self.with_attribute_list,
778                           with_overloaded_functions = self.with_overloaded_functions,
779                           textwidth = self.textwidth,
780                           quiet = self.quiet)
781             p.generate()
782             self.pieces.extend(p.pieces)
783
784 # MARK: main
785 def main():
786     usage = __doc__
787     parser = optparse.OptionParser(usage)
788     parser.add_option("-f", '--function-signature',
789                       action='store_true',
790                       default=False,
791                       dest='f',
792                       help='include function signature in the documentation. This is handy when not using swig auto-generated function definitions %feature("autodoc", [0,1])')
793     parser.add_option("-t", '--type-info',
794                       action='store_true',
795                       default=False,
796                       dest='t',
797                       help='include type information for arguments in function signatures. This is similar to swig autodoc level 1')
798     parser.add_option("-c", '--constructor-list',
799                       action='store_true',
800                       default=False,
801                       dest='c',
802                       help='generate a constructor list for class documentation. Useful for target languages where the object construction should be documented in the class documentation.')
803     parser.add_option("-a", '--attribute-list',
804                       action='store_true',
805                       default=False,
806                       dest='a',
807                       help='generate an attributes list for class documentation. Useful for target languages where class attributes should be documented in the class documentation.')
808     parser.add_option("-o", '--overloaded-functions',
809                       action='store_true',
810                       default=False,
811                       dest='o',
812                       help='collect all documentation for overloaded functions. Useful for target languages that have no concept of overloaded functions, but also to avoid having to attach the correct docstring to each function overload manually')
813     parser.add_option("-w", '--width', type="int",
814                       action='store',
815                       dest='w',
816                       default=80,
817                       help='textwidth for wrapping (default: 80). Note that the generated lines may include 2 additional spaces (for markdown).')
818     parser.add_option("-q", '--quiet',
819                       action='store_true',
820                       default=False,
821                       dest='q',
822                       help='be quiet and minimize output')
823
824     options, args = parser.parse_args()
825     if len(args) != 2:
826         parser.error("no input and output specified")
827
828     p = Doxy2SWIG(args[0],
829                   with_function_signature = options.f,
830                   with_type_info = options.t,
831                   with_constructor_list = options.c,
832                   with_attribute_list = options.a,
833                   with_overloaded_functions = options.o,
834                   textwidth = options.w,
835                   quiet = options.q)
836     p.generate()
837     p.write(args[1])
838
839 if __name__ == '__main__':
840     main()