Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius
diff options
context:
space:
mode:
Diffstat (limited to 'tutorius')
-rw-r--r--tutorius/tutorial.py55
-rw-r--r--tutorius/vault.py400
2 files changed, 270 insertions, 185 deletions
diff --git a/tutorius/tutorial.py b/tutorius/tutorial.py
index 9831a7b..b45363f 100644
--- a/tutorius/tutorial.py
+++ b/tutorius/tutorial.py
@@ -67,7 +67,8 @@ class Tutorial(object):
self.add_transition(Tutorial.INIT, \
(AutomaticTransitionEvent(), Tutorial.END))
else:
- raise NotImplementedError("Tutorial: Initilization from a dictionary is not supported yet")
+ self._state_dict = state_dict
+
# Minimally check for the presence of an INIT and an END
@@ -528,15 +529,20 @@ class Tutorial(object):
def _generate_unique_state_name(self):
name = "State" + str(self._state_name_nb)
- self._state_name_nb += 1
+ while name in self._state_dict:
+ self._state_name_nb += 1
+ name = "State" + str(self._state_name_nb)
return name
+ # Python Magic Methods
def __str__(self):
"""
Return a string representation of the tutorial
"""
return str(self._state_dict)
+ def __eq__(self, other):
+ return isinstance(other, type(self)) and self.get_state_dict() == other.get_state_dict()
class State(object):
"""
@@ -548,16 +554,20 @@ class State(object):
inputs, the validation should be done by the containing class.
"""
- def __init__(self, name, action_list=(), transition_list=()):
+ def __init__(self, name, actions={}, transitions={}):
"""
Initializes the content of the state, such as loading the actions
that are required and building the correct transitions.
- @param action_list The list of actions to execute when entering this
+ @param actions list or dict of actions to perform when entering the
state
- @param transition_list A list of tuples of the form
+ @param transitions list or dict of tuples of the form
(event, next_state_name), that explains the outgoing links for
this state
+
+ For actions and transitions, dictionaries allow specifying the name.
+ If lists are given, their contents will be added with add_action or
+ add_transition
"""
object.__init__(self)
@@ -567,13 +577,19 @@ class State(object):
self.action_name_nb = 0
self.transition_name_nb = 0
- self._actions = {}
- for action in action_list:
- self.add_action(action)
-
- self._transitions = {}
- for transition in transition_list:
- self.add_transition(transition)
+ if type(actions) is dict:
+ self._actions = dict(actions)
+ else:
+ self._actions = {}
+ for action in actions:
+ self.add_action(action)
+
+ if type(transitions) is dict:
+ self._transitions = dict(transitions)
+ else:
+ self._transitions = {}
+ for transition in transitions:
+ self.add_transition(transition)
# Action manipulations
@@ -741,7 +757,9 @@ class State(object):
# to make it easier to debug and know what we are
# manipulating
name = self.name + _NAME_SEPARATOR + "action" + str(self.action_name_nb)
- self.action_name_nb += 1
+ while name in self._actions:
+ self.action_name_nb += 1
+ name = self.name + _NAME_SEPARATOR + "action" + str(self.action_name_nb)
return name
def _generate_unique_transition_name(self, transition):
@@ -757,7 +775,9 @@ class State(object):
# generate a name to make it easier to debug and know
# what we are manipulating
name = self.name + _NAME_SEPARATOR + "transition" + str(self.transition_name_nb)
- self.transition_name_nb += 1
+ while name in self._transitions:
+ self.transition_name_nb += 1
+ name = self.name + _NAME_SEPARATOR + "transition" + str(self.transition_name_nb)
return name
def __eq__(self, otherState):
@@ -775,12 +795,15 @@ class State(object):
@param otherState The state that will be compared to this one
@return True if the states are the same, False otherwise
` """
- raise NotImplementedError
+ return isinstance(otherState, type(self)) and \
+ self.get_action_dict() == otherState.get_action_dict() and \
+ self.get_transition_dict() == otherState.get_transition_dict()
#TODO: Define the automatic transition in the same way as
# other events
class AutomaticTransitionEvent(TPropContainer):
- pass
+ def __repr__(self):
+ return str(self.__class__.__name__)
################## Error Handling and Exceptions ##############################
diff --git a/tutorius/vault.py b/tutorius/vault.py
index 9cda2e9..728cf64 100644
--- a/tutorius/vault.py
+++ b/tutorius/vault.py
@@ -28,10 +28,10 @@ import uuid
import xml.dom.minidom
from xml.dom import NotFoundErr
import zipfile
+from ConfigParser import SafeConfigParser
from . import addon
-from .core import Tutorial, State, FiniteStateMachine
-from ConfigParser import SafeConfigParser
+from .tutorial import Tutorial, State, AutomaticTransitionEvent
logger = logging.getLogger("tutorius")
@@ -58,11 +58,23 @@ INI_XML_FSM_PROPERTY = "fsm_filename"
INI_VERSION_PROPERTY = 'version'
INI_FILENAME = "meta.ini"
TUTORIAL_FILENAME = "tutorial.xml"
+RESOURCES_FOLDER = 'resources'
+
+######################################################################
+# XML Tag names and attributes
+######################################################################
+ELEM_FSM = "FSM"
+ELEM_STATES = "States"
+ELEM_STATE = "State"
+ELEM_ACTIONS = "Actions"
+ELEM_TRANS = "Transitions"
+ELEM_AUTOTRANS = "AutomaticTransition"
NODE_COMPONENT = "Component"
NODE_SUBCOMPONENT = "property"
NODE_SUBCOMPONENTLIST = "listproperty"
-NEXT_STATE_ATTR = "next_state"
-RESSOURCES_FOLDER = 'ressources'
+NAME_ATTR = "__name__"
+NEXT_STATE_ATTR = "__next_state__"
+START_STATE_ATTR = "__start_state__"
class Vault(object):
@@ -74,7 +86,7 @@ class Vault(object):
given activity.
@param activity_name the name of the activity associated with this tutorial. None means ALL activities
- @param activity_vers the version number of the activity to find tutorail for. 0 means find for ANY version. Ifactivity_ame is None, version number is not used
+ @param activity_vers the version number of the activity to find tutorial for. 0 means find for ANY version. If activity_name is None, version number is not used
@returns a map of tutorial {names : GUID}.
"""
# check both under the activity data and user installed folders
@@ -238,12 +250,14 @@ class Vault(object):
# Return tutorial list
return tutorial_list
+
@staticmethod
def loadTutorial(Guid):
"""
Creates an executable version of a tutorial from its saved representation.
- @returns an executable representation of a tutorial
+ @param Guid Unique identifier of the tutorial
+ @returns Tutorial object
"""
bundle = TutorialBundler(Guid)
@@ -254,15 +268,20 @@ class Vault(object):
serializer = XMLSerializer()
name = config.get(INI_METADATA_SECTION, INI_NAME_PROPERTY)
- fsm = serializer.load_fsm(Guid, bundle_path)
- tuto = Tutorial(name, fsm)
- return tuto
+ # Open the XML file
+ tutorial_file = os.path.join(bundle_path, TUTORIAL_FILENAME)
+ with open(tutorial_file, 'r') as tfile:
+ tutorial = serializer.load_tutorial(tfile)
+
+ return tutorial
@staticmethod
def saveTutorial(tutorial, metadata_dict):
"""
Creates a persistent version of a tutorial in the Vault.
+ @param tutorial Tutorial
+ @param metadata_dict dictionary of metadata for the Tutorial
@returns true if the tutorial was saved correctly
"""
@@ -276,7 +295,9 @@ class Vault(object):
# Serialize the tutorial and write it to disk
xml_ser = XMLSerializer()
os.makedirs(tutorial_path)
- xml_ser.save_fsm(tutorial.state_machine, TUTORIAL_FILENAME, tutorial_path)
+
+ with open(os.path.join(tutorial_path, TUTORIAL_FILENAME), 'w') as fsmfile:
+ xml_ser.save_tutorial(tutorial, fsmfile)
# Create the metadata file
ini_file_path = os.path.join(tutorial_path, "meta.ini")
@@ -321,32 +342,34 @@ class Vault(object):
else:
return False
-
+
@staticmethod
- def add_ressource(tutorial_guid, file_path):
+ def add_resource(tutorial_guid, file_path):
"""
- Returns a unique name for this resource composed from the original name of the file
- and a suffix to make it unique ( ex: name_1.jpg ) and add it to the resources for the tutorial.
+ Add given resource file in the vault and returns a unique name for this resource
+ composed from the original name of the file and a suffix to make it unique
+ ( ex: name_1.jpg ).
@param tutorial_guid The guid of the tutorial
- @param file_path the file path of the ressource to add
- @returns the ressource_id of the ressource
+ @param file_path the file path of the resource to add
+ @returns the resource_id of the resource
"""
# Get the tutorial path
bundler = TutorialBundler(tutorial_guid)
tutorial_path = bundler.get_tutorial_path(tutorial_guid)
# Get the file name
- fname_splitted = file_path.rsplit('/')
- file_name = fname_splitted[fname_splitted.__len__() - 1]
+ file_name = os.path.basename(file_path)
+ #fname_splitted = file_path.rsplit('/')
+ #file_name = fname_splitted[fname_splitted.__len__() - 1]
base_name, extension = os.path.splitext(file_name)
# Append unique name to file name
file_name_appended = base_name + '_' + str(uuid.uuid1()) + extension
- # Check if the ressource file already exists
- new_file_path = os.path.join(tutorial_path, RESSOURCES_FOLDER, file_name_appended)
+ # Check if the resource file already exists
+ new_file_path = os.path.join(tutorial_path, RESOURCES_FOLDER, file_name_appended)
if os.path.isfile(new_file_path) == False:
- # Copy the ressource file in the vault
- if os.path.isdir(os.path.join(tutorial_path, RESSOURCES_FOLDER)) == False:
- os.makedirs(os.path.join(tutorial_path, RESSOURCES_FOLDER))
+ # Copy the resource file in the vault
+ if os.path.isdir(os.path.join(tutorial_path, RESOURCES_FOLDER)) == False:
+ os.makedirs(os.path.join(tutorial_path, RESOURCES_FOLDER))
assert os.path.isfile(file_path)
shutil.copyfile(file_path, new_file_path)
@@ -354,36 +377,36 @@ class Vault(object):
@staticmethod
- def delete_ressource(tutorial_guid, ressource_id):
+ def delete_resource(tutorial_guid, resource_id):
"""
Delete the resource from the resources of the tutorial.
@param tutorial_guid the guid of the tutorial
- @param ressource_id the ressource id of the ressource to delete
+ @param resource_id the resource id of the resource to delete
"""
# Get the tutorial path
bundler = TutorialBundler(tutorial_guid)
tutorial_path = bundler.get_tutorial_path(tutorial_guid)
- # Check if the ressource file exists
- file_path = os.path.join(tutorial_path, RESSOURCES_FOLDER, ressource_id)
+ # Check if the resource file exists
+ file_path = os.path.join(tutorial_path, RESOURCES_FOLDER, resource_id)
if os.path.isfile(file_path):
- # Delete the ressource
+ # Delete the resource
os.remove(file_path)
else:
print('File not found, no delete took place')
@staticmethod
- def get_ressource_path(tutorial_guid, ressource_id):
+ def get_resource_path(tutorial_guid, resource_id):
"""
Returns the absolute file path to the resourceID
@param tutorial_guid the guid of the tutorial
- @param ressource_id the ressource id of the ressource to find the path for
- @returns the absolute path of the ressource file
+ @param resource_id the resource id of the resource to find the path for
+ @returns the absolute path of the resource file
"""
# Get the tutorial path
bundler = TutorialBundler(tutorial_guid)
tutorial_path = bundler.get_tutorial_path(tutorial_guid)
- # Check if the ressource file exists
- file_path = os.path.join(tutorial_path, RESSOURCES_FOLDER, ressource_id)
+ # Check if the resource file exists
+ file_path = os.path.join(tutorial_path, RESOURCES_FOLDER, resource_id)
if os.path.isfile(file_path):
return file_path
else:
@@ -396,7 +419,7 @@ class Serializer(object):
used in the tutorials to/from disk. Must be inherited.
"""
- def save_fsm(self,fsm):
+ def save_tutorial(self,fsm):
"""
Save fsm to disk. If a GUID parameter is provided, the existing GUID is
located in the .ini files in the store root and bundle root and
@@ -406,7 +429,7 @@ class Serializer(object):
"""
raise NotImplementedError()
- def load_fsm(self):
+ def load_tutorial(self):
"""
Load fsm from disk.
"""
@@ -417,21 +440,26 @@ class XMLSerializer(Serializer):
Class that provide serializing and deserializing of the FSM
used in the tutorials to/from a .xml file. Inherit from Serializer
"""
-
- def _create_state_dict_node(self, state_dict, doc):
+
+ @classmethod
+ def _create_state_dict_node(cls, state_dict, doc):
"""
Create and return a xml Node from a State dictionnary.
+ @param state_dict dictionary of State objects
+ @param doc The XML document root (used to create nodes only
+ @return xml Element containing the states
"""
- statesList = doc.createElement("States")
+ statesList = doc.createElement(ELEM_STATES)
for state_name, state in state_dict.items():
- stateNode = doc.createElement("State")
+ stateNode = doc.createElement(ELEM_STATE)
statesList.appendChild(stateNode)
stateNode.setAttribute("Name", state_name)
- actionsList = stateNode.appendChild(self._create_action_list_node(state.get_action_list(), doc))
- eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc))
+ actionsList = stateNode.appendChild(cls._create_action_list_node(state.get_action_dict(), doc))
+ transitionsList = stateNode.appendChild(cls._create_transitions_node(state.get_transition_dict(), doc))
return statesList
-
- def _create_addon_component_node(self, parent_attr_name, comp, doc):
+
+ @classmethod
+ def _create_addon_component_node(cls, 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.
@@ -458,13 +486,14 @@ class XMLSerializer(Serializer):
subCompNode = doc.createElement(NODE_SUBCOMPONENT)
subCompNode.setAttribute("name", parent_attr_name)
- subNode = self._create_component_node(comp, doc)
+ subNode = cls._create_component_node(comp, doc)
subCompNode.appendChild(subNode)
return subCompNode
- def _create_addonlist_component_node(self, parent_attr_name, comp_list, doc):
+ @classmethod
+ def _create_addonlist_component_node(cls, 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
@@ -491,12 +520,13 @@ class XMLSerializer(Serializer):
subCompListNode.setAttribute("name", parent_attr_name)
for comp in comp_list:
- compNode = self._create_component_node(comp, doc)
+ compNode = cls._create_component_node(comp, doc)
subCompListNode.appendChild(compNode)
return subCompListNode
- def _create_component_node(self, comp, doc):
+ @classmethod
+ def _create_component_node(cls, comp, doc):
"""
Takes a single component (action or eventfilter) and transforms it
into a xml node.
@@ -515,68 +545,86 @@ class XMLSerializer(Serializer):
for propname in comp.get_properties():
propval = getattr(comp, propname)
if getattr(type(comp), propname).type == "addonlist":
- compNode.appendChild(self._create_addonlist_component_node(propname, propval, doc))
+ compNode.appendChild(cls._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))
+ compNode.appendChild(cls._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.
compNode.setAttribute(propname, repr(propval))
return compNode
-
- def _create_action_list_node(self, action_list, doc):
+
+ @classmethod
+ def _create_action_list_node(cls, action_dict, doc):
"""
Create and return a xml Node from a Action list.
- @param action_list A list of actions
+ @param action_dict Dictionary of actions with names as keys
@param doc The XML document root (used to create new nodes only)
@return A XML Node object with the Actions tag name and a serie of
Action children
"""
- actionsList = doc.createElement("Actions")
- for action in action_list:
+ actionsList = doc.createElement(ELEM_ACTIONS)
+ for name, action in action_dict.items():
# Create the action node
- actionNode = self._create_component_node(action, doc)
+ actionNode = cls._create_component_node(action, doc)
+ actionNode.setAttribute(NAME_ATTR, name)
# Append it to the list
actionsList.appendChild(actionNode)
return actionsList
-
- def _create_event_filters_node(self, event_filters, doc):
- """
- Create and return a xml Node from an event filters.
+
+ @classmethod
+ def _create_transitions_node(cls, transition_dict, doc):
"""
- eventFiltersList = doc.createElement("EventFiltersList")
- for event, state in event_filters:
- eventFilterNode = self._create_component_node(event, doc)
- eventFilterNode.setAttribute(NEXT_STATE_ATTR, str(state))
+ Create and return a xml Node from a transition dictionary.
+ @param transition_dict dictionary of (event, next_state) transitions.
+ @param doc The XML document root (used to create nodes only
+ @return xml Element containing the transitions
+ """
+ eventFiltersList = doc.createElement(ELEM_TRANS)
+ for transition_name, (event, end_state) in transition_dict.items():
+ #start_state = transition_name.split(Tutorial._NAME_SEPARATOR)[0]
+ #XXX The addon is not in the cache and cannot be loaded so we
+ # store it differently for now
+ if type(event) == AutomaticTransitionEvent:
+ eventFilterNode = doc.createElement(ELEM_AUTOTRANS)
+ else:
+ eventFilterNode = cls._create_component_node(event, doc)
+ #eventFilterNode.setAttribute(START_STATE_ATTR, unicode(start_state))
+ eventFilterNode.setAttribute(NEXT_STATE_ATTR, unicode(end_state))
+ eventFilterNode.setAttribute(NAME_ATTR, transition_name)
eventFiltersList.appendChild(eventFilterNode)
return eventFiltersList
- def save_fsm(self, fsm, xml_filename, path):
+ @classmethod
+ def save_tutorial(cls, fsm, file_obj):
"""
- Save fsm to disk, in the xml file specified by "xml_filename", in the
- "path" folder. If the specified file doesn't exist, it will be created.
+ Save fsm to file
+
+ @param fsm Tutorial to save
+ @param file_obj file-like object in which the serialized fsm is saved
+
+ Side effects:
+ A serialized version of the Tutorial is written to file_obj.
+ The file is not closed automatically.
"""
- self.doc = doc = xml.dom.minidom.Document()
- fsm_element = doc.createElement("FSM")
+ doc = xml.dom.minidom.Document()
+ fsm_element = doc.createElement(ELEM_FSM)
doc.appendChild(fsm_element)
+
fsm_element.setAttribute("Name", fsm.name)
- fsm_element.setAttribute("StartStateName", fsm.start_state_name)
- statesDict = fsm_element.appendChild(self._create_state_dict_node(fsm._states, doc))
-
- fsm_actions_node = self._create_action_list_node(fsm.actions, doc)
- fsm_actions_node.tagName = "FSMActions"
- actionsList = fsm_element.appendChild(fsm_actions_node)
-
- file_object = open(os.path.join(path, xml_filename), "w")
- file_object.write(doc.toprettyxml())
- file_object.close()
- def _get_direct_descendants_by_tag_name(self, node, name):
+ states = cls._create_state_dict_node(fsm.get_state_dict(), doc)
+ fsm_element.appendChild(states)
+
+ file_obj.write(doc.toprettyxml())
+
+ @classmethod
+ def _get_direct_descendants_by_tag_name(cls, node, name):
"""
Searches in the list of direct descendants of a node to find all the node
that have the given name.
@@ -597,40 +645,63 @@ class XMLSerializer(Serializer):
return_list.append(childNode)
return return_list
-
-## def _load_xml_properties(self, properties_elem):
-## """
-## Changes a list of properties into fully instanciated properties.
-##
-## @param properties_elem An XML element reprensenting a list of
-## properties
-## """
-## return []
-
- def _load_xml_event_filters(self, filters_elem):
+ @classmethod
+ def _load_xml_transitions(cls, filters_elem):
"""
Loads up a list of Event Filters.
@param filters_elem An XML Element representing a list of event filters
+ @return dict of (event, next_state) transitions, keyed by name
"""
- transition_list = []
- event_filter_element_list = self._get_direct_descendants_by_tag_name(filters_elem, NODE_COMPONENT)
- new_event_filter = None
+ transition_dict = {}
+
+ #Retrieve normal transitions
+ transition_element_list = cls._get_direct_descendants_by_tag_name(filters_elem, NODE_COMPONENT)
+ new_transition = None
- for event_filter in event_filter_element_list:
- next_state = event_filter.getAttribute(NEXT_STATE_ATTR)
+ for transition in transition_element_list:
+ #start_state = transition.getAttribute(START_STATE_ATTR)
+ next_state = transition.getAttribute(NEXT_STATE_ATTR)
+ transition_name = transition.getAttribute(NAME_ATTR)
try:
- event_filter.removeAttribute(NEXT_STATE_ATTR)
+ #The attributes must be removed so that they are not
+ # viewed as a property in load_xml_component
+ # transition.removeAttribute(START_STATE_ATTR)
+ transition.removeAttribute(NEXT_STATE_ATTR)
+ transition.removeAttribute(NAME_ATTR)
except NotFoundErr:
- next_state = None
- new_event_filter = self._load_xml_component(event_filter)
+ continue
+
+ new_transition = cls._load_xml_component(transition)
- if new_event_filter is not None:
- transition_list.append((new_event_filter, next_state))
+ if new_transition is not None:
+ transition_dict[transition_name] = (new_transition, next_state)
+
+ #Retrieve automatic transitions
+ # XXX This is done differently as the AutomaticTransitionEvent
+ # cannot be loaded dynamically (yet?)
+ transition_element_list = cls._get_direct_descendants_by_tag_name(filters_elem, ELEM_AUTOTRANS)
+ new_transition = None
+
+ for transition in transition_element_list:
+ #start_state = transition.getAttribute(START_STATE_ATTR)
+ next_state = transition.getAttribute(NEXT_STATE_ATTR)
+ transition_name = transition.getAttribute(NAME_ATTR)
+ try:
+ #The attributes must be removed so that they are not
+ # viewed as a property in load_xml_component
+ # transition.removeAttribute(START_STATE_ATTR)
+ transition.removeAttribute(NEXT_STATE_ATTR)
+ transition.removeAttribute(NAME_ATTR)
+ except NotFoundErr:
+ continue
- return transition_list
-
- def _load_xml_subcomponents(self, node, properties):
+ transition_dict[transition_name] = (AutomaticTransitionEvent(), next_state)
+
+ return transition_dict
+
+ @classmethod
+ def _load_xml_subcomponents(cls, node, properties):
"""
Loads all the subcomponent node below the given node and inserts them with
the right property name inside the properties dictionnary.
@@ -640,15 +711,16 @@ class XMLSerializer(Serializer):
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)
+ subCompList = cls._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENT)
for subComp in subCompList:
property_name = subComp.getAttribute("name")
- internal_comp_node = self._get_direct_descendants_by_tag_name(subComp, NODE_COMPONENT)[0]
- internal_comp = self._load_xml_component(internal_comp_node)
+ internal_comp_node = cls._get_direct_descendants_by_tag_name(subComp, NODE_COMPONENT)[0]
+ internal_comp = cls._load_xml_component(internal_comp_node)
properties[str(property_name)] = internal_comp
- def _load_xml_subcomponent_lists(self, node, properties):
+ @classmethod
+ def _load_xml_subcomponent_lists(cls, node, properties):
"""
Loads all the subcomponent lists below the given node and stores them
under the correct property name for that node.
@@ -657,16 +729,17 @@ class XMLSerializer(Serializer):
@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)
+ listOf_subCompListNode = cls._get_direct_descendants_by_tag_name(node, NODE_SUBCOMPONENTLIST)
for subCompListNode in listOf_subCompListNode:
property_name = subCompListNode.getAttribute("name")
subCompList = []
- for subCompNode in self._get_direct_descendants_by_tag_name(subCompListNode, NODE_COMPONENT):
- subComp = self._load_xml_component(subCompNode)
+ for subCompNode in cls._get_direct_descendants_by_tag_name(subCompListNode, NODE_COMPONENT):
+ subComp = cls._load_xml_component(subCompNode)
subCompList.append(subComp)
properties[str(property_name)] = subCompList
- def _load_xml_component(self, node):
+ @classmethod
+ def _load_xml_component(cls, node):
"""
Loads a single addon component instance from an Xml node.
@@ -685,8 +758,8 @@ class XMLSerializer(Serializer):
properties[str(prop)] = eval(node.getAttribute(prop))
# Read the complex attributes
- self._load_xml_subcomponents(node, properties)
- self._load_xml_subcomponent_lists(node, properties)
+ cls._load_xml_subcomponents(node, properties)
+ cls._load_xml_subcomponent_lists(node, properties)
new_action = addon.create(class_name, **properties)
@@ -694,99 +767,88 @@ class XMLSerializer(Serializer):
return None
return new_action
-
- def _load_xml_actions(self, actions_elem):
+
+ @classmethod
+ def _load_xml_actions(cls, actions_elem):
"""
- Transforms an Actions element into a list of instanciated Action.
+ Transforms an Actions element into a dict of instanciated Action.
@param actions_elem An XML Element representing a list of Actions
+ @return dictionary of actions keyed by name
"""
- reformed_actions_list = []
- actions_element_list = self._get_direct_descendants_by_tag_name(actions_elem, NODE_COMPONENT)
+ action_dict = {}
+ actions_element_list = cls._get_direct_descendants_by_tag_name(actions_elem, NODE_COMPONENT)
for action in actions_element_list:
- new_action = self._load_xml_component(action)
+ action_name = action.getAttribute(NAME_ATTR)
+ try:
+ #The name attribute must be removed so that it is not
+ # viewed as a property in load_xml_component
+ action.removeAttribute(NAME_ATTR)
+ except NotFoundErr:
+ continue
+ new_action = cls._load_xml_component(action)
- reformed_actions_list.append(new_action)
+ action_dict[action_name] = new_action
- return reformed_actions_list
-
- def _load_xml_states(self, states_elem):
+ return action_dict
+
+ @classmethod
+ def _load_xml_states(cls, states_elem):
"""
Takes in a States element and fleshes out a complete list of State
objects.
@param states_elem An XML Element that represents a list of States
+ @return dictionary of States
"""
- reformed_state_list = []
+ state_dict = {}
# item(0) because there is always only one <States> tag in the xml file
# so states_elem should always contain only one element
- states_element_list = states_elem.item(0).getElementsByTagName("State")
+ states_element_list = states_elem.item(0).getElementsByTagName(ELEM_STATE)
for state in states_element_list:
stateName = state.getAttribute("Name")
# Using item 0 in the list because there is always only one
# Actions and EventFilterList element per State node.
- actions_list = self._load_xml_actions(state.getElementsByTagName("Actions")[0])
- event_filters_list = self._load_xml_event_filters(state.getElementsByTagName("EventFiltersList")[0])
- reformed_state_list.append(State(stateName, actions_list, event_filters_list))
+ actions_list = cls._load_xml_actions(state.getElementsByTagName(ELEM_ACTIONS)[0])
+ transitions_list = cls._load_xml_transitions(state.getElementsByTagName(ELEM_TRANS)[0])
+
+ state_dict[stateName] = State(stateName, actions_list, transitions_list)
- return reformed_state_list
+ return state_dict
- def load_xml_fsm(self, fsm_elem):
+ @classmethod
+ def load_xml_tutorial(cls, fsm_elem):
"""
Takes in an XML element representing an FSM and returns the fully
crafted FSM.
@param fsm_elem The XML element that describes a FSM
+ @return Tutorial loaded from xml element
"""
# Load the FSM's name and start state's name
fsm_name = fsm_elem.getAttribute("Name")
- fsm_start_state_name = None
- try:
- fsm_start_state_name = fsm_elem.getAttribute("StartStateName")
- except:
- pass
-
- fsm = FiniteStateMachine(fsm_name, start_state_name=fsm_start_state_name)
-
# Load the states
- states = self._load_xml_states(fsm_elem.getElementsByTagName("States"))
- for state in states:
- fsm.add_state(state)
-
- # Load the actions on this FSM
- actions = self._load_xml_actions(fsm_elem.getElementsByTagName("FSMActions")[0])
- for action in actions:
- fsm.add_action(action)
-
- # Load the event filters
- events = self._load_xml_event_filters(fsm_elem.getElementsByTagName("EventFiltersList")[0])
- for event, next_state in events:
- fsm.add_event_filter(event, next_state)
-
- return fsm
+ states_dict = cls._load_xml_states(fsm_elem.getElementsByTagName(ELEM_STATES))
+ fsm = Tutorial(fsm_name, states_dict)
-
- def load_fsm(self, guid, path=None):
+ return fsm
+
+ @classmethod
+ def load_tutorial(cls, tutorial_file):
"""
- Load fsm from xml file whose .ini file guid match argument guid.
+ Load fsm from xml file
+ @param tutorial_file file-like object to read the fsm from
+ @return Tutorial object that was loaded from the file
"""
- # Fetch the directory (if any)
- bundler = TutorialBundler(guid)
- tutorial_dir = bundler.get_tutorial_path(guid)
-
- # Open the XML file
- tutorial_file = os.path.join(tutorial_dir, TUTORIAL_FILENAME)
-
xml_dom = xml.dom.minidom.parse(tutorial_file)
- fsm_elem = xml_dom.getElementsByTagName("FSM")[0]
+ fsm_elem = xml_dom.getElementsByTagName(ELEM_FSM)[0]
- return self.load_xml_fsm(fsm_elem)
-
-
+ return cls.load_xml_tutorial(fsm_elem)
+
class TutorialBundler(object):
"""
This class provide the various data handling methods useable by the tutorial
@@ -919,11 +981,11 @@ class TutorialBundler(object):
path = os.path.join(self.Path, "meta.ini")
config.read(path)
xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY)
- serializer.save_fsm(fsm, xml_filename, self.Path)
+ serializer.save_tutorial(fsm, xml_filename, self.Path)
@staticmethod
def add_resources(typename, file):
"""
- Add ressources to metadata.
+ Add resources to metadata.
"""
raise NotImplementedError("add_resources not implemented")