1"""
2Name: doxymlparser.py
3Author: Kevin Ollivier
4Licence: wxWindows licence
5"""
6
7__description__ = """
8Takes the output of Doxygen XML and parses it to retrieve metadata about the classes and methods.
9
10To create the Doxygen XML files, from the wxWidgets/docs/doxygen directory, do:
11
12./regen.sh xml
13
14To see the results from parsing a particular class, do:
15
16python doxymlparser.py --report out/xml/classwx_<whatever>.xml
17"""
18
19#!/usr/bin/env python
20import optparse
21import os
22import string
23
24import sys
25import types
26
27from common import *
28from xml.dom import minidom
29
30class ClassDefinition:
31    def __init__(self):
32        self.name = ""
33        self.constructors = []
34        self.destructors = []
35        self.methods = []
36        self.brief_description = ""
37        self.detailed_description = ""
38        self.includes = []
39        self.bases = []
40        self.enums = {}
41
42    def __str__(self):
43        str_repr = """
44Class: %s
45Bases: %s
46Includes: %s
47Brief Description:
48%s
49
50Detailed Description:
51%s
52""" % (self.name, string.join(self.bases, ", "), self.includes, self.brief_description, self.detailed_description)
53        str_repr += "Methods:\n"
54
55        for method in self.methods:
56            str_repr += str(method)
57
58        return str_repr
59
60class MethodDefinition:
61    def __init__(self):
62        self.name = ""
63        self.return_type = ""
64        self.argsstring = ""
65        self.definition = ""
66        self.params = []
67        self.brief_description = ""
68        self.detailed_description = ""
69
70    def __str__(self):
71        str_repr = """
72Method: %s
73Return Type: %s
74Params: %r
75Prototype: %s
76Brief Description:
77%s
78
79Detailed Description:
80%s
81""" % (self.name, self.return_type, self.params, self.definition + self.argsstring, self.brief_description, self.detailed_description)
82        return str_repr
83
84def getTextValue(node, recursive=False):
85    text = ""
86    for child in node.childNodes:
87        if child.nodeType == child.ELEMENT_NODE and child.nodeName == "ref":
88            text += getTextValue(child)
89        if child.nodeType == child.TEXT_NODE:
90            # Add a space to ensure we have a space between qualifiers and parameter names
91            text += child.nodeValue.strip() + " "
92
93    return text.strip()
94
95def doxyMLToText(node):
96    return text
97
98class DoxyMLParser:
99    def __init__(self, verbose = False):
100        self.classes = []
101        self.verbose = verbose
102
103    def find_class(self, name):
104        for aclass in self.classes:
105            if aclass.name == name:
106                return aclass
107
108        return None
109
110    def get_enums_and_functions(self, filename, aclass):
111        file_path = os.path.dirname(filename)
112        enum_filename = os.path.join(file_path, aclass.name[2:] + "_8h.xml")
113        if os.path.exists(enum_filename):
114            root = minidom.parse(enum_filename).documentElement
115            for method in root.getElementsByTagName("memberdef"):
116                if method.getAttribute("kind") == "enum":
117                    self.parse_enum(aclass, method, root)
118
119    def is_derived_from_base(self, aclass, abase):
120        base = get_first_value(aclass.bases)
121        while base and base != "":
122
123            if base == abase:
124                return True
125
126            parentclass = self.find_class(base)
127
128            if parentclass:
129                base = get_first_value(parentclass.bases)
130            else:
131                base = None
132
133        return False
134
135    def parse(self, filename):
136        self.xmldoc = minidom.parse(filename).documentElement
137        for node in self.xmldoc.getElementsByTagName("compounddef"):
138            new_class = self.parse_class(node)
139            self.classes.append(new_class)
140            self.get_enums_and_functions(filename, new_class)
141
142    def parse_class(self, class_node):
143        new_class = ClassDefinition()
144        new_class.name = getTextValue(class_node.getElementsByTagName("compoundname")[0])
145        for node in class_node.childNodes:
146            if node.nodeName == "basecompoundref":
147                new_class.bases.append(getTextValue(node))
148            elif node.nodeName == "briefdescription":
149                # let the post-processor determ
150                new_class.brief_description = node.toxml()
151            elif node.nodeName == "detaileddescription":
152                new_class.detailed_description = node.toxml()
153            elif node.nodeName == "includes":
154                new_class.includes.append(getTextValue(node))
155
156        self.parse_methods(new_class, class_node)
157
158        return new_class
159
160    def parse_enum(self, new_class, enum, root):
161        enum_name = ""
162        enum_values = []
163
164        for node in enum.childNodes:
165            if node.nodeName == "name":
166                enum_name = getTextValue(node)
167            elif node.nodeName == "enumvalue":
168                enum_values.append(getTextValue(node.getElementsByTagName("name")[0]))
169
170        new_class.enums[enum_name] = enum_values
171
172    def parse_methods(self, new_class, root):
173        for method in root.getElementsByTagName("memberdef"):
174            new_method = MethodDefinition()
175            for node in method.childNodes:
176                if node.nodeName == "name":
177                    new_method.name = getTextValue(node)
178                elif node.nodeName == "type":
179                    new_method.return_type = getTextValue(node)
180                elif node.nodeName == "definition":
181                    new_method.definition = getTextValue(node)
182                elif node.nodeName == "argsstring":
183                    new_method.argsstring = getTextValue(node)
184                elif node.nodeName == "param":
185                    param = {}
186                    for child in node.childNodes:
187                        if child.nodeType == child.ELEMENT_NODE:
188                            param[child.nodeName] = getTextValue(child)
189                    new_method.params.append(param)
190
191            if self.verbose:
192                print "Adding %s" % (new_method.name + new_method.argsstring)
193
194            if new_method.name == new_class.name:
195                new_class.constructors.append(new_method)
196            elif new_method.name == "~" + new_class.name:
197                new_class.destructors.append(new_method)
198            else:
199                new_class.methods.append(new_method)
200
201if __name__ == "__main__":
202    option_dict = {
203                "report"        : (False, "Print out the classes and methods found by this script."),
204                "verbose"       : (False, "Provide status updates and other information."),
205              }
206
207    parser = optparse.OptionParser(usage="usage: %prog [options] <doxyml files to parse>\n" + __description__, version="%prog 1.0")
208
209    for opt in option_dict:
210        default = option_dict[opt][0]
211
212        action = "store"
213        if type(default) == types.BooleanType:
214            action = "store_true"
215        parser.add_option("--" + opt, default=default, action=action, dest=opt, help=option_dict[opt][1])
216
217    options, arguments = parser.parse_args()
218
219    if len(arguments) < 1:
220        parser.print_usage()
221        sys.exit(1)
222
223    doxyparse = DoxyMLParser(verbose = options.verbose)
224    for arg in arguments:
225        doxyparse.parse(arg)
226
227    if options.report:
228        for aclass in doxyparse.classes:
229            print str(aclass)
230
231