3 from pathlib
import Path
4 import xml.etree.ElementTree
as ET
9 Parses and extracts docs from Doxygen-generated XML.
22 Get the ElementTree of an XML file given the file name.
23 If an error occurs, prints a warning and returns None.
26 return ET.parse(xml_file)
27 except FileNotFoundError:
28 print(f
"Warning: XML file '{xml_file}' not found.")
31 print(f
"Warning: Failed to parse XML file '{xml_file}'.")
35 cpp_method: str, method_args_names:
'list[str]'):
37 Extract the docstrings for a C++ class's method from the Doxygen-generated XML.
40 xml_folder (str): The path to the folder that contains all of the Doxygen-generated XML.
41 cpp_class (str): The name of the C++ class that contains the function whose docstring is to be extracted.
42 cpp_method (str): The name of the C++ method whose docstring is to be extracted.
43 method_args_names (list): A list of the names of the cpp_method's parameters.
53 maybe_member_defs, method_args_names)
57 cpp_class, cpp_method, method_args_names, member_defs)
65 """Get all of the member definitions in cpp_class with name cpp_method.
68 xml_folder (str): The folder containing the Doxygen XML documentation.
69 cpp_class (str): The name of the C++ class that contains the function whose docstring is to be extracted.
70 cpp_method (str): The name of the C++ method whose docstring is to be extracted.
73 list: All of the member definitions in cpp_class with name cpp_method.
75 xml_folder_path = Path(xml_folder)
78 xml_index_file = xml_folder_path /
"index.xml"
81 index_tree = self.
parse_xml(xml_index_file)
86 index_root = index_tree.getroot()
89 class_index = index_root.find(f
"./*[name='{cpp_class}']")
91 if class_index
is None:
93 f
"Could not extract docs for {cpp_class}.{cpp_method}; class not found in index file."
98 xml_class_file = xml_folder_path / class_index.attrib[
'refid'] +
'.xml'
101 class_tree = self.
parse_xml(xml_class_file)
106 class_root = class_tree.getroot()
109 maybe_member_defs = class_root.findall(
110 f
"compounddef/sectiondef//*[name='{cpp_method}']")
112 return maybe_member_defs
115 method_args_names: list):
117 Remove member definitions which do not match the supplied argument names list.
120 maybe_member_defs (list): The list of all member definitions in the class which share the same name.
121 method_args_names (list): The list of argument names in the definition of the function whose documentation is desired.
122 Supplying the argument names allows for the filtering of overloaded functions with the same name but different arguments.
125 tuple[list, list]: (the filtered member definitions, parameters which should be ignored because they are optional)
133 for maybe_member_def
in maybe_member_defs:
135 f
"Investigating member_def with argstring {maybe_member_def.find('argsstring').text}"
139 params = maybe_member_def.findall(
"param")
140 num_tot_params =
len(params)
143 num_req_params = num_tot_params - sum([
144 1
if param.find(
"defval")
is not None else 0
152 if len(method_args_names) != num_req_params
and len(
153 method_args_names) != num_tot_params:
155 f
"Wrong number of parameters: got {len(method_args_names)}, expected required {num_req_params} or total {num_tot_params}."
161 for i, arg_name
in enumerate(method_args_names):
163 param_name = params[i].find(
167 if param_name
is None:
168 param_name = params[i].find(
"defname")
169 if param_name
is None:
175 if arg_name != param_name.text:
184 member_defs.append(maybe_member_def)
188 for i
in range(
len(method_args_names), num_tot_params):
189 ignored_params.append(params[i].find(
"declname").text)
191 return member_defs, ignored_params
194 method_args_names: list,
197 Determine which member definition to retrieve documentation from, if there are multiple.
200 cpp_class (str): The name of the C++ class that contains the function whose docstring is to be extracted.
201 cpp_method (str): The name of the C++ method whose docstring is to be extracted.
202 method_args_names (list): A list of the names of the cpp_method's parameters.
203 member_defs (list): All of the member definitions of cpp_class which match cpp_method in name
204 and whose arguments have the same names as method_args_names.
207 int: The index indicating which member definition to document.
218 documenting_index = 0
219 if len(member_defs) > 1:
220 function_key = f
"{cpp_class}.{cpp_method}({','.join(method_args_names) if method_args_names else ''})"
221 if function_key
in self.
_memory:
222 self.
_memory[function_key] += 1
223 documenting_index = self.
_memory[function_key]
227 return documenting_index
230 member_def:
'xml.etree.ElementTree.Element',
231 ignored_params: list):
232 """Gets the formatted docstring for the supplied XML element representing a member definition.
235 member_def (xml.etree.ElementTree.Element): The member definition to document.
236 ignored_params (list): The optional parameters which should be ignored, if any.
239 str: The formatted docstring.
243 brief_description = member_def.find(
".//briefdescription")
244 detailed_description = member_def.find(
".//detaileddescription")
247 if brief_description
is not None:
248 for para
in brief_description.findall(
"para"):
249 docstring +=
"".join(t
for t
in para.itertext()
if t.strip())
252 if detailed_description
is not None:
255 for element
in list(detailed_description):
256 if element.tag ==
"para" and "parameterlist" not in [
257 e.tag
for e
in element
259 docstring +=
"".join(
260 t
for t
in element.itertext()
if t.strip()) +
" "
263 parameter_list = detailed_description.find(
".//parameterlist")
264 if parameter_list
is not None:
265 for i, parameter_item
in enumerate(
266 parameter_list.findall(
".//parameteritem")):
267 name = parameter_item.find(
".//parametername").text
268 desc = parameter_item.find(
269 ".//parameterdescription/para").text
270 if name
not in ignored_params:
271 docstring += f
"{name.strip() if name else f'[Parameter {i}]'}: {desc.strip() if desc else 'No description provided'}\n"
274 return_sect = detailed_description.find(
".//simplesect")
275 if return_sect
is not None and return_sect.attrib[
276 "kind"] ==
"return" and return_sect.find(
277 "para").text
is not None:
278 docstring += f
"Returns: {return_sect.find('para').text.strip()}"
280 return docstring.strip()
284 Print text if the parser is in verbose mode.
290 if __name__ ==
"__main__":
291 if len(sys.argv) != 5:
293 "Usage: python xml_parser.py <doxygen_xml_folder> <cpp_class> <cpp_method> <method_args_names (comma-separated)>"
297 parser._verbose =
True
298 xml_file = sys.argv[1]
299 extracted_doc = parser.extract_docstring(xml_file, sys.argv[2],
301 sys.argv[4].
split(
","))
304 print(extracted_doc.strip())