diff -r 42188c7ea2d9 -r 82f11024044a Orb/python/doxygen/guidiser.py --- a/Orb/python/doxygen/guidiser.py Thu Jan 21 17:29:01 2010 +0000 +++ b/Orb/python/doxygen/guidiser.py Thu Mar 18 18:26:18 2010 +0000 @@ -18,14 +18,16 @@ import sys import shutil import xml +import logging +from optparse import OptionParser, check_choice from xml.etree import ElementTree as etree from cStringIO import StringIO -from lib import scan, main, xml_decl, doctype_identifier +from lib import scan, xml_decl, doctype_identifier, XmlParser +from doxyidredirect import DoxyIdRedirect, ExceptionDoxyIdRedirectLookup __version__ = "0.1" - class Guidiser(object): """ A simple class that parses an xml file and converts the values of all @@ -39,62 +41,219 @@ >>> root.attrib['id'] 'GUID-25825EC4-341F-3EA4-94AA-7DCE380E6D2E' """ - def __init__(self, namespace='www.nokia.com'): + # Publishing targets + PT_MODE = 0 + PT_DITAOT = 1 + PUBLISHING_TARGETS = (PT_MODE, PT_DITAOT) + + def __init__(self, namespace='www.nokia.com', publishing_target=0, xmlparser=XmlParser(), doxyidredirect=DoxyIdRedirect(None)): self.namespace = self._get_namespace(namespace) - + self.set_publishing_target(publishing_target) + self.xmlparser = xmlparser + self.doxyidredirect = doxyidredirect + + def set_publishing_target(self, target): + if not target in self.PUBLISHING_TARGETS: + raise Exception('Invalid Publishing Target \"%s\"' % target) + self._publishing_target = target + + def get_publishing_target(self): + return self._publishing_target + def _get_namespace(self, namespace, LEN_BYTES=16): if len(namespace) < LEN_BYTES: namespace = namespace + (' ' * (LEN_BYTES - len(namespace))) return uuid.UUID(bytes=namespace[:LEN_BYTES]) - def _get_guid(self, id): - # Sometimes need to remove filepath and hash if id is an href - id = id.split('#')[-1] - # If id is an href and points to a ditamap, then the id of the map is filename minus ".ditamap" - if id.endswith(".ditamap"): - id = id[:-8] - return ('GUID-%s' % (uuid.uuid3(self.namespace, id))).upper() + def _get_guid(self, fqn): + return ('GUID-%s' % (uuid.uuid3(self.namespace, fqn))).upper() + + def _guidise_href(self, href, tag): + if tag == "xref": + return self._guidise_xref_href(href) + else: + # Tag is a topicref or topicref descended element + return self._guidise_topicref_href(href) + + def _guidise_topicref_href(self, href): + # Guidise an href that points to a ditamap + # NOTE: the id of the map is assumed to be the same as the filename + # (minus the ".ditamap" extension) + if href.endswith(".ditamap"): + guid = self._get_guid(href[:-len(".ditamap")]) + if self.get_publishing_target() == self.PT_DITAOT: + guid += ".ditamap" + return guid + + # Guidise an href that points to a topic + # NOTE: Doxygen currently outputs "filepath#topicid" for topicref hrefs + # the "#topicid" is redundant (as topicrefs can't reference below the topic level) + # so will probably be removed from doxygen output at some point. + filename = href.split('#')[0] + id = os.path.splitext(filename)[0] + fqn = None + try: + filename, fqn = self.doxyidredirect.lookupId(id) + except ExceptionDoxyIdRedirectLookup, err: + logging.error("Could not file id %s in DoxyIdRedirect\n" % id) + #if the id was not found just guidise the id + #this is just to make the id unique for mode + guid = self._get_guid(fqn) if fqn else self._get_guid(id) + if self.get_publishing_target() == self.PT_DITAOT: + guid+=".xml" + return guid + + def _guidise_xref_href(self, href): + # Don't guidise references without hashes. Assume they are filepaths + # to files other than ditatopics + if href.find('#') == -1: + return href + + # Doxygen currently outputs hrefs in the format autolink_8cpp.xml#autolink_8cpp_1ae0e289308b6d2cbb5c86e753741981dc + # The right side of the # is not enough to extract the fully qualified name of the function because it is md5ed + # Send the right side to doxyidredirect to get the fqn of the function + filename, id = href.split('#') + try: + fqn = self.doxyidredirect.lookupId(id)[1] + except ExceptionDoxyIdRedirectLookup, err: + logging.error("No API name for element id %s, guidising id instead" % id) + fqn = None + guid = self._get_guid(fqn) if fqn else self._get_guid(id) + + basename, ext = os.path.splitext(filename) + try: + base_guid = self._get_guid(self.doxyidredirect.lookupId(basename)[1]) + except ExceptionDoxyIdRedirectLookup, e: + base_guid = self._get_guid(basename) + + if self.get_publishing_target() == self.PT_DITAOT: + return base_guid + ext + "#" + guid + else: + return guid + + def _guidise_id(self, id): + try: + filename, fqn = self.doxyidredirect.lookupId(id) + return self._get_guid(fqn) + except ExceptionDoxyIdRedirectLookup, err: + logging.debug("Could not file id %s in DoxyIdRedirect\n" % id) + return self._get_guid(id) def guidise(self, xmlfile): - def _update_attrib(el, key): - child.attrib[key] = self._get_guid(child.attrib[key]) + #WORKAROUND: ElementTree provides no function to set prefixes and makes up its own if they are not set (ns0, ns1, ns2) + etree._namespace_map["http://dita.oasis-open.org/architecture/2005/"] = 'ditaarch' try: root = etree.parse(xmlfile).getroot() except xml.parsers.expat.ExpatError, e: - sys.stderr.write("ERROR: %s could not be parser: %s\n" % (xmlfile, str(e))) + logging.error("%s could not be parsed: %s\n" % (xmlfile, str(e))) return None for child in root.getiterator(): for key in [key for key in ('id', 'href', 'keyref') if key in child.attrib]: - _update_attrib(child, key) - return root - + if key == 'id': + child.attrib['id'] = self._guidise_id(child.attrib['id']) + elif key == 'href': + if 'format' in child.attrib and child.attrib['format'] == 'html': + continue + else: + base_dir = os.path.dirname(xmlfile) if isinstance(xmlfile, str) else "" + child.attrib['href'] = self._guidise_href(child.attrib['href'], child.tag) + elif key == 'keyref': + child.attrib['keyref'] = self._get_guid(child.attrib['keyref']) -def updatefiles(xmldir): - guidiser = Guidiser() + return root + + +def updatefiles(xmldir, publishing_target=Guidiser.PT_MODE): + guidiser = Guidiser(publishing_target=publishing_target, doxyidredirect=DoxyIdRedirect(xmldir)) for filepath in scan(xmldir): + logging.debug('Guidising file \"%s\"' % filepath) root = guidiser.guidise(filepath) if root is not None: - os.chmod(filepath, stat.S_IWRITE) + try: + os.chmod(filepath, stat.S_IWRITE) + except Exception, e: + logging.error("Could not make file \"%s\" writable, error was \"%s\"" % (filepath, e)) + continue with open(filepath, 'w') as f: f.write(xml_decl()+'\n') - f.write(doctype_identifier(root.tag)+'\n') + try: + doc_id = doctype_identifier(root.tag) + except Exception, e: + logging.error("Could not write doctype identifier for file \"%s\", error was \"%s\"" + %(filepath, e)) + else: + f.write(doc_id+'\n') f.write(etree.tostring(root)) + f.close() + +def main(): + usage = "usage: %prog [options] " + parser = OptionParser(usage, version='%prog ' + __version__) + parser.add_option("-p", dest="publishing_target", type="choice", choices=["mode", "ditaot"], default="mode", + help="Publishing Target: mode|ditaot, [default: %default]") + parser.add_option("-l", "--loglevel", type="int", default=30, help="Log Level (debug=10, info=20, warning=30, [error=40], critical=50)") + (options, args) = parser.parse_args() + if len(args) < 1: + parser.print_help() + parser.error("Please supply the path to the XML content") + if options.loglevel: + logging.basicConfig(level=options.loglevel) + pub_target = Guidiser.PT_MODE if (options.publishing_target == "mode") else Guidiser.PT_DITAOT + updatefiles(args[0], pub_target) if __name__ == '__main__': - sys.exit(main(updatefiles, __version__)) + sys.exit(main()) + ###################################### # Test code ###################################### +class StubDoxyIdRedirect(object): + def __init__(self, theDir): + self.dict = {'struct_e_sock_1_1_t_addr_update':('struct_e_sock_1_1_t_addr_update.xml', 'ESock::TAddrUpdate'), + 'class_c_active_scheduler_1_1_t_cleanup_bundle':('class_c_active_scheduler_1_1_t_cleanup_bundle.xml', 'CActiveScheduler::TCleanupBundle'), + 'class_test':('class_test.xml', 'Test'), + 'class_test_1a99f2bbfac6c95612322b0f10e607ebe5':('cxxclass.xml', 'Test')} + + def lookupId(self, doxy_id): + try: + filename, fqn = self.dict[doxy_id] + return (filename, fqn) + except Exception, e: + raise ExceptionDoxyIdRedirectLookup("StubException: %s" % e) + + class TestGuidiser(unittest.TestCase): def setUp(self): - self.guidiser = Guidiser() + self.guidiser = Guidiser(publishing_target=Guidiser.PT_MODE, doxyidredirect=StubDoxyIdRedirect('adir')) + self.test_dir = "guidiser_test_dir" + + def _create_test_data(self): + f = open("struct_e_sock_1_1_t_addr_update.xml", "w") + f.write(struct_e_sock_1_1_t_addr_update) + f.close() + os.mkdir(self.test_dir) + f = open(os.path.join(self.test_dir, "struct_e_sock_1_1_t_addr_update.xml"), "w") + f.write(struct_e_sock_1_1_t_addr_update) + f.close() + + def _cleanup_test_data(self): + os.remove("struct_e_sock_1_1_t_addr_update.xml") + shutil.rmtree(self.test_dir) + + def test_i_can_get_and_set_a_PT(self): + self.assertEqual(self.guidiser.get_publishing_target(), Guidiser.PT_MODE) + self.guidiser.set_publishing_target(Guidiser.PT_DITAOT) + self.assertEqual(self.guidiser.get_publishing_target(), Guidiser.PT_DITAOT) + + def test_i_raise_an_exception_when_trying_to_set_an_invalid_PT(self): + self.assertRaises(Exception, self.guidiser.set_publishing_target, 2) def test_i_update_root_elements_id(self): root = self.guidiser.guidise(StringIO(cxxclass)) - self.assertTrue(root.attrib['id'] == "GUID-25825EC4-341F-3EA4-94AA-7DCE380E6D2E") + self.assertEqual(root.attrib['id'], "GUID-56866D87-2CE9-31EA-8FA7-F4275FDBCB93") def test_i_continue_if_passed_an_invalid_file(self): try: @@ -142,26 +301,68 @@ "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67FD" ) - def test_based_toplevel_class_jarnos_output(self): - self.assertTrue(self.guidiser._get_guid("RConnection") == - "GUID-01926597-E8A1-35E5-A221-B4530CF1783F" - ) - def test_target_id(self): self.assertTrue(self.guidiser._get_guid("ESock::TAddrUpdate") == "GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B" ) - def test_map_href(self): - self.assertTrue(self.guidiser._get_guid("ESock::struct_e_sock_1_1_t_addr_update.xml#ESock::TAddrUpdate") == + def test_topicref_href_to_topic_for_mode(self): + self.assertEquals(self.guidiser._guidise_href("struct_e_sock_1_1_t_addr_update.xml#struct_e_sock_1_1_t_addr_update", "topicref"), "GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B" ) - - def test_map_href_to_another_map(self): - self.assertTrue(self.guidiser._get_guid("ziplib.ditamap") == + + def test_topicref_href_to_topic_for_ditaot(self): + self.guidiser.set_publishing_target(Guidiser.PT_DITAOT) + self._create_test_data() + try: + self.assertEquals(self.guidiser._guidise_href("struct_e_sock_1_1_t_addr_update.xml#struct_e_sock_1_1_t_addr_update", "topicref"), + "GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B.xml") + finally: + self._cleanup_test_data() + + def test_topicref_href_to_map_for_mode(self): + self.assertEquals(self.guidiser._guidise_href("ziplib.ditamap", "topicref"), "GUID-7C7A889C-AE2B-31FC-A5DA-A87019E1251D" ) + def test_topicref_href_to_map_for_ditaot(self): + self.guidiser.set_publishing_target(Guidiser.PT_DITAOT) + self.assertEquals(self.guidiser._guidise_href("ziplib.ditamap", "topicref"), + "GUID-7C7A889C-AE2B-31FC-A5DA-A87019E1251D.ditamap" + ) + + def test_xref_href_to_topic_in_same_file_for_mode(self): + self.assertEquals(self.guidiser._guidise_href("struct_e_sock_1_1_t_addr_update.xml#struct_e_sock_1_1_t_addr_update", "xref"), + "GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B" + ) + + def test_xref_href_to_topic_in_same_file_for_ditaot(self): + self.guidiser.set_publishing_target(Guidiser.PT_DITAOT) + self.assertEquals(self.guidiser._guidise_href("struct_e_sock_1_1_t_addr_update.xml#struct_e_sock_1_1_t_addr_update", "xref"), + "GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B.xml#GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B" + ) + + def test_xref_href_to_some_other_file_on_file_system(self): + self.guidiser.set_publishing_target(Guidiser.PT_DITAOT) + self.assertEquals(self.guidiser._guidise_href("../../documentation/RFCs/rfc3580.txt", "xref"), + "../../documentation/RFCs/rfc3580.txt" + ) + + def test_i_guidise_the_id_of_a_fully_qualified_apiname(self): + self.assertEquals(self.guidiser._guidise_id("struct_e_sock_1_1_t_addr_update"), + "GUID-E72084E6-C1CE-3388-93F7-5B7A3F506C3B" + ) + + def test_id_guidise_the_id_something_that_is_not_a_fully_qualified_apiname(self): + self.assertEquals(self.guidiser._guidise_id("commsdataobjects"), + "GUID-2F2463E0-6C84-3FAB-8B60-57E57315FDEB" + ) + + def test_i_preserve_namespaces(self): + xml_in = """""" + xml_expected = """""" + root = self.guidiser.guidise(StringIO(xml_in)) + self.assertEqual(etree.tostring(root), xml_expected) class Testupdate_files(unittest.TestCase): @@ -176,835 +377,234 @@ return open(os.path.join(self.test_dir, "reference.dita"), mode) os.mkdir(self.test_dir) f = reference_file_handle("w") - f.write(reference) + f.write(filesys_cxxclass) f.close() updatefiles(self.test_dir) - self.assertEquals(reference_file_handle("r").read(), guidised_reference) - - + self.assertEquals(reference_file_handle("r").read(), filesys_cxxclass_guidised) -reference = """ - - - CActiveScheduler::TCleanupBundle - - -
- - -
-
- - - iCleanupPtr - - -
- - -
-
- - - - iDummyInt - - -
-
-
- - - -""" - -guidised_reference = """ - - - CActiveScheduler::TCleanupBundle - - -
- - -
-
- - - iCleanupPtr - - -
- - -
-
- - - - iDummyInt - - -
-
-
- - -""" +struct_e_sock_1_1_t_addr_update = """ + + + CoordStruct + + + + + + + + + + + + + +

