Orb/python/doxygen/linkinserter.py
changeset 0 42188c7ea2d9
child 1 82f11024044a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orb/python/doxygen/linkinserter.py	Thu Jan 21 17:29:01 2010 +0000
@@ -0,0 +1,474 @@
+# Copyright (c) 2007-2010 Nokia Corporation and/or its subsidiary(-ies) All rights reserved.
+# This component and the accompanying materials are made available under the terms of the License 
+# "Eclipse Public License v1.0" which accompanies this distribution, 
+# and is available at the URL "http://www.eclipse.org/legal/epl-v10.html".
+#
+# Initial Contributors:
+# Nokia Corporation - initial contribution.
+#
+# Contributors:
+#
+# Description:
+#
+import unittest
+import sys
+from cStringIO import StringIO
+from optparse import OptionParser
+from xml.etree import ElementTree as etree
+import os.path
+import logging
+import shutil
+
+__version__ = "0.1"
+
+
+class LinkFile(object):
+    """
+    Parses string representations of cxxapiref XML files and returns an updated
+    string representation with inserted ids for linking to cxxFunctions.
+    """
+    
+    def _get_cxxfunction_elems(self, elem):
+        """
+        Takes an elements and generates a list of all child elements that are called cxxFunction 
+        """
+        return [e for e in elem.getiterator() if e.tag == "cxxFunction"]
+    
+    def _get_funcs_with_no_params(self, func_elem_list):
+        """
+        Takes a list of cxxFunction elements and returns a list of those with no parameters.
+        """
+        no_params = []
+        for func in func_elem_list:
+            apiname = func.find("apiName").text
+            params_elem = func.find("cxxFunctionDetail/cxxFunctionDefinition/cxxFunctionParameters")
+            # if cxxFunctionParameters has no children
+            if len(params_elem.getiterator()) == 1:
+                no_params.append(apiname)            
+        return no_params
+    
+    
+    def _filter_funcs_with_no_params(self, func_elem_list):
+        """
+        Takes a list of cxxFunction elements and returns a list with parameterless functions
+        and all of their overloads removed.
+        """
+        no_param_funcs = self._get_funcs_with_no_params(func_elem_list)
+        return [func_elem for func_elem in func_elem_list if not func_elem.find("apiName").text in no_param_funcs]
+    
+    def _filter_duplicate_funcs(self, func_elem_list):
+        """
+        Takes a list of cxxFunction elements and returns a list ones with unique apiName text.
+        In the case of overloads the first instance found is taken and the rest are filtered.
+        """
+        seen_func_names = []
+        filtered = []
+        for func_elem in func_elem_list:
+            this_apiname = func_elem.find("apiName").text
+            if not this_apiname in seen_func_names:
+                filtered.append(func_elem)
+                seen_func_names.append(this_apiname)
+        return filtered
+    
+    def _insert_id_into_cxxfunction_apiname(self, func_elem):
+        """
+        Takes a cxxFunction element. Returns the element with an id inserted into the child apiName element.
+        """
+        function_scoped_name=func_elem.find("cxxFunctionDetail/cxxFunctionDefinition/cxxFunctionScopedName").text
+        
+        if function_scoped_name == None:
+            function_scoped_name = ""
+        else:
+            function_scoped_name+="::"
+        
+        apiname_id = "".join([function_scoped_name,func_elem.find("apiName").text,"()"])
+
+        func_elem.find("apiName").attrib["id"] = apiname_id
+        return func_elem
+        
+    def get_func_elems_to_linkify(self, root):
+        cxxfunction_elems = self._get_cxxfunction_elems(root)
+        cxxfunction_elems = self._filter_funcs_with_no_params(cxxfunction_elems)
+        cxxfunction_elems = self._filter_duplicate_funcs(cxxfunction_elems)
+        return cxxfunction_elems
+    
+    def get_linkified(self, file_as_string):
+        """
+        Takes a string representation of a cxxapiref file and returns the string
+        with inserted cxxFunction ids for any functions that fit the insertion rule.
+        
+        The id insertion rule is:
+        
+        If a function and none of its overloads have no arguments then insert an
+        id that represents a function with no arguments over the first function definition encountered.
+        The id is inserted into the apiName child element of the function.
+        """
+        try:
+            root = etree.fromstring(file_as_string)
+        except Exception, e:
+            raise Exception("Failed to parse string as xml file error was %s" % e)
+        funcs_to_linkify = self.get_func_elems_to_linkify(root)
+        for index in xrange(0, len(funcs_to_linkify)):
+            func = self._insert_id_into_cxxfunction_apiname(funcs_to_linkify[index])
+        return etree.tostring(root)
+        
+
+class LinkInserter(object):
+    
+    def __init__(self, link_file):
+        self.link_file = link_file
+        
+    def _handle_xml_file(self, xml_file):
+        """
+        Runs linkify function on each file and writes the result to disk
+        """
+        logging.info("Inserting links into %s" % xml_file)
+        content = open(xml_file, "r").read()
+        try:
+            linkified_contents = self.link_file.get_linkified(content)
+        except Exception,e:
+            logging.error("%s %s" %(e, xml_file))
+            return
+
+        try:
+            f = open(xml_file, "w")
+        except Exception, e:
+            raise IOError("Could not open xml file %s for writing, error was: %s" % (xml_file, e))
+        else:
+            f.write(linkified_contents)
+            f.close()
+        
+    def _handle_xml_files(self, xml_files):
+        """
+        Iterates over a list of files and calls _handle_xml_file on them
+        """
+        for xml_file in xml_files:
+            self._handle_xml_file(xml_file)
+            
+    def _do_linkifying(self, dir):
+        """
+        Takes a directory and calls a handler function on a list of xml files in that and sub directories.
+        """
+        for root, dirs, files in os.walk(dir):
+            xml_file_paths = [os.path.join(root, f) for f in os.listdir(root) if os.path.splitext(f)[1].lower() == (".xml")]
+            self._handle_xml_files(xml_file_paths)
+            
+    def linkify_dir(self, dir):
+        if not os.path.exists(os.path.abspath(dir)):
+            raise IOError("Directory to linkify does not exist: %s" % dir) 
+        self._do_linkifying(dir)
+        
+
+def insertlinks(xml_dir):
+    link_inserter = LinkInserter(LinkFile())
+    link_inserter.linkify_dir(xml_dir)
+
+
+def main():
+    usage = "usage: %prog <Path to the XML content>"
+    parser = OptionParser(usage, version='%prog ' + __version__)
+    (options, args) = parser.parse_args()
+    if len(args) < 1:
+        parser.print_help()
+        parser.error("Please supply the path to the XML content")
+    insertlinks(args[0])
+
+
+if __name__ == '__main__':
+    sys.exit(main())
+
+    
+######################################
+# Test code
+######################################
+
+class TestLinkFile(unittest.TestCase):
+
+    def setUp(self):
+        self.link_file = LinkFile()
+        
+    def test__get_funcs_with_no_params(self):
+        func = """    
+        <cxxClass><cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+        <apiName>Init0</apiName>
+        <cxxFunctionDetail>
+            <cxxFunctionDefinition>
+                <cxxFunctionParameters/>
+            </cxxFunctionDefinition>
+        </cxxFunctionDetail>
+    </cxxFunction></cxxClass>
+"""
+        root = etree.fromstring(func)
+        func_list = [e for e in root.getiterator() if e.tag == "cxxFunction"]
+        expected = ["Init0"]
+        returned = self.link_file._get_funcs_with_no_params(func_list)
+        self.assertEqual(expected, returned)
+        
+    def test__get_funcs_with_no_params_ignores_a_func_with_params(self):
+        func = """    
+        <cxxClass><cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+        <apiName>Init0</apiName>
+        <cxxFunctionDetail>
+            <cxxFunctionDefinition>
+                <cxxFunctionParameters>
+                    <cxxFunctionParameter>
+                    <cxxFunctionParameterDeclaredType>
+                        <apiRelation keyref="_always_online_manager_client_8cpp_1a8240e11f17c80b6b222fc2af50234da4">TUint32</apiRelation>
+                        </cxxFunctionParameterDeclaredType>
+                        <cxxFunctionParameterDeclarationName>a0</cxxFunctionParameterDeclarationName>
+                        <apiDefNote/>
+                    </cxxFunctionParameter>
+                </cxxFunctionParameters>
+            </cxxFunctionDefinition>
+        </cxxFunctionDetail>
+    </cxxFunction></cxxClass>
+"""
+        root = etree.fromstring(func)
+        func_list = [e for e in root.getiterator() if e.tag == "cxxFunction"]
+        expected = []
+        returned = self.link_file._get_funcs_with_no_params(func_list)
+        self.assertEqual(expected, returned)    
+    
+    def test_filter_duplicate_funcs_ignores_duplicate_funcs(self):
+        func = """    
+        <cxxClass>
+            <cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+                <apiName>Init0</apiName>
+            </cxxFunction>
+            <cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+                <apiName>Init0</apiName>
+            </cxxFunction>
+        </cxxClass>
+"""     
+        root = etree.fromstring(func)   
+        func_list = [e for e in root.getiterator() if e.tag == "cxxFunction"]
+        expected = [func_list[0]]
+        returned = self.link_file._filter_duplicate_funcs(func_list)
+        self.assertEqual(expected, returned)  
+        
+    def test__filter_funcs_with_no_params_filters_funcs_with_no_params(self):
+        func = """    
+        <cxxClass>
+            <cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+                <apiName>Init0</apiName>
+                <cxxFunctionDetail>
+                    <cxxFunctionDefinition>
+                        <cxxFunctionParameters/>
+                    </cxxFunctionDefinition>
+                </cxxFunctionDetail>
+            </cxxFunction>
+            <cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+                <apiName>Init1</apiName>
+                <cxxFunctionDetail>
+                    <cxxFunctionDefinition>
+                        <cxxFunctionParameters>
+                            <cxxFunctionParameter>
+                                <cxxFunctionParameterDeclaredType>
+                                    <apiRelation keyref="_always_online_manager_client_8cpp_1a8240e11f17c80b6b222fc2af50234da4">TUint32</apiRelation>
+                                </cxxFunctionParameterDeclaredType>
+                                <cxxFunctionParameterDeclarationName>a0</cxxFunctionParameterDeclarationName>
+                                <apiDefNote/>
+                            </cxxFunctionParameter>
+                        </cxxFunctionParameters>
+                    </cxxFunctionDefinition>
+                </cxxFunctionDetail>
+            </cxxFunction>
+        </cxxClass>
+"""     
+        root = etree.fromstring(func)   
+        func_list = [e for e in root.getiterator() if e.tag == "cxxFunction"]
+        expected = [func_list[1]]
+        returned = self.link_file._filter_funcs_with_no_params(func_list)
+        self.assertEqual(expected, returned)
+        
+    def test__insert_id_into_cxxfunction_apiname(self):
+        func_str = """<cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+        <apiName>Init0</apiName>
+        <shortdesc/>
+        <cxxFunctionDetail>
+            <cxxFunctionDefinition>
+                <cxxFunctionAccessSpecifier value="public"/>
+                <cxxFunctionStorageClassSpecifierStatic/>
+                <cxxFunctionDeclaredType>void</cxxFunctionDeclaredType>
+                <cxxFunctionScopedName>BTrace</cxxFunctionScopedName>
+                <cxxFunctionPrototype>static void Init0(TUint32 a0)</cxxFunctionPrototype>
+                <cxxFunctionNameLookup>BTrace::Init0(TUint32 a0)</cxxFunctionNameLookup>
+                <cxxFunctionParameters>
+                    <cxxFunctionParameter>
+                        <cxxFunctionParameterDeclaredType>
+                            <apiRelation keyref="_always_online_manager_client_8cpp_1a8240e11f17c80b6b222fc2af50234da4">TUint32</apiRelation>
+                        </cxxFunctionParameterDeclaredType>
+                        <cxxFunctionParameterDeclarationName>a0</cxxFunctionParameterDeclarationName>
+                        <apiDefNote/>
+                    </cxxFunctionParameter>
+                </cxxFunctionParameters>
+                <cxxFunctionAPIItemLocation>
+                    <cxxFunctionDeclarationFile name="filePath" value="D:/epoc32/include/e32btrace.h"/>
+                    <cxxFunctionDeclarationFileLine name="lineNumber" value="3882"/>
+                    <cxxFunctionDefinitionFile name="filePath" value="D:/EPOC/master/sf/mw/messagingmw/messagingfw/alwaysonline/AlwaysOnlineManager/src/AlwaysOnlineManagerClient.cpp"/>
+                    <cxxFunctionDefinitionFileLineStart name="lineNumber" value="-1"/>
+                    <cxxFunctionDefinitionFileLineEnd name="lineNumber" value="-1"/>
+                </cxxFunctionAPIItemLocation>
+            </cxxFunctionDefinition>
+            <apiDesc/>
+        </cxxFunctionDetail>
+    </cxxFunction>"""
+        func_elem = etree.fromstring(func_str)
+        returned = self.link_file._insert_id_into_cxxfunction_apiname(func_elem)
+        self.assertEquals(returned.find("apiName").attrib["id"], "BTrace::Init0()")
+
+    def test__insert_id_into_cxxfunction_apiname_when_the_cxxfunction_has_no_scoped_name(self):
+        func_str = """<cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+        <apiName>Init0</apiName>
+        <shortdesc/>
+        <cxxFunctionDetail>
+            <cxxFunctionDefinition>
+                <cxxFunctionAccessSpecifier value="public"/>
+                <cxxFunctionStorageClassSpecifierStatic/>
+                <cxxFunctionDeclaredType>void</cxxFunctionDeclaredType>
+                <cxxFunctionScopedName/>
+                <cxxFunctionPrototype>static void Init0(TUint32 a0)</cxxFunctionPrototype>
+                <cxxFunctionNameLookup>BTrace::Init0(TUint32 a0)</cxxFunctionNameLookup>
+                <cxxFunctionParameters>
+                    <cxxFunctionParameter>
+                        <cxxFunctionParameterDeclaredType>
+                            <apiRelation keyref="_always_online_manager_client_8cpp_1a8240e11f17c80b6b222fc2af50234da4">TUint32</apiRelation>
+                        </cxxFunctionParameterDeclaredType>
+                        <cxxFunctionParameterDeclarationName>a0</cxxFunctionParameterDeclarationName>
+                        <apiDefNote/>
+                    </cxxFunctionParameter>
+                </cxxFunctionParameters>
+                <cxxFunctionAPIItemLocation>
+                    <cxxFunctionDeclarationFile name="filePath" value="D:/epoc32/include/e32btrace.h"/>
+                    <cxxFunctionDeclarationFileLine name="lineNumber" value="3882"/>
+                    <cxxFunctionDefinitionFile name="filePath" value="D:/EPOC/master/sf/mw/messagingmw/messagingfw/alwaysonline/AlwaysOnlineManager/src/AlwaysOnlineManagerClient.cpp"/>
+                    <cxxFunctionDefinitionFileLineStart name="lineNumber" value="-1"/>
+                    <cxxFunctionDefinitionFileLineEnd name="lineNumber" value="-1"/>
+                </cxxFunctionAPIItemLocation>
+            </cxxFunctionDefinition>
+            <apiDesc/>
+        </cxxFunctionDetail>
+    </cxxFunction>"""
+        func_elem = etree.fromstring(func_str)
+        returned = self.link_file._insert_id_into_cxxfunction_apiname(func_elem)
+        self.assertEquals(returned.find("apiName").attrib["id"], "Init0()")
+        
+    def test_i_can_insert_an_id_to_a_cxxfunction_apiname(self):
+        file_as_string = """    
+        <cxxClass>
+<cxxFunction id="class_b_trace_1a7217f2fa88e99af3dbb5827fdc8507b7">
+        <apiName>Init0</apiName>
+        <shortdesc/>
+        <cxxFunctionDetail>
+            <cxxFunctionDefinition>
+                <cxxFunctionAccessSpecifier value="public"/>
+                <cxxFunctionStorageClassSpecifierStatic/>
+                <cxxFunctionDeclaredType>void</cxxFunctionDeclaredType>
+                <cxxFunctionScopedName>BTrace</cxxFunctionScopedName>
+                <cxxFunctionPrototype>static void Init0(TUint32 a0)</cxxFunctionPrototype>
+                <cxxFunctionNameLookup>BTrace::Init0(TUint32 a0)</cxxFunctionNameLookup>
+                <cxxFunctionParameters>
+                    <cxxFunctionParameter>
+                        <cxxFunctionParameterDeclaredType>
+                            <apiRelation keyref="_always_online_manager_client_8cpp_1a8240e11f17c80b6b222fc2af50234da4">TUint32</apiRelation>
+                        </cxxFunctionParameterDeclaredType>
+                        <cxxFunctionParameterDeclarationName>a0</cxxFunctionParameterDeclarationName>
+                        <apiDefNote/>
+                    </cxxFunctionParameter>
+                </cxxFunctionParameters>
+                <cxxFunctionAPIItemLocation>
+                    <cxxFunctionDeclarationFile name="filePath" value="D:/epoc32/include/e32btrace.h"/>
+                    <cxxFunctionDeclarationFileLine name="lineNumber" value="3882"/>
+                    <cxxFunctionDefinitionFile name="filePath" value="D:/EPOC/master/sf/mw/messagingmw/messagingfw/alwaysonline/AlwaysOnlineManager/src/AlwaysOnlineManagerClient.cpp"/>
+                    <cxxFunctionDefinitionFileLineStart name="lineNumber" value="-1"/>
+                    <cxxFunctionDefinitionFileLineEnd name="lineNumber" value="-1"/>
+                </cxxFunctionAPIItemLocation>
+            </cxxFunctionDefinition>
+            <apiDesc/>
+        </cxxFunctionDetail>
+    </cxxFunction>
+        </cxxClass>"""
+        returned = self.link_file.get_linkified(file_as_string)
+        self.assertEquals(etree.fromstring(returned).find("cxxFunction/apiName").attrib["id"], "BTrace::Init0()")
+
+
+basic_class_file_str = """\    
+<cxxClass>
+    <cxxFunction id="function_id">
+        <apiName>Init0</apiName>
+        <shortdesc/>
+        <cxxFunctionDetail>
+            <cxxFunctionDefinition>
+                <cxxFunctionAccessSpecifier value="public"/>
+                <cxxFunctionStorageClassSpecifierStatic/>
+                <cxxFunctionDeclaredType>void</cxxFunctionDeclaredType>
+                <cxxFunctionScopedName>BTrace</cxxFunctionScopedName>
+                <cxxFunctionPrototype>static void Init0()</cxxFunctionPrototype>
+                <cxxFunctionNameLookup>BTrace::Init0()</cxxFunctionNameLookup>
+                <cxxFunctionParameters/>
+                <cxxFunctionAPIItemLocation>
+                    <cxxFunctionDeclarationFile name="filePath" value="D:/epoc32/include/e32btrace.h"/>
+                    <cxxFunctionDeclarationFileLine name="lineNumber" value="3882"/>
+                    <cxxFunctionDefinitionFile name="filePath" value="D:/EPOC/master/sf/mw/messagingmw/messagingfw/alwaysonline/AlwaysOnlineManager/src/AlwaysOnlineManagerClient.cpp"/>
+                    <cxxFunctionDefinitionFileLineStart name="lineNumber" value="-1"/>
+                    <cxxFunctionDefinitionFileLineEnd name="lineNumber" value="-1"/>
+                </cxxFunctionAPIItemLocation>
+            </cxxFunctionDefinition>
+            <apiDesc/>
+        </cxxFunctionDetail>
+    </cxxFunction>
+</cxxClass>"""
+
+
+xml_files = {
+               "basic_xml_file_1.xml": """<tag1></tag1>""",
+               "basic_xml_file_2.xml": """<tag2></tag2>""",
+               }
+no_xml_files = {
+               "non_xml_file.txt": """Some text""",                     
+               }
+
+
+class DummyLinkFile(object):
+    
+    def __init__(self):
+        self.visited_files = []
+    
+    def get_linkified(self, file_as_string):
+        self.visited_files.append(file_as_string)
+        return file_as_string
+        
+class TestLinkInserter(unittest.TestCase):
+    
+    def setUp(self):
+        self.dummy_link_file = DummyLinkFile()
+        self.inserter = LinkInserter(self.dummy_link_file)
+        
+    def test__handle_xml_file_writes_to_a_file(self):
+        tmp_file_path = os.getcwd() + os.sep + "tmp_file.xml"
+        tmp_file = open(tmp_file_path, "w")
+        tmp_file.write(basic_class_file_str)
+        tmp_file.close()
+        self.inserter._handle_xml_file(tmp_file_path)
+        self.assertEquals(self.dummy_link_file.visited_files, [basic_class_file_str])
+        os.remove(tmp_file_path)
+        
+    def test_i_raise_an_exception_when_dir_doesnt_exist(self):
+        self.assertRaises(IOError, self.inserter.linkify_dir, "non_existant_dir")
+        
+    def test_i_call_linkify_on_each_file_xml_file_in_a_dir(self):
+        basic_files = {}
+        basic_files.update(xml_files)
+        basic_files.update(no_xml_files)
+        test_dir = os.path.join(os.getcwd(), "test_dir")
+        os.mkdir(test_dir)
+        for filename,file_content in basic_files.items():
+            handle = open(os.path.join(test_dir, filename),"w")
+            handle.write(file_content)
+            handle.close()
+        self.inserter._do_linkifying(test_dir)
+        self.dummy_link_file.visited_files.sort()
+        xml_files_input = xml_files.values()
+        xml_files_input.sort()
+        self.assertEquals(self.dummy_link_file.visited_files, xml_files_input)      
+        shutil.rmtree(test_dir)