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