A coordinate pair.

+
+
+ + x + + + + + float + CoordStruct + float x + CoordStruct::x + + + + + + +

The x coordinate

+
+
+
+ + y + + + + + float + CoordStruct + float y + CoordStruct::y + + + + + + +

The y coordinate

+
+
+
+
""" -cxxclass = """ +filesys_cxxclass = """ - - CP_class + + CActiveScheduler::TCleanupBundle - CB_class - CB_class - CB_class - + - - - - - + + + + + - -

CP_class_text. A link to a method, CP_class::CPD_function(CPD_type_1).

-
+
- - CPD_function - - - - - CPD_return - CP_class - CPD_return CPD_function(CPD_type_1 CPD_param_1) - CP_class::CPD_function(CPD_type_1) - - - CPD_type_1 - CPD_param_1 - CPD_param_1_text - - - - - - - - - - - -

CPD_function_text. CPD_return CPD_return_text

-
-
-
- - CPC_function - - - - - CPC_return - CP_class - CPC_return CPC_function(CPC_type_1 CPC_param_1) - CP_class::CPC_function(CPC_type_1) - - - CPC_type_1 - CPC_param_1 - CPC_param_1_text - - - - - - - - - - - -

CPC_function_text. CPC_return CPC_return_text

-
-
-
- - CPF_function - - - - - CPF_return - CP_class - CPF_return CPF_function(CPF_type_1 CPF_param_1) - CP_class::CPF_function(CPF_type_1) - - - CPF_type_1 - CPF_param_1 - CPF_param_1_text - - - - - - - - - - - -

CPF_function_text. CPF_return CPF_return_text

-
-
-
- - CPE_function - - - - - CPE_return - CP_class - CPE_return CPE_function(CPE_type_1 CPE_param_1) - CP_class::CPE_function(CPE_type_1) - - - CPE_type_1 - CPE_param_1 - CPE_param_1_text - - - - - - - - - - - -

CPE_function_text. CPE_return CPE_return_text

-
-
-
- - CPB_function - - - - - CPB_return - CP_class - CPB_return CPB_function(CPB_type_1 CPB_param_1) - CP_class::CPB_function(CPB_type_1) - - - CPB_type_1 - CPB_param_1 - CPB_param_1_text - - - - - - - - - - - -

CPB_function_text. CPB_return CPB_return_text

-
-
-
- - CPA_function + + iCleanupPtr - - - - CPA_return - CP_class - CPA_return CPA_function(CPA_type_1 CPA_param_1) - CP_class::CPA_function(CPA_type_1) - - - CPA_type_1 - CPA_param_1 - CPA_param_1_text - - - - - - - - - - - -

CPA_function_text. CPA_return CPA_return_text

-
-
-
- - CPH_function - - - - - - CPH_return - CP_class - static CPH_return CPH_function(CPH_type_1 CPH_param_1) - CP_class::CPH_function(CPH_type_1) - - - CPH_type_1 - CPH_param_1 - CPH_param_1_text - - - - - - - - - - - -

CPH_function_text. CPH_return CPH_return_text

-
-
-
- - CPG_function - - - - - - CPG_return - CP_class - static CPG_return CPG_function(CPG_type_1 CPG_param_1) - CP_class::CPG_function(CPG_type_1) - - - CPG_type_1 - CPG_param_1 - CPG_param_1_text - - - - - - - - - - - -

CPG_function_text. CPG_return CPG_return_text

-
-
-
- - CPJ_function - - - - - - CPJ_return - CP_class - static CPJ_return CPJ_function(CPJ_type_1 CPJ_param_1) - CP_class::CPJ_function(CPJ_type_1) - - - CPJ_type_1 - CPJ_param_1 - CPJ_param_1_text - - - - - - - - - - - -

CPJ_function_text. CPJ_return CPJ_return_text

-
-
-
- - CPI_function - - - - - - CPI_return - CP_class - static CPI_return CPI_function(CPI_type_1 CPI_param_1) - CP_class::CPI_function(CPI_type_1) - - - CPI_type_1 - CPI_param_1 - CPI_param_1_text - - - - - - - - - - - -

CPI_function_text. CPI_return CPI_return_text

-
-
-
- - CPL_function - - - - - - CPL_return - CP_class - static CPL_return CPL_function(CPL_type_1 CPL_param_1) - CP_class::CPL_function(CPL_type_1) - - - CPL_type_1 - CPL_param_1 - CPL_param_1_text - - - - - - - - - - - -

CPL_function_text. CPL_return CPL_return_text

-
-
-
- - CPK_function - - - - - - CPK_return - CP_class - static CPK_return CPK_function(CPK_type_1 CPK_param_1) - CP_class::CPK_function(CPK_type_1) - - - CPK_type_1 - CPK_param_1 - CPK_param_1_text - - - - - - - - - - - -

CPK_function_text. CPK_return CPK_return_text

-
-
-
- - CPN_name + + + + + CCleanup * + CActiveScheduler::TCleanupBundle + CCleanup * iCleanupPtr + CActiveScheduler::TCleanupBundle::iCleanupPtr + + + + + + + + + + iDummyInt - CPN_type - CP_class - CPN_type CPN_name - CP_class::CPN_name + + TInt + + CActiveScheduler::TCleanupBundle + TInt iDummyInt + CActiveScheduler::TCleanupBundle::iDummyInt - - + + - -

CPN_text

-
-
-
- - CPM_name - - - - - CPM_type - CP_class - CPM_type CPM_name - CP_class::CPM_name - - - - - - -

CPM_text

-
+
- - CPP_name - - - - - CPP_type - CP_class - CPP_type CPP_name - CP_class::CPP_name - - - - - - -

CPP_text

-
-
-
- - CPO_name - - - - - CPO_type - CP_class - CPO_type CPO_name - CP_class::CPO_name - - - - - - -

CPO_text

-
-
-
- - CPR_name - +
""" + +filesys_cxxclass_guidised = """ + + + CActiveScheduler::TCleanupBundle + + + + + + + + + + + + + + + + iCleanupPtr + - - CPR_type - CP_class - CPR_type CPR_name - CP_class::CPR_name + + + CCleanup * + CActiveScheduler::TCleanupBundle + CCleanup * iCleanupPtr + CActiveScheduler::TCleanupBundle::iCleanupPtr - - - - - -

CPR_text

-
-
-
- - CPQ_name - - - - - CPQ_type - CP_class - CPQ_type CPQ_name - CP_class::CPQ_name - - - + + - -

CPQ_text

-
-
-
- - CPX_name - - - - - - CPX_type - CP_class - static CPX_type CPX_name - CP_class::CPX_name - - - - - - -

CPX_text

-
-
-
- - CPW_name - - - - - - CPW_type - CP_class - static CPW_type CPW_name - CP_class::CPW_name - - - - - - -

CPW_text

-
-
-
- - CPV_name - - - - - - CPV_type - CP_class - static CPV_type CPV_name - CP_class::CPV_name - - - - - - -

CPV_text

-
+
- - CPU_name - - - - - - CPU_type - CP_class - static CPU_type CPU_name - CP_class::CPU_name - - - - - - -

CPU_text

-
-
-
- - CPT_name - + + iDummyInt + - - - CPT_type - CP_class - static CPT_type CPT_name - CP_class::CPT_name + + + TInt + + CActiveScheduler::TCleanupBundle + TInt iDummyInt + CActiveScheduler::TCleanupBundle::iDummyInt - - + + - -

CPT_text

-
-
-
- - CPS_name - - - - - - CPS_type - CP_class - static CPS_type CPS_name - CP_class::CPS_name - - - - - - -

CPS_text

-
+
- - CPb_type_alias - - - - - CPb_typedef - CP_class - CPb_typedef CPb_type_alias - CP_class::CPb_type_alias - - - - - - -

CPb_text

-
-
-
- - CPa_type_alias - - - - - CPa_typedef - CP_class - CPa_typedef CPa_type_alias - CP_class::CPa_type_alias - - - - - - -

CPa_text

-
-
-
- - CPd_type_alias - - - - - CPd_typedef - CP_class - CPd_typedef CPd_type_alias - CP_class::CPd_type_alias - - - - - - -

CPd_text

-
-
-
- - CPc_type_alias - - - - - CPc_typedef - CP_class - CPc_typedef CPc_type_alias - CP_class::CPc_type_alias - - - - - - -

CPc_text

-
-
-
- - CPf_type_alias - - - - - CPf_typedef - CP_class - CPf_typedef CPf_type_alias - CP_class::CPf_type_alias - - - - - - -

CPf_text

-
-
-
- - CPe_type_alias - - - - - CPe_typedef - CP_class - CPe_typedef CPe_type_alias - CP_class::CPe_type_alias - - - - - - -

CPe_text

-
-
-
+
""" + +cxxclass = """ + + + Test + + + + + + + + + + + + + +

Points to function Test()

+
+
+ + Test + + + + + + + Test + Test() + Test::Test() + + + + + + + + + + +

details.

+
+
+
""" \ No newline at end of file