123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- #!@PYTHON@
- # Copyright (C) 2011 Internet Systems Consortium.
- #
- # Permission to use, copy, modify, and distribute this software for any
- # purpose with or without fee is hereby granted, provided that the above
- # copyright notice and this permission notice appear in all copies.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
- # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
- # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
- # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
- # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
- # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- r"""
- A helper to semi-auto generate Python docstring text from C++ Doxygen
- documentation.
- This script converts an XML-format doxygen documentation for C++ library
- into a template Python docstring for the corresponding Python version
- of the library. While it's not perfect and you'll still need to edit the
- output by hand, but past experiments showed the script produces a pretty
- good template. It will help provide more compatible documentation for
- both C++ and Python versions of library from a unified source (C++ Doxygen
- documentation) with minimizing error-prone and boring manual conversion.
- HOW TO USE IT
- 1. Generate XML output by doxygen. Use bind10/doc/Doxyfile-xml:
- % cd bind10/doc
- % doxygen Doxyfile-xml
- (XML files will be generated under bind10/doc/html/xml)
- 2. Identify the xml file of the conversion target (C++ class, function, etc)
- This is a bit tricky. You'll probably need to do manual search.
- For example, to identify the xml file for a C++ class
- isc::datasrc::memory::ZoneWriter, you might do:
- % cd bind10/doc/html/xml
- % grep ZoneWriter *.xml | grep 'kind="class"'
- index.xml: <compound refid="d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter" kind="class"><name>isc::datasrc::memory::ZoneWriter</name>
- In this case the file under the d4/d3c directory (with .xml suffix) would
- be the file you're looking for.
- 3. Run this script for the xml file:
- % python3 doxygen2pydoc.py <top_srcdir>/doc/html/xml/d4/d3c/classisc_1_1datasrc_1_1memory_1_1ZoneWriter.xml > output.cc
- The template content is dumped to standard out (redirected to file
- "output.cc" in this example).
- Sometimes the script produces additional output to standard error,
- like this:
- Replaced camelCased terms:
- resetMemorySegment => reset_memory_segment
- getConfiguration => get_configuration
- In BIND 10 naming convention for methods is different for C++ and
- Python. This script uses some heuristic guess to convert the
- C++-style method names to likely Python-style ones, and the converted
- method names are used in the dumped template. In many cases the guessed
- names are correct, but you should check this list and make adjustments
- by hand if necessary.
- If there's no standard error output, this type of conversion didn't
- happen.
- 4. Edit and copy the template
- The dumped template has the following organization:
- namespace {
- #ifdef COPY_THIS_TO_MAIN_CC
- { "cleanup", ZoneWriter_cleanup, METH_NOARGS,
- ZoneWriter_cleanup_doc },
- { "install", ZoneWriter_install, METH_NOARGS,
- ZoneWriter_install_doc },
- { "load", ZoneWriter_load, METH_VARARGS,
- ZoneWriter_load_doc },
- #endif // COPY_THIS_TO_MAIN_CC
- const char* const ZoneWriter_doc = "\
- ...
- ";
- const char* const ZoneWriter_install_doc = "\
- ...
- ";
- ...
- }
- The ifdef-ed block is a template for class methods information
- to be added to the corresponding PyMethodDef structure array
- (your wrapper C++ source would have something like ZoneWriter_methods
- of this type). These lines should be copied there. As long as
- the method names and corresponding wrapper function (such as
- ZoneWriter_cleanup) are correct you shouldn't have to edit this part
- (and they would be normally correct, unless the guessed method name
- conversion was needed).
- The rest of the content is a sequence of constant C-string variables.
- Usually the first variable corresponds to the class description, and
- the rest are method descriptions (note that ZoneWriter_install_doc
- is referenced from the ifdef-ed block). The content of this part
- would generally make sense, but you'll often need to make some
- adjsutments by hand. A common examples of such adjustment is to
- replace "NULL" with "None". Also, it's not uncommon that some part
- of the description simply doesn't apply to the Python version or
- that Python specific notes are needed. So go through the description
- carefully and make necessary changes. A common practice is to add
- comments for a summary of adjustments like this:
- // Modifications:
- // NULL->None
- // - removed notes about derived classes (which doesn't apply for python)
- const char* const ZoneWriter_doc = "\
- ...
- ";
- This note will help next time you need to auto-generate and edit the
- template (probably because the original C++ document is updated).
- You can simply copy this part to the main C++ wrapper file, but since
- it's relatively large a common practice is to maintain it in a separate
- file that is exclusively included from the main file: if the name of
- the main file is zonewriter_python.cc, the pydoc strings would be copied
- in zonewriter_python_inc.cc, and the main file would have this line:
- #include "zonewriter_inc.cc"
- (In case you are C++ language police: it's okay to use the unnamed
- name space for a file to be included because it's essentially a part
- of the single .cc file, not expected to be included by others).
- In either case, the ifdef-ed part should be removed.
- ADVANCED FEATURES
- You can use a special "xmlonly" doxygen command in C++ doxygent document
- in order to include Python code excerpt (while hiding it from the doxygen
- output for the C++ version). This command will be converted to
- a special XML tag in the XML output.
- The block enclosed by \xmlonly and \endxmlonly should contain
- a verbatim XML tag named "pythonlisting", in which the python code should
- be placed.
- /// \code
- /// Name name("example.com");
- /// std::cout << name.toText() << std::endl;
- /// \endcode
- ///
- /// \xmlonly <pythonlisting>
- /// name = Name("example.com")
- /// print(name.to_text())
- /// </pythonlisting> \endxmlonly
- Note that there must be a blank line between \endcode and \xmlonly.
- doxygen2pydoc assume the pythonlisting tag is in a separate <para> node.
- This blank ensures doxygen will produce the XML file that meets the
- assumption.
- INTERNAL MEMO (incomplete, and not very unredable yet)
- This simplified utility assumes the following structure:
- ...
- <compounddef ...>
- <compoundname>isc::dns::TSIGError</compoundname>
- <sectiondef kind="user-defined">
- constructor, destructor
- </sectiondef>
- <sectiondef kind="public-type">
- ..
- </sectiondef>
- <sectiondef kind="public-func">
- <memberdef kind="function"...>
- <type>return type (if any)</type>
- <argsstring>(...) [const]</argsstring>
- <name>method name</name>
- <briefdescription>method's brief description</briefdescription>
- <detaileddescription>
- <para>...</para>...
- <para>
- <parameterlist kind="exception">
- <parameteritem>
- <parameternamelist>
- <parametername>Exception name</parametername>
- </parameternamelist>
- <parameterdescription>
- <para>exception desc</para>
- </parameterdescription>
- </parameteritem>
- </parameterlist>
- <parameterlist kind="param">
- <parameteritem>
- <parameternamelist>
- <parametername>param name</parametername>
- </parameternamelist>
- <parameterdescription>
- <para>param desc</para>
- </parameterdescription>
- </parameteritem>
- ...
- </parameterlist>
- <simplesect kind="return">Return value</simplesect>
- </para>
- </detaileddescription>
- </memberdef>
- </sectiondef>
- <sectiondef kind="public-static-attrib|user-defined">
- <memberdef kind="variable"...>
- <name>class-specific-constant</name>
- <initializer>value</initializer>
- <brief|detaileddescription>paragraph(s)</brief|detaileddescription>
- </sectiondef>
- <briefdescription>
- class's brief description
- </briefdescription>
- <detaileddescription>
- class's detailed description
- </detaileddescription>
- </compounddef>
- """
- import re, string, sys, textwrap
- from xml.dom.minidom import parse
- from textwrap import fill, dedent, TextWrapper
- camel_replacements = {}
- member_functions = []
- constructors = []
- class_variables = []
- RE_CAMELTERM = re.compile('([\s\.]|^)[a-z]+[A-Z]\S*')
- RE_SIMPLECAMEL = re.compile("([a-z])([A-Z])")
- RE_CAMELAFTERUPPER = re.compile("([A-Z])([A-Z])([a-z])")
- class Paragraph:
- TEXT = 0
- ITEMIZEDLIST = 1
- CPPLISTING = 2
- PYLISTING = 3
- VERBATIM = 4
- def __init__(self, xml_node):
- if len(xml_node.getElementsByTagName("pythonlisting")) > 0:
- self.type = self.PYLISTING
- self.text = re.sub("///", "", get_text(xml_node))
- elif len(xml_node.getElementsByTagName("verbatim")) > 0:
- self.type = self.VERBATIM
- self.text = get_text(xml_node)
- elif len(xml_node.getElementsByTagName("programlisting")) > 0:
- # We ignore node containing a "programlisting" tag.
- # They are C++ example code, and we are not interested in them
- # in pydoc.
- self.type = self.CPPLISTING
- elif len(xml_node.getElementsByTagName("itemizedlist")) > 0:
- self.type = self.ITEMIZEDLIST
- self.items = []
- for item in xml_node.getElementsByTagName("listitem"):
- self.items.append(get_text(item))
- else:
- self.type = self.TEXT
- # A single textual paragraph could have multiple simple sections
- # if it contains notes.
- self.texts = []
- subnodes = []
- for child in xml_node.childNodes:
- if child.nodeType == child.ELEMENT_NODE and \
- child.nodeName == 'simplesect' and \
- child.getAttribute('kind') == 'note':
- if len(subnodes) > 0:
- self.texts.append(get_text_fromnodelist(subnodes))
- subnodes = []
- subtext = 'Note: '
- for t in child.childNodes:
- subtext += get_text(t)
- self.texts.append(subtext)
- else:
- subnodes.append(child)
- if len(subnodes) > 0:
- self.texts.append(get_text_fromnodelist(subnodes))
- def dump(self, f, wrapper):
- if self.type == self.CPPLISTING:
- return
- elif self.type == self.ITEMIZEDLIST:
- for item in self.items:
- item_wrapper = TextWrapper(\
- initial_indent=wrapper.initial_indent + "- ",
- subsequent_indent=wrapper.subsequent_indent + " ")
- dump_filled_text(f, item_wrapper, item)
- f.write("\\n\\\n")
- elif self.type == self.TEXT:
- for text in self.texts:
- if text != self.texts[0]:
- f.write("\\n\\\n")
- dump_filled_text(f, wrapper, text)
- f.write("\\n\\\n")
- else:
- dump_filled_text(f, None, self.text)
- f.write("\\n\\\n")
- f.write("\\n\\\n")
- class NamedItem:
- def __init__(self, name, desc):
- self.name = name
- self.desc = desc
- def dump(self, f, wrapper):
- # we use deeper indent inside the item list.
- new_initial_indent = wrapper.initial_indent + " " * 2
- new_subsequent_indent = wrapper.subsequent_indent + " " * (2 + 11)
- local_wrapper = TextWrapper(initial_indent=new_initial_indent,
- subsequent_indent=new_subsequent_indent)
- # concatenate name and description with a fixed width (up to 10 chars)
- # for the name, and wrap the entire text, then dump it to file.
- dump_filled_text(f, local_wrapper, "%-10s %s" % (self.name, self.desc))
- f.write("\\n\\\n")
- class FunctionDefinition:
- # function types
- CONSTRUCTOR = 0
- COPY_CONSTRUCTOR = 1
- DESTRUCTOR = 2
- ASSIGNMENT_OP = 3
- OTHER = 4
- def __init__(self):
- self.type = self.OTHER
- self.name = None
- self.pyname = None
- self.args = ""
- self.ret_type = None
- self.brief_desc = None
- self.detailed_desc = []
- self.exceptions = []
- self.parameters = []
- self.returns = None
- self.have_param = False
- def dump_doc(self, f, wrapper=TextWrapper()):
- f.write(self.pyname + "(" + self.args + ")")
- if self.ret_type is not None:
- f.write(" -> " + self.ret_type)
- f.write("\\n\\\n\\n\\\n")
- if self.brief_desc is not None:
- dump_filled_text(f, wrapper, self.brief_desc)
- f.write("\\n\\\n\\n\\\n")
- for para in self.detailed_desc:
- para.dump(f, wrapper)
- if len(self.exceptions) > 0:
- f.write(wrapper.fill("Exceptions:") + "\\n\\\n")
- for ex_desc in self.exceptions:
- ex_desc.dump(f, wrapper)
- f.write("\\n\\\n")
- if len(self.parameters) > 0:
- f.write(wrapper.fill("Parameters:") + "\\n\\\n")
- for param_desc in self.parameters:
- param_desc.dump(f, wrapper)
- f.write("\\n\\\n")
- if self.returns is not None:
- dump_filled_text(f, wrapper, "Return Value(s): " + self.returns)
- f.write("\\n\\\n")
- def dump_pymethod_def(self, f, class_name):
- f.write(' { "' + self.pyname + '", ')
- f.write(class_name + '_' + self.name + ', ')
- if len(self.parameters) == 0:
- f.write('METH_NOARGS,\n')
- else:
- f.write('METH_VARARGS,\n')
- f.write(' ' + class_name + '_' + self.name + '_doc },\n')
- class VariableDefinition:
- def __init__(self, nodelist):
- self.value = None
- self.brief_desc = None
- self.detailed_desc = []
- for node in nodelist:
- if node.nodeName == "name":
- self.name = get_text(node)
- elif node.nodeName == "initializer":
- self.value = get_text(node)
- elif node.nodeName == "briefdescription":
- self.brief_desc = get_text(node)
- elif node.nodeName == "detaileddescription":
- for para in node.childNodes:
- if para.nodeName != "para":
- # ignore surrounding empty nodes
- continue
- self.detailed_desc.append(Paragraph(para))
- def dump_doc(self, f, wrapper=TextWrapper()):
- name_value = self.name
- if self.value is not None:
- name_value += ' = ' + self.value
- dump_filled_text(f, wrapper, name_value)
- f.write('\\n\\\n')
- desc_initial_indent = wrapper.initial_indent + " "
- desc_subsequent_indent = wrapper.subsequent_indent + " "
- desc_wrapper = TextWrapper(initial_indent=desc_initial_indent,
- subsequent_indent=desc_subsequent_indent)
- if self.brief_desc is not None:
- dump_filled_text(f, desc_wrapper, self.brief_desc)
- f.write("\\n\\\n\\n\\\n")
- for para in self.detailed_desc:
- para.dump(f, desc_wrapper)
- def dump_filled_text(f, wrapper, text):
- """Fill given text using wrapper, and dump it to the given file
- appending an escaped CR at each end of line.
- """
- filled_text = wrapper.fill(text) if wrapper is not None else text
- f.write("".join(re.sub("\n", r"\\n\\\n", filled_text)))
- def camel_to_lowerscores(matchobj):
- oldtext = matchobj.group(0)
- newtext = re.sub(RE_SIMPLECAMEL, r"\1_\2", oldtext)
- newtext = re.sub(RE_CAMELAFTERUPPER, r"\1_\2\3", newtext)
- newtext = newtext.lower()
- camel_replacements[oldtext] = newtext
- return newtext.lower()
- def cpp_to_python(text):
- text = text.replace("::", ".")
- text = text.replace('"', '\\"')
- # convert camelCase to "_"-concatenated format
- # (e.g. getLength -> get_length)
- return re.sub(RE_CAMELTERM, camel_to_lowerscores, text)
- def convert_type_name(type_name):
- """Convert C++ type name to python type name using common conventions"""
- # strip off leading 'const' and trailing '&/*'
- type_name = re.sub("^const\S*", "", type_name)
- type_name = re.sub("\S*[&\*]$", "", type_name)
- # We often typedef smart pointers as [Const]TypePtr. Convert them to
- # just "Type"
- type_name = re.sub("^Const", "", type_name)
- type_name = re.sub("Ptr$", "", type_name)
- if type_name == "std::string":
- return "string"
- if re.search(r"(int\d+_t|size_t)", type_name):
- return "integer"
- return type_name
- def get_text(root, do_convert=True):
- """Recursively extract bare text inside the specified node (root),
- concatenate all extracted text and return the result.
- """
- nodelist = root.childNodes
- rc = []
- for node in nodelist:
- if node.nodeType == node.TEXT_NODE:
- if do_convert:
- rc.append(cpp_to_python(node.data))
- else:
- rc.append(node.data)
- elif node.nodeType == node.ELEMENT_NODE:
- rc.append(get_text(node))
- # return the result, removing any leading newlines (that often happens for
- # brief descriptions, which will cause lines not well aligned)
- return re.sub("^(\n*)", "", ''.join(rc))
- def get_text_fromnodelist(nodelist, do_convert=True):
- """Recursively extract bare text inside the specified node (root),
- concatenate all extracted text and return the result.
- """
- rc = []
- for node in nodelist:
- if node.nodeType == node.TEXT_NODE:
- if do_convert:
- rc.append(cpp_to_python(node.data))
- else:
- rc.append(node.data)
- elif node.nodeType == node.ELEMENT_NODE:
- rc.append(get_text(node))
- # return the result, removing any leading newlines (that often happens for
- # brief descriptions, which will cause lines not well aligned)
- return re.sub("^(\n*)", "", ''.join(rc))
- def parse_parameters(nodelist):
- rc = []
- for node in nodelist:
- if node.nodeName != "parameteritem":
- continue
- # for simplicity, we assume one parametername and one
- # parameterdescription for each parameter.
- name = get_text(node.getElementsByTagName("parametername")[0])
- desc = get_text(node.getElementsByTagName("parameterdescription")[0])
- rc.append(NamedItem(name, desc))
- return rc
- def parse_function_description(func_def, nodelist):
- for node in nodelist:
- # nodelist contains beginning and ending empty text nodes.
- # ignore them (otherwise they cause disruption below).
- if node.nodeName != "para":
- continue
- if node.getElementsByTagName("parameterlist"):
- # within this node there may be exception list, parameter list,
- # and description for return value. parse and store them
- # seprately.
- for paramlist in node.getElementsByTagName("parameterlist"):
- if paramlist.getAttribute("kind") == "exception":
- func_def.exceptions = \
- parse_parameters(paramlist.childNodes)
- elif paramlist.getAttribute("kind") == "param":
- func_def.parameters = \
- parse_parameters(paramlist.childNodes)
- if node.getElementsByTagName("simplesect"):
- simplesect = node.getElementsByTagName("simplesect")[0]
- if simplesect.getAttribute("kind") == "return":
- func_def.returns = get_text(simplesect)
- else:
- # for normal text, python listing and itemized list, append them
- # to the list of paragraphs
- func_def.detailed_desc.append(Paragraph(node))
- def parse_function(func_def, class_name, nodelist):
- for node in nodelist:
- if node.nodeName == "name":
- func_def.name = get_text(node, False)
- func_def.pyname = cpp_to_python(func_def.name)
- elif node.nodeName == "argsstring":
- # extract parameter names only, assuming they immediately follow
- # their type name + space, and are immeidatelly followed by
- # either "," or ")". If it's a pointer or reference, */& is
- # prepended to the parameter name without a space:
- # e.g. (int var1, char *var2, Foo &var3)
- args = get_text(node, False)
- # extract parameter names, possibly with */&
- func_def.args = ', '.join(re.findall(r"\s(\S+)[,)]", args))
- # then remove any */& symbols
- func_def.args = re.sub("[\*&]", "", func_def.args)
- elif node.nodeName == "type" and node.hasChildNodes():
- func_def.ret_type = convert_type_name(get_text(node, False))
- elif node.nodeName == "param":
- func_def.have_param = True
- elif node.nodeName == "briefdescription":
- func_def.brief_desc = get_text(node)
- elif node.nodeName == "detaileddescription":
- parse_function_description(func_def, node.childNodes)
- # identify the type of function using the name and arg
- if func_def.name == class_name and \
- re.search("^\(const " + class_name + " &[^,]*$", args):
- # This function is ClassName(const ClassName& param), which is
- # the copy constructor.
- func_def.type = func_def.COPY_CONSTRUCTOR
- elif func_def.name == class_name:
- # if it's not the copy ctor but the function name == class name,
- # it's a constructor.
- func_def.type = func_def.CONSTRUCTOR
- elif func_def.name == "~" + class_name:
- func_def.type = func_def.DESTRUCTOR
- elif func_def.name == "operator=":
- func_def.type = func_def.ASSIGNMENT_OP
- # register the definition to the approriate list
- if func_def.type == func_def.CONSTRUCTOR:
- constructors.append(func_def)
- elif func_def.type == func_def.OTHER:
- member_functions.append(func_def)
- def parse_functions(class_name, nodelist):
- for node in nodelist:
- if node.nodeName == "memberdef" and \
- node.getAttribute("kind") == "function":
- func_def = FunctionDefinition()
- parse_function(func_def, class_name, node.childNodes)
- def parse_class_variables(class_name, nodelist):
- for node in nodelist:
- if node.nodeName == "memberdef" and \
- node.getAttribute("kind") == "variable":
- class_variables.append(VariableDefinition(node.childNodes))
- def dump(f, class_name, class_brief_doc, class_detailed_doc):
- f.write("namespace {\n")
- f.write('#ifdef COPY_THIS_TO_MAIN_CC\n')
- for func in member_functions:
- func.dump_pymethod_def(f, class_name)
- f.write('#endif // COPY_THIS_TO_MAIN_CC\n\n')
- f.write("const char* const " + class_name + '_doc = "\\\n')
- if class_brief_doc is not None:
- f.write("".join(re.sub("\n", r"\\n\\\n", fill(class_brief_doc))))
- f.write("\\n\\\n")
- f.write("\\n\\\n")
- if len(class_detailed_doc) > 0:
- for para in class_detailed_doc:
- para.dump(f, wrapper=TextWrapper())
- # dump constructors
- for func in constructors:
- indent = " " * 4
- func.dump_doc(f, wrapper=TextWrapper(initial_indent=indent,
- subsequent_indent=indent))
- # dump class variables
- if len(class_variables) > 0:
- f.write("Class constant data:\\n\\\n")
- for var in class_variables:
- var.dump_doc(f)
- f.write("\";\n")
- for func in member_functions:
- f.write("\n")
- f.write("const char* const " + class_name + "_" + func.name + \
- "_doc = \"\\\n");
- func.dump_doc(f)
- f.write("\";\n")
- f.write("} // unnamed namespace") # close namespace
- if __name__ == '__main__':
- dom = parse(sys.argv[1])
- class_elements = dom.getElementsByTagName("compounddef")[0].childNodes
- class_brief_doc = None
- class_detailed_doc = []
- for node in class_elements:
- if node.nodeName == "compoundname":
- # class name is the last portion of the period-separated fully
- # qualified class name. (this should exist)
- class_name = re.split("\.", get_text(node))[-1]
- if node.nodeName == "briefdescription":
- # we assume a brief description consists at most one para
- class_brief_doc = get_text(node)
- elif node.nodeName == "detaileddescription":
- # a detaild description consists of one or more paragraphs
- for para in node.childNodes:
- if para.nodeName != "para": # ignore surrounding empty nodes
- continue
- class_detailed_doc.append(Paragraph(para))
- elif node.nodeName == "sectiondef" and \
- node.getAttribute("kind") == "public-func":
- parse_functions(class_name, node.childNodes)
- elif node.nodeName == "sectiondef" and \
- node.getAttribute("kind") == "public-static-attrib":
- parse_class_variables(class_name, node.childNodes)
- elif node.nodeName == "sectiondef" and \
- node.getAttribute("kind") == "user-defined":
- # there are two possiblities: functions and variables
- for child in node.childNodes:
- if child.nodeName != "memberdef":
- continue
- if child.getAttribute("kind") == "function":
- parse_function(FunctionDefinition(), class_name,
- child.childNodes)
- elif child.getAttribute("kind") == "variable":
- class_variables.append(VariableDefinition(child.childNodes))
- dump(sys.stdout, class_name, class_brief_doc, class_detailed_doc)
- if len(camel_replacements) > 0:
- sys.stderr.write("Replaced camelCased terms:\n")
- for oldterm in camel_replacements.keys():
- sys.stderr.write("%s => %s\n" % (oldterm,
- camel_replacements[oldterm]))
|