doxymlparser.py 7.3 KB

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