From cf40c1951f4f0f26090226fb4969ca147341a031 Mon Sep 17 00:00:00 2001 From: mike Date: Fri, 02 Oct 2009 22:24:56 +0000 Subject: LP 439980 : Refactored the XMLSerializer to support complex components; Corrected specs for the addons properties and constructor parameters names; Moved all existing actions and events to components (except a few left in code for testing purposes) --- (limited to 'tutorius/bundler.py') diff --git a/tutorius/bundler.py b/tutorius/bundler.py index 8808d93..734c679 100644 --- a/tutorius/bundler.py +++ b/tutorius/bundler.py @@ -48,6 +48,46 @@ INI_XML_FSM_PROPERTY = "FSM_FILENAME" INI_FILENAME = "meta.ini" TUTORIAL_FILENAME = "tutorial.xml" NODE_COMPONENT = "Component" +NODE_SUBCOMPONENT = "SubComponent" +NODE_SUBCOMPONENTLIST = "SubComponentList" + +class Vault(object): + """ + The Vault is the primary interface for the storage and installation of tutorials + on the machine. It needs to accomplish the following tasks : + - query() : Lists the + - installTutorial() : + - deleteTutorial() : + - readTutorial() : + - saveTutorial() : + """ + def query(keyword="", category="", start_index=0, num_results=10): + """ + Returns a list of tutorial meta-data corresponding to the keywords + and category mentionned. + + @param keyword The keyword to look for in the tutorial title and description. + @param category The category in which to look for tutorials + @param start_index The first result to be shown (e.g. ) + @param num_results The number of results to show + @return The list of tutorial metadata that corresponds to the query parameters. + """ + raise NotImplementedError("The query function on the Vault is not implemented") + + def installTutorial(path ,force_install=False): + """ + Inserts the tutorial inside the Vault. Once installed, it will show up + """ + raise NotImplementedError("Installation in the Vault not supported yet") + + def deleteTutorial(tutorial_id): + raise NotImplementedError("") + + def readTutorial(tutorial_id): + raise NotImplementedError("") + + def saveTutorial(tutorial, metadata, resource_list): + raise NotImplementedError("") class TutorialStore(object): @@ -150,6 +190,71 @@ class XMLSerializer(Serializer): eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc)) return statesList + def _create_addon_component_node(self, parent_attr_name, comp, doc): + """ + Takes a component that is embedded in another component (e.g. the content + of a OnceWrapper) and encapsulate it in a node with the property name. + + e.g. + + + + + + + When reloading this node, we should look up the property name for the parent + in the attribute of the node, then examine the subnode to create the addon + object itself. + + @param parent_attr_name The name of the parent's attribute for this addon + e.g. the OnceWrapper has the action attribute, which corresponds to a + sub-action it must execute once. + @param comp The component node itself + @param doc The XML document root (only used to create the nodes) + @returns A NODE_SUBCOMPONENT node, with the property attribute and a sub node + that represents another component. + """ + subCompNode = doc.createElement(NODE_SUBCOMPONENT) + subCompNode.setAttribute("property", parent_attr_name) + + subNode = self._create_component_node(comp, doc) + + subCompNode.appendChild(subNode) + + return subCompNode + + def _create_addonlist_component_node(self, parent_attr_name, comp_list, doc): + """ + Takes a list of components that are embedded in another component (ex. the + content of a ChainAction) and encapsulate them in a node with the property + name. + + e.g. + + + + + + + + When reloading this node, we should look up the property name for the parent + in the the attribute of the node, then rebuild the list by appending the + content of all the subnodes. + + @param parent_attr_name The name of the parent component's property + @param comp_list A list of components that comprise the property + @param doc The XML document root (only for creating new nodes) + @returns A NODE_SUBCOMPONENTLIST node with the property attribute + """ + subCompListNode = doc.createElement(NODE_SUBCOMPONENTLIST) + subCompListNode.setAttribute("property", parent_attr_name) + + for comp in comp_list: + compNode = self._create_component_node(comp, doc) + subCompListNode.appendChild(compNode) + + return subCompListNode + def _create_component_node(self, comp, doc): """ Takes a single component (action or eventfilter) and transforms it @@ -169,10 +274,10 @@ class XMLSerializer(Serializer): for propname in comp.get_properties(): propval = getattr(comp, propname) if getattr(type(comp), propname).type == "addonlist": - for subval in propval: - compNode.appendChild(self._create_component_node(subval, doc)) - elif getattr(type(comp), propname).type == "addonlist": - compNode.appendChild(self._create_component_node(subval, doc)) + compNode.appendChild(self._create_addonlist_component_node(propname, propval, doc)) + elif getattr(type(comp), propname).type == "addon": + #import rpdb2; rpdb2.start_embedded_debugger('pass') + compNode.appendChild(self._create_addon_component_node(propname, propval, doc)) else: # repr instead of str, as we want to be able to eval() it into a # valid object. @@ -282,6 +387,27 @@ class XMLSerializer(Serializer): # Error : none of these directories contain the tutorial raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid) + def _get_direct_descendants_by_tag_name(self, node, name): + """ + Searches in the list of direct descendants of a node to find all the node + that have the given name. + + This is used because the Document.getElementsByTagName() function returns the + list of all the descendants (whatever their distance to the start node) that + have that name. In the case of complex components, we absolutely need to inspect + a single layer of the tree at the time. + + @param node The node from which we want the direct descendants with a particular + name + @param name The name of the node + @returns A list, possibly empty, of direct descendants of node that have this name + """ + return_list = [] + for childNode in node.childNodes: + if childNode.nodeName == name: + return_list.append(childNode) + return return_list + def _load_xml_properties(self, properties_elem): """ Changes a list of properties into fully instanciated properties. @@ -298,7 +424,7 @@ class XMLSerializer(Serializer): @param filters_elem An XML Element representing a list of event filters """ reformed_event_filters_list = [] - event_filter_element_list = filters_elem.getElementsByTagName(NODE_COMPONENT) + event_filter_element_list = self._get_direct_descendants_by_tag_name(filters_elem, NODE_COMPONENT) new_event_filter = None for event_filter in event_filter_element_list: @@ -309,6 +435,42 @@ class XMLSerializer(Serializer): return reformed_event_filters_list + def _load_xml_subcomponents(self, node, properties): + """ + Loads all the subcomponent node below the given node and inserts them with + the right property name inside the properties dictionnary. + + @param node The parent node that contains one or many SubComponent nodes. + @param properties A dictionnary where the subcomponent property names + and the instantiated components will be stored + @returns Nothing. The properties dict will contain the property->comp mapping. + """ + subCompList = self._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENT) + + for subComp in subCompList: + property_name = subComp.getAttribute("property") + internal_comp_node = self._get_direct_descendants_by_tag_name(subComp, NODE_COMPONENT)[0] + internal_comp = self._load_xml_component(internal_comp_node) + properties[str(property_name)] = internal_comp + + def _load_xml_subcomponent_lists(self, node, properties): + """ + Loads all the subcomponent lists below the given node and stores them + under the correct property name for that node. + + @param node The node from which we want to read the subComponent lists + @param properties The dictionnary that will contain the mapping of prop->subCompList + @returns Nothing. The values are returns inside the properties dict. + """ + listOf_subCompListNode = self._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENTLIST) + for subCompListNode in listOf_subCompListNode: + property_name = subCompListNode.getAttribute("property") + subCompList = [] + for subCompNode in self._get_direct_descendants_by_tag_name(subCompListNode, NODE_COMPONENT): + subComp = self._load_xml_component(subCompNode) + subCompList.append(subComp) + properties[str(property_name)] = subCompList + def _load_xml_component(self, node): """ Loads a single addon component instance from an Xml node. @@ -318,20 +480,23 @@ class XMLSerializer(Serializer): @return The addon component object of the correct type according to the XML description """ - new_action = addon.create(node.getAttribute("Class")) - if not new_action: - return None + class_name = node.getAttribute("Class") + + properties = {} + + for prop in node.attributes.keys(): + if prop == "Class" : continue + # security : keep sandboxed + properties[str(prop)] = eval(node.getAttribute(prop)) + + # Read the complex attributes + self._load_xml_subcomponents(node, properties) + self._load_xml_subcomponent_lists(node, properties) - for attrib in node.attributes.keys(): - if attrib == "Class": continue - # security note: keep sandboxed - setattr(new_action, attrib, eval(node.getAttribute(attrib), {}, {})) + new_action = addon.create(class_name, **properties) - # recreate complex attributes - for sub in node.childNodes: - name = getattr(new_action, sub.nodeName) - if name == "addon": - setattr(new_action, sub.getAttribute("Name"), self._load_xml_action(sub)) + if not new_action: + return None return new_action @@ -342,7 +507,7 @@ class XMLSerializer(Serializer): @param actions_elem An XML Element representing a list of Actions """ reformed_actions_list = [] - actions_element_list = actions_elem.getElementsByTagName(NODE_COMPONENT) + actions_element_list = self._get_direct_descendants_by_tag_name(actions_elem, NODE_COMPONENT) for action in actions_element_list: new_action = self._load_xml_component(action) -- cgit v0.9.1