xml_parser.py
Go to the documentation of this file.
1 import os
2 import sys
3 from pathlib import Path
4 import xml.etree.ElementTree as ET
5 
6 
8  """
9  Parses and extracts docs from Doxygen-generated XML.
10  """
11 
12  def __init__(self):
13  # Memory for overloaded functions with identical parameter name sets
14  self._memory = {}
15  # Verbosity is useful for investigating functions that cause problems for extract_docstring.
16  # Set this to true to have useful information for debugging this class, as in the CLI
17  # function at the bottom of this class.
18  self._verbose = False
19  # Cache parsed XML trees to avoid re-parsing during recursive calls
21 
22  def _get_class_xml_path(self, xml_folder: str,
23  cpp_class: str) -> Path | None:
24  """Finds the XML file path for a given class name using the index."""
25  xml_folder_path = Path(xml_folder)
26  xml_index_file = xml_folder_path / "index.xml"
27 
28  index_tree = self.parse_xml(xml_index_file)
29  if not index_tree:
30  self.print_if_verbose(
31  f"Index file {xml_index_file} was empty or failed to parse.")
32  return None
33 
34  index_root = index_tree.getroot()
35  class_index = index_root.find(
36  f".//compound[@kind='class'][name='{cpp_class}']")
37  if class_index is None:
38  # Also check for structs
39  class_index = index_root.find(
40  f".//compound[@kind='struct'][name='{cpp_class}']")
41 
42  if class_index is None:
43  self.print_if_verbose(
44  f"Class or Struct '{cpp_class}' not found in index file {xml_index_file}."
45  )
46  return None
47 
48  refid = class_index.attrib.get('refid')
49  if not refid:
50  self.print_if_verbose(
51  f"Class or Struct '{cpp_class}' found in index, but has no refid."
52  )
53  return None
54 
55  return xml_folder_path / f"{refid}.xml"
56 
57  def parse_xml(self, xml_file: Path | str):
58  """
59  Get the ElementTree of an XML file given the file name.
60  Uses a cache to avoid re-parsing.
61  If an error occurs, prints a warning and returns None.
62  """
63  xml_file_path = Path(xml_file)
64  file_key = str(xml_file_path.resolve())
65 
66  if file_key in self._parsed_xml_cache:
67  return self._parsed_xml_cache[file_key]
68 
69  try:
70  tree = ET.parse(xml_file_path)
71  self._parsed_xml_cache[file_key] = tree
72  return tree
73  except FileNotFoundError:
74  print(f"Warning: XML file '{xml_file_path}' not found.")
75  self._parsed_xml_cache[file_key] = None # Cache failure
76  return None
77  except ET.ParseError:
78  print(f"Warning: Failed to parse XML file '{xml_file_path}'.")
79  self._parsed_xml_cache[file_key] = None # Cache failure
80  return None
81 
82  def extract_docstring(self, xml_folder: str, cpp_class: str,
83  cpp_method: str, method_args_names: 'list[str]'):
84  """
85  Extract the docstrings for a C++ class's method from the Doxygen-generated XML.
86  If not found in the specified class, searches parent classes recursively.
87 
88  Args:
89  xml_folder (str): The path to the folder that contains all of the Doxygen-generated XML.
90  cpp_class (str): The name of the C++ class that contains the function whose docstring is to be extracted.
91  cpp_method (str): The name of the C++ method whose docstring is to be extracted.
92  method_args_names (list): A list of the names of the cpp_method's parameters.
93  """
94  self.print_if_verbose(
95  f"--- Attempting to extract docs for {cpp_class}.{cpp_method} ---")
96 
97  # Find the XML file for the current class
98  xml_class_file = self._get_class_xml_path(xml_folder, cpp_class)
99  if not xml_class_file:
100  self.print_if_verbose(
101  f"Could not find XML file for class {cpp_class}.")
102  return "" # Cannot proceed without the class file
103 
104  # Parse the class XML file
105  class_tree = self.parse_xml(xml_class_file)
106  if not class_tree:
107  self.print_if_verbose(
108  f"Class file {xml_class_file} was empty or failed to parse.")
109  return ""
110 
111  class_root = class_tree.getroot()
112 
113  # --- Step 1: Search for the method definition directly within this class ---
114  maybe_member_defs = self.get_member_defs_from_root(
115  class_root, cpp_method)
116 
117  # Filter member definitions which don't match the given argument names
118  member_defs, ignored_params = self.filter_member_defs(
119  maybe_member_defs, method_args_names, cpp_class,
120  cpp_method) # Pass class/method for verbose printing
121 
122  # If we found matching definitions in *this* class
123  if member_defs:
124  # Find which member to get docs from, if there are multiple that match in name and args
125  documenting_index = self.determine_documenting_index(
126  cpp_class, cpp_method, method_args_names, member_defs)
127 
128  # Ensure the index is valid (can happen with memory issues or complex inheritance/overload combos)
129  if documenting_index < len(member_defs):
130  self.print_if_verbose(
131  f"Found direct documentation for {cpp_class}.{cpp_method}."
132  )
133  # Extract the docs for the function that matches cpp_class.cpp_method(*method_args_names).
134  return self.get_formatted_docstring(
135  member_defs[documenting_index], ignored_params)
136  else:
137  self.print_if_verbose(
138  f"Calculated documenting_index {documenting_index} is out of bounds for {cpp_class}.{cpp_method} (len={len(member_defs)})."
139  )
140  # Fall through to parent search, maybe the specific overload documented is in parent
141 
142  # --- Step 2: If not found directly, search in parent classes ---
143  self.print_if_verbose(
144  f"No direct documentation found for {cpp_class}.{cpp_method} with matching args. Checking base classes..."
145  )
146 
147  # Find base classes from the current class's XML
148  compound_def = class_root.find("compounddef")
149  if compound_def is None:
150  self.print_if_verbose(
151  f"Could not find <compounddef> in {xml_class_file}")
152  return ""
153 
154  base_refs = compound_def.findall("basecompoundref")
155  for base_ref in base_refs:
156  base_refid = base_ref.attrib.get("refid")
157  if not base_refid:
158  continue
159 
160  # We need the *name* of the base class to perform the recursive search correctly.
161  # Doxygen often uses the refid as the filename.
162  parent_xml_file = Path(xml_folder) / f"{base_refid}.xml"
163  parent_tree = self.parse_xml(parent_xml_file)
164  if not parent_tree:
165  self.print_if_verbose(
166  f"Could not parse parent XML file {parent_xml_file} for refid {base_refid}."
167  )
168  continue
169 
170  parent_root = parent_tree.getroot()
171  parent_compound_def = parent_root.find("compounddef")
172  if parent_compound_def is None:
173  self.print_if_verbose(
174  f"Could not find <compounddef> in parent XML {parent_xml_file}."
175  )
176  continue
177 
178  parent_name_element = parent_compound_def.find("compoundname")
179  if parent_name_element is None or not parent_name_element.text:
180  self.print_if_verbose(
181  f"Could not find <compoundname> in parent XML {parent_xml_file}."
182  )
183  continue
184 
185  parent_class_name = parent_name_element.text
186  self.print_if_verbose(
187  f"Recursively searching for {cpp_method} in base class: {parent_class_name} (refid: {base_refid})"
188  )
189 
190  # Recursive call for the parent class
191  # Reset memory for the parent context? No, memory should track specific overloads globally.
192  parent_docstring = self.extract_docstring(xml_folder,
193  parent_class_name,
194  cpp_method,
195  method_args_names)
196 
197  if parent_docstring:
198  self.print_if_verbose(
199  f"Found documentation for {cpp_method} in base class {parent_class_name}."
200  )
201  # NOTE: We return the docstring found in the parent. The _memory tracking in
202  # determine_documenting_index handles cases where the *same* overload
203  # signature (name+arg_names) appears multiple times across the hierarchy,
204  # ensuring we pick the 'next' available one based on previous calls.
205  return parent_docstring
206  else:
207  self.print_if_verbose(
208  f"Method {cpp_method} not found or documented in base class {parent_class_name}."
209  )
210 
211  # --- Step 3: If not found anywhere ---
212  self.print_if_verbose(
213  f"Method {cpp_method} with matching args not documented in {cpp_class} or any base classes."
214  )
215  return ""
216 
217  def get_member_defs_from_root(self, class_root: ET.Element,
218  cpp_method: str) -> list[ET.Element]:
219  """Finds member definitions for a method name within a given class XML root."""
220  # Find the member(s) in cpp_class with name == cpp_method
221  # Search within <sectiondef> elements which contain function definitions
222  member_defs = class_root.findall(
223  f"compounddef/sectiondef[@kind='public-func']/memberdef[@kind='function'][name='{cpp_method}']"
224  )
225  member_defs.extend(
226  class_root.findall(
227  f"compounddef/sectiondef[@kind='public-static-func']/memberdef[@kind='function'][name='{cpp_method}']"
228  ))
229  # Add other kinds if necessary (e.g., protected-func, user-defined sections containing functions)
230  member_defs.extend(
231  class_root.findall(
232  f"compounddef/sectiondef[@kind='user-defined']/memberdef[@kind='function'][name='{cpp_method}']"
233  ))
234  # Search for typedefs too, if methods might be hidden behind them. Unlikely for funcs.
235 
236  return member_defs
237 
238  def filter_member_defs(self, maybe_member_defs: list[ET.Element],
239  method_args_names: list[str], cpp_class: str,
240  cpp_method: str):
241  """
242  Remove member definitions which do not match the supplied argument names list.
243 
244  Args:
245  maybe_member_defs (list): The list of all member definitions in the class which share the same name.
246  method_args_names (list): The list of argument names in the definition of the function whose documentation is desired.
247  cpp_class (str): The name of the class being investigated (for verbose output).
248  cpp_method (str): The name of the method being investigated (for verbose output).
249 
250  Returns:
251  tuple[list, list]: (the filtered member definitions, parameters which should be ignored because they are optional)
252  """
253  member_defs = []
254  # Track ignored params per specific successful match
255  ignored_params = []
256 
257  # Filter out the members which don't match the method_args_names
258  for maybe_member_def in maybe_member_defs:
259  # Ignored params for *this specific* potential match
260  current_ignored = []
261  args_string_elem = maybe_member_def.find('argsstring')
262  args_string_text = args_string_elem.text if args_string_elem is not None else "[no argsstring]"
263  self.print_if_verbose(
264  f" Investigating potential match for {cpp_class}.{cpp_method}: "
265  f"argstring '{args_string_text}' (loc: {maybe_member_def.find('location').attrib.get('file', '?')}:{maybe_member_def.find('location').attrib.get('line', '?')})"
266  )
267  # Find the number of required parameters and the number of total parameters from the
268  # Doxygen XML for this member_def
269  params = maybe_member_def.findall("param")
270  num_tot_params = len(params)
271  # Calculate required params by subtracting the number of optional params (params where defval is
272  # set--defval means default value) from the number of total params
273  num_req_params = num_tot_params - sum([
274  1 if param.find("defval") is not None else 0
275  for param in params
276  ])
277 
278  self.print_if_verbose(
279  f" XML Params: Total={num_tot_params}, Required={num_req_params}. Provided args count: {len(method_args_names)}"
280  )
281 
282  # If the number of parameters in method_args_names matches neither number, eliminate this member_def
283  # This is done because wrap generates a python wrapper function twice for every function with
284  # optional parameters: one with none of the optional parameters, and one with all of the optional
285  # parameters, required.
286  if len(method_args_names) != num_req_params and len(
287  method_args_names) != num_tot_params:
288  self.print_if_verbose(
289  f" Parameter count mismatch. Skipping.")
290  continue
291 
292  eliminate = False
293 
294  # If the parameter names don't match, eliminate this member_def
295  # Ensure we don't try to access params beyond the XML list size
296  if len(method_args_names) > num_tot_params:
297  eliminate = True
298  self.print_if_verbose(
299  f" Provided args count ({len(method_args_names)}) > XML total params ({num_tot_params}). Skipping."
300  )
301  else:
302  for i, arg_name in enumerate(method_args_names):
303  # Try to find the name of the parameter in the XML
304  param_elem = params[i]
305  # declname is the tag that usually contains the param name
306  param_name_elem = param_elem.find("declname")
307  # If we couldn't find the declname, try the defname (used uncommonly)
308  if param_name_elem is None:
309  param_name_elem = param_elem.find("defname")
310 
311  if param_name_elem is None or param_name_elem.text is None:
312  # Can't find the name for this parameter. This may be an unreachable statement but
313  # I don't want to rely on a <declname> or a <defname> always being defined inside a <param>.
314  self.print_if_verbose(
315  f" Could not find XML name for parameter index {i}. Skipping."
316  )
317  eliminate = True
318  break # No point checking further params for this def
319 
320  xml_param_name = param_name_elem.text
321  # Eliminate if any param name doesn't match the expected name
322  if arg_name != xml_param_name:
323  self.print_if_verbose(
324  f" Parameter name mismatch at index {i}: Provided='{arg_name}', XML='{xml_param_name}'. Skipping."
325  )
326  eliminate = True
327  break # No point checking further params for this def
328 
329  if eliminate:
330  continue
331 
332  # At this point, this member_def matches the required/total parameter count AND the names match up to method_args_names.
333  # This is a candidate function.
334  member_defs.append(maybe_member_def)
335  self.print_if_verbose(
336  " Confirmed as candidate function by arg names and count.")
337 
338  # If this matched based on the *total* number of parameters, there are no ignored optional parameters
339  # for *this specific call*. If it matched based on the *required* number, then the remaining
340  # parameters in the XML are the ones ignored *for this specific call*.
341  if len(method_args_names
342  ) == num_req_params and num_req_params != num_tot_params:
343  self.print_if_verbose(
344  f" Matched on required params ({num_req_params}). Identifying ignored optional params..."
345  )
346  for i in range(num_req_params, num_tot_params):
347  ignored_name_elem = params[i].find("declname")
348  if ignored_name_elem is None:
349  ignored_name_elem = params[i].find("defname")
350  if ignored_name_elem is not None and ignored_name_elem.text:
351  current_ignored.append(ignored_name_elem.text)
352  self.print_if_verbose(
353  f" Ignoring optional param: {ignored_name_elem.text}"
354  )
355  # The ignored_params list will contain those
356  # from the *last* successful match. This relies on determine_documenting_index
357  # picking correctly. A cleaner way would be to associate ignored_params *with* the member_def.
358  ignored_params = current_ignored # Overwrite with ignored params from this match
359  else:
360  self.print_if_verbose(
361  f" Matched on total params ({num_tot_params}) or req==tot. No ignored optional params for this match."
362  )
363  # No ignored params if we matched the total count
364  ignored_params = []
365 
366  return member_defs, ignored_params # Return the list associated with the last match found
367 
368  def determine_documenting_index(self, cpp_class: str, cpp_method: str,
369  method_args_names: list,
370  member_defs: list):
371  """
372  Determine which member definition to retrieve documentation from, if there are multiple.
373 
374  Args:
375  cpp_class (str): The name of the C++ class that contains the function whose docstring is to be extracted.
376  cpp_method (str): The name of the C++ method whose docstring is to be extracted.
377  method_args_names (list): A list of the names of the cpp_method's parameters.
378  member_defs (list): All of the member definitions of cpp_class which match cpp_method in name
379  and whose arguments have the same names as method_args_names.
380 
381  Returns:
382  int: The index indicating which member definition to document.
383  """
384  documenting_index = 0
385  num_matches = len(member_defs)
386 
387  if num_matches <= 1:
388  return 0 # Trivial case
389 
390  # If there are multiple member defs that survived filtering (e.g. same name, same arg *names*,
391  # but potentially different types, or defined in both parent and child),
392  # use memory to cycle through them.
393  function_key = f"{cpp_class}.{cpp_method}({','.join(method_args_names) if method_args_names else ''})"
394 
395  # How many times have we *previously* returned a doc for this exact signature?
396  # Use -1 to start index at 0 after increment
397  times_documented = self._memory.get(function_key, -1)
398 
399  # The index we should use *this time* is the number of times documented previously.
400  documenting_index = times_documented + 1
401 
402  # Make sure the index is within the bounds of the *currently found* matches.
403  # If we've documented more times than there are current matches (e.g., due to complex
404  # inheritance/overload scenarios or issues in filtering), default to 0.
405  if documenting_index >= num_matches:
406  self.print_if_verbose(
407  f" Memory index {documenting_index} >= num_matches {num_matches} for {function_key}. Wrapping to 0."
408  )
409  documenting_index = 0
410  self._memory[function_key] = 0 # Store the index we are returning
411  else:
412  # Store the index we are about to return, so the *next* call gets the subsequent one.
413  self._memory[function_key] = documenting_index
414 
415  self.print_if_verbose(
416  f" Multiple matches ({num_matches}) found for {function_key}. Using index {documenting_index} based on memory."
417  )
418  return documenting_index
419 
420  def get_formatted_docstring(self, member_def: 'ET.Element',
421  ignored_params: list):
422  """Gets the formatted docstring for the supplied XML element representing a member definition.
423 
424  Args:
425  member_def (xml.etree.ElementTree.Element): The member definition to document.
426  ignored_params (list): The optional parameters which should be ignored *for this specific overload match*, if any.
427 
428  Returns:
429  str: The formatted docstring. Returns empty string if member_def lacks documentation tags.
430  """
431  docstring = ""
432  has_content = False
433 
434  # Location info for debugging which definition is being used
435  location_elem = member_def.find('location')
436  loc_info = f"(from {location_elem.attrib.get('file', '?')}:{location_elem.attrib.get('line', '?')})" if location_elem is not None else ""
437  self.print_if_verbose(f" Formatting docstring {loc_info}")
438 
439  brief_description = member_def.find("./briefdescription")
440  detailed_description = member_def.find("./detaileddescription")
441 
442  # Add the brief description first, if it exists and has content.
443  if brief_description is not None:
444  brief_text = "".join(t.strip()
445  for para in brief_description.findall("para")
446  for t in para.itertext()
447  if t.strip()).strip()
448  if brief_text:
449  docstring += brief_text
450  has_content = True
451  self.print_if_verbose(f" Brief: {brief_text}")
452 
453  # Add the detailed description. This includes the parameter list and the return value.
454  if detailed_description is not None:
455  detailed_content = ""
456  # Add non-parameter detailed description paragraphs
457  has_detailed_para = False
458  for element in list(detailed_description):
459  # Check if it's a para AND doesn't contain a parameterlist or simplesect (handle those separately)
460  if element.tag == "para" and not element.findall(
461  ".//parameterlist") and not element.findall(
462  ".//simplesect[@kind='return']"
463  ): # Check simplesect kind
464  para_text = "".join(t for t in element.itertext()
465  if t.strip()).strip()
466  if para_text:
467  detailed_content += para_text + " "
468  has_detailed_para = True
469 
470  if has_detailed_para:
471  if has_content: # Add newline if brief description was present
472  docstring += "\n\n" # Use two newlines for separation like Sphinx
473  detailed_content = detailed_content.strip()
474  docstring += detailed_content
475  has_content = True
476  self.print_if_verbose(
477  f" Detailed Paras: {detailed_content}")
478 
479  # Add parameter docs
480  parameter_list = detailed_description.find(
481  ".//parameterlist[@kind='param']") # Ensure it's for 'param'
482  param_docs = ""
483  if parameter_list is not None:
484  self.print_if_verbose(
485  f" Processing parameters (ignoring: {ignored_params})..."
486  )
487  for i, parameter_item in enumerate(
488  parameter_list.findall("./parameteritem")):
489  name_elem = parameter_item.find(
490  "./parameternamelist/parametername")
491  desc_elem = parameter_item.find( # Description might be complex
492  "./parameterdescription/para")
493  name = name_elem.text.strip(
494  ) if name_elem is not None and name_elem.text else f'[Param {i+1}]'
495  # Handle potentially complex descriptions (multiple paras, refs, etc.)
496  desc = "".join(t for t in desc_elem.itertext() if t.strip(
497  )).strip(
498  ) if desc_elem is not None else 'No description provided'
499 
500  if name not in ignored_params:
501  param_docs += f"{name}: {desc}\n"
502  self.print_if_verbose(f" Param: {name} - {desc}")
503  else:
504  self.print_if_verbose(
505  f" Ignoring documented param: {name}")
506 
507  if param_docs:
508  if has_content:
509  docstring += "\n\nArgs:\n" # Sphinx style
510  else:
511  docstring += "Args:\n"
512  # Strip trailing newline from last param
513  docstring += param_docs.strip()
514  has_content = True
515 
516  # Add return value docs
517  # Doxygen uses simplesect[@kind='return'], potentially nested. Use .//
518  return_sect = detailed_description.find(
519  ".//simplesect[@kind='return']")
520  return_doc = ""
521  if return_sect is not None:
522  # Find the paragraph text *within* the return section
523  return_para = return_sect.find(
524  ".//para") # Use .// just in case it's nested further
525  if return_para is not None:
526  return_text = "".join(t for t in return_para.itertext()
527  if t.strip()).strip()
528  if return_text:
529  return_doc = f"Returns: {return_text}"
530  self.print_if_verbose(f" Return: {return_text}")
531 
532  if return_doc:
533  # Decide where to put the newline
534  if param_docs: # If args were present, add newline after them
535  docstring += "\n\n" + return_doc
536  elif has_content: # If brief/detailed paras were present but no args
537  # Potential for different formatting
538  docstring += "\n\n" + return_doc
539  else: # Only return doc is present
540  docstring += return_doc
541  has_content = True
542 
543  final_docstring = docstring.strip()
544  if not final_docstring and not has_content:
545  self.print_if_verbose(
546  " No documentation content found in brief/detailed descriptions."
547  )
548  return "" # Return empty if no actual documentation was found
549 
550  return final_docstring
551 
552  def print_if_verbose(self, text: str):
553  """
554  Print text if the parser is in verbose mode.
555  """
556  if self._verbose:
557  print(text)
558 
559 
560 if __name__ == "__main__":
561  if len(sys.argv) < 4 or len(sys.argv) > 5:
562  print(
563  "Usage: python xml_parser.py <doxygen_xml_folder> <cpp_class> <cpp_method> <method_args_names (comma-separated)>"
564  )
565  sys.exit(1)
566 
567  parser = XMLDocParser()
568  parser._verbose = True
569  xml_folder = sys.argv[1]
570  cpp_class = sys.argv[2]
571  cpp_method = sys.argv[3]
572  method_args = []
573  if len(sys.argv) == 5 and sys.argv[4]:
574  method_args = sys.argv[4].split(",")
575 
576  extracted_doc = parser.extract_docstring(xml_folder, cpp_class, cpp_method,
577  method_args)
578 
579  print("\n--- Extracted Docstring ---")
580  print(extracted_doc if extracted_doc else "[No documentation found]")
581  print("---------------------------")
gtwrap.xml_parser.xml_parser.XMLDocParser.parse_xml
def parse_xml(self, Path|str xml_file)
Definition: xml_parser.py:57
Eigen::internal::print
EIGEN_STRONG_INLINE Packet4f print(const Packet4f &a)
Definition: NEON/PacketMath.h:3115
gtwrap.xml_parser.xml_parser.XMLDocParser.__init__
def __init__(self)
Definition: xml_parser.py:12
gtwrap.xml_parser.xml_parser.XMLDocParser._memory
_memory
Definition: xml_parser.py:14
gtwrap.xml_parser.xml_parser.XMLDocParser.get_member_defs_from_root
list[ET.Element] get_member_defs_from_root(self, ET.Element class_root, str cpp_method)
Definition: xml_parser.py:217
list
Definition: pytypes.h:2168
gtwrap.xml_parser.xml_parser.XMLDocParser.filter_member_defs
def filter_member_defs(self, list[ET.Element] maybe_member_defs, list[str] method_args_names, str cpp_class, str cpp_method)
Definition: xml_parser.py:238
gtwrap.xml_parser.xml_parser.XMLDocParser.print_if_verbose
def print_if_verbose(self, str text)
Definition: xml_parser.py:552
gtwrap.xml_parser.xml_parser.XMLDocParser.determine_documenting_index
def determine_documenting_index(self, str cpp_class, str cpp_method, list method_args_names, list member_defs)
Definition: xml_parser.py:368
gtwrap.xml_parser.xml_parser.XMLDocParser.get_formatted_docstring
def get_formatted_docstring(self, 'ET.Element' member_def, list ignored_params)
Definition: xml_parser.py:420
gtsam::range
Double_ range(const Point2_ &p, const Point2_ &q)
Definition: slam/expressions.h:30
gtwrap.xml_parser.xml_parser.XMLDocParser
Definition: xml_parser.py:7
gtwrap.xml_parser.xml_parser.XMLDocParser._verbose
_verbose
Definition: xml_parser.py:18
gtwrap.xml_parser.xml_parser.XMLDocParser._get_class_xml_path
Path|None _get_class_xml_path(self, str xml_folder, str cpp_class)
Definition: xml_parser.py:22
str
Definition: pytypes.h:1560
gtwrap.xml_parser.xml_parser.XMLDocParser.extract_docstring
def extract_docstring(self, str xml_folder, str cpp_class, str cpp_method, 'list[str]' method_args_names)
Definition: xml_parser.py:82
gtsam::split
void split(const G &g, const PredecessorMap< KEY > &tree, G &Ab1, G &Ab2)
Definition: graph-inl.h:245
len
size_t len(handle h)
Get the length of a Python object.
Definition: pytypes.h:2448
get
Container::iterator get(Container &c, Position position)
Definition: stdlist_overload.cpp:29
gtwrap.xml_parser.xml_parser.XMLDocParser._parsed_xml_cache
_parsed_xml_cache
Definition: xml_parser.py:20


gtsam
Author(s):
autogenerated on Wed May 28 2025 03:08:44