capocchi/DEVSimPy

View on GitHub
XMLModule.py

Summary

Maintainability
F
3 wks
Test Coverage
# -*- coding: utf-8 -*-

"""
Name: XML.py
Brief descritpion: All classes and functions linked with xml aspects
Author(s): L. Capocchi <capocchi@univ-corse.fr>, J.F. Santucci <santucci@univ-corse.fr> 
Version:  1.0
Last modified: 2018.08.02
GENERAL NOTES AND REMARKS: XMLToDict function must integrate the coupling info into D in order to be independant of the XML.

GLOBAL VARIABLES AND FUNCTIONS:
"""

import os
import sys
import re
import tempfile

import gettext
_ = gettext.gettext

import xml.etree.ElementTree as ET
from xml.dom import minidom

if __name__ == '__main__':
    import builtins
    import sys
    ABS_HOME_PATH = os.path.abspath(os.path.dirname(sys.argv[0]))
    builtins.__dict__['GUI_FLAG'] = True
    builtins.__dict__['DEVS_DIR_PATH_DICT'] = {'PyDEVS':os.path.join(ABS_HOME_PATH,'DEVSKernel','PyDEVS'),
                                    'PyPDEVS_221':os.path.join(ABS_HOME_PATH,'DEVSKernel','PyPDEVS','pypdevs221' ,'src'),
                                    'PyPDEVS':os.path.join(ABS_HOME_PATH,'DEVSKernel','PyPDEVS','old')}
    builtins.__dict__['DEFAULT_DEVS_DIRNAME'] = 'PyDEVS'
    builtins.__dict__['HOME_PATH'] = ABS_HOME_PATH

import Container
import Components

def makeDEVSXML(label, D, filename):
    """ Make XML file from D graph of the diagram
    """

    # build a tree structure
    root = ET.Element("CoupledModel", {'xmlns:xsi':"http://www.w3.org/2001/XMLSchema-instance", 'xmlns:xsd':"http://www.w3.org/2001/XMLSchema"})

    nom = ET.SubElement(root, "nom")
    nom.text = label

    # all of root (CoupledModel) children
    inp = ET.SubElement(root, "portsIn")
    outp = ET.SubElement(root, "portsOut")
    ioc = ET.SubElement(root, "inputCoupling")
    eoc = ET.SubElement(root, "outputCoupling")
    ic = ET.SubElement(root, "internalCoupling")
    composants = ET.SubElement(root, "composants")

    # for all models composing coupled model
    for component in D:
        # if iPort
        if isinstance(component, Container.iPort):
            # nb iport
            n = ET.SubElement(inp, "nom")
            s = ET.SubElement(n, "string")
            s.text = component.label
            # IC
            for d in D[component]:
                for comp in d:
                    c = ET.SubElement(ioc, "coupling")
                    s = ET.SubElement(c, "source")
                    s.text = label
                    ps = ET.SubElement(c, "portSource")
                    ps.text = str(d[comp][0])
                    dest = ET.SubElement(c, "destination")
                    dest.text = comp.label
                    pd = ET.SubElement(c, "portDestination")
                    pd.text = str(d[comp][1])

        elif isinstance(component, Container.oPort):
            n = ET.SubElement(outp, "nom")
            s = ET.SubElement(n, "string")
            s.text = component.label
            # IC
            for d in D[component]:
                for comp in d:
                    c = ET.SubElement(eoc, "coupling")
                    s = ET.SubElement(c, "source")
                    s.text = comp.label
                    ps = ET.SubElement(c, "portSource")
                    ps.text = str(d[comp][1])
                    dest = ET.SubElement(c, "destination")
                    dest.text = label
                    pd = ET.SubElement(c, "portDestination")
                    pd.text = str(d[comp][0])
        else:
            # IC
            for d in D[component]:
                for comp in [c for c in d if isinstance(c, Container.Block)]:
                    c = ET.SubElement(ic, "coupling")
                    s = ET.SubElement(c, "source")
                    s.text = component.label
                    ps = ET.SubElement(c, "portSource")
                    ps.text = str(d[comp][0])
                    dest = ET.SubElement(c, "destination")
                    dest.text = comp.label
                    pd = ET.SubElement(c, "portDestination")
                    pd.text = str(d[comp][1])
            #D
            class_name = os.path.splitext(os.path.basename(component.python_path))[0]
            model = ET.SubElement(composants, "Model", {'xsi:type':class_name})
            n = ET.SubElement(model, "nom")
            n.text = component.label
            pi = ET.SubElement(model, "portsIn")
            n = ET.SubElement(pi, "nom")
            for i in range(component.input):
                s = ET.SubElement(n, "string")
                # label port dont exist ! replace by the port id
                s.text = str(i)
            po = ET.SubElement(model, "portsOut")
            n = ET.SubElement(po, "nom")
            for i in range(component.output):
                s = ET.SubElement(n, "string")
                s.text = str(i)

    # create a new XML file with the results
    file = open(filename, "w")
    file.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n")
    file.write(ET.tostring(root).decode("utf-8"))
    file.close()

def getDiagramFromXML(xml_file="", name="", canvas=None, D={}):
    """
    """

    from . import WizardGUI

    xmldoc = minidom.parse(xml_file)

    ### all item [2:] for id 0 and 1
    itemlist = xmldoc.getElementsByTagName('mxCell')[2:]

    ### item corresponding to the block
    blocklist = []
    ### item corresponding to the connection
    connectionlist = []
    for s in itemlist:
        if 'source' in s.attributes and 'target' in s.attributes:
            connectionlist.append(s)
        else:
            blocklist.append(s)

    #mxGraphModel = xmldoc.getElementsByTagName('mxGraphModel')[0]
    #dx = int(mxGraphModel.attributes['dx'].value)
    #dy = int(mxGraphModel.attributes['dy'].value)

    ### parent of all block is canvas
    D['1'] = canvas

    ### make block (atomic or coupled model)
    while(blocklist!=[]):

        s = blocklist[0]

        name = s.attributes['value'].value
        if 'style' in s.attributes:

            ### coupled model have swimlane style or *couple{d}* in value filed
            if s.attributes['style'].value == 'swimlane' or re.match('[a-zA-Z0-9_ ]*[c|C]oupl[ed|e|é][a-zA-Z0-9_ ]*',name, re.IGNORECASE):
                attr = s.getElementsByTagName('mxGeometry')[0].attributes
                temp = tempfile.NamedTemporaryFile(suffix='.py')
                temp.write(WizardGUI.coupledCode('CoupledModel'))
                temp.seek(0)

                block = Components.BlockFactory.CreateBlock(x=int(attr['x'].value), y=int(attr['y'].value), name=name, python_file=temp.name, canvas=canvas)
                block.label = name

                parent_id = s.attributes['parent'].value
                id = str(s.attributes['id'].value)
                if parent_id == '1':
                    canvas.AddShape(block)
                    D[id] = block
                    del blocklist[0]

                elif parent_id in list(D.keys()):
                    canvas_parent = D[parent_id]
                    canvas_parent.AddShape(block)
                    D[id] = block
                    del blocklist[0]

                else:
                    blocklist.insert(len(blocklist),blocklist.pop(0))

            elif re.match('[a-zA-Z0-9_ ]*[a|A]tomi[c|que][a-zA-Z0-9_ ]*',name, re.IGNORECASE):
                attr = s.getElementsByTagName('mxGeometry')[0].attributes
                temp = tempfile.NamedTemporaryFile(suffix='.py')
                temp.write(WizardGUI.atomicCode('AtomicModel'))
                temp.seek(0)

                block = Components.BlockFactory.CreateBlock(x=int(attr['x'].value), y=int(attr['y'].value), name=name, python_file=temp.name, canvas=canvas)
                block.label = name

                parent_id = s.attributes['parent'].value
                id = str(s.attributes['id'].value)

                if parent_id == '1':
                    canvas.AddShape(block)
                    D[id] = block
                    del blocklist[0]

                elif parent_id in list(D.keys()):
                    canvas_parent = D[parent_id]
                    canvas_parent.AddShape(block)
                    D[id] = block
                    del blocklist[0]

                else:
                    blocklist.insert(len(blocklist),blocklist.pop(0))

        elif 'vertex' in s.attributes:
            if s.attributes['vertex'].value == '1' or re.match('[a-zA-Z0-9_ ]*[a|A]tomi[c|que][a-zA-Z0-9_ ]*',name, re.IGNORECASE):
                attr = s.getElementsByTagName('mxGeometry')[0].attributes
                temp = tempfile.NamedTemporaryFile(suffix='.py')
                temp.write(WizardGUI.atomicCode('AtomicModel'))
                temp.seek(0)

                block = Components.BlockFactory.CreateBlock(x=int(attr['x'].value), y=int(attr['y'].value), name=name, python_file=temp.name, canvas=canvas)
                block.label = name

                parent_id = s.attributes['parent'].value
                id = str(s.attributes['id'].value)
                if parent_id == '1':
                    canvas.AddShape(block)
                    D[id] = block
                    del blocklist[0]

                elif parent_id in list(D.keys()):
                    canvas_parent = D[parent_id]
                    canvas_parent.AddShape(block)
                    D[id] = block
                    del blocklist[0]

                else:
                    blocklist.insert(len(blocklist),blocklist.pop(0))
        else:
            sys.stdout.write(_('Element not considered!\n'))

    ### make connection
    while(connectionlist):
        s = connectionlist[0]

        source_id = s.attributes['target'].value
        target_id = s.attributes['source'].value
        parent_id = s.attributes['parent'].value
        #style = s.attributes['style'].value.split(';')

        source = D[source_id]
        target = D[target_id]
        c = D[parent_id]

        if source in canvas.diagram.shapes and target in canvas.diagram.shapes:
            a,b = canvas.GetNodeLists(source, target)
            if a == [] or b == []:
                a,b = canvas.GetNodeLists(target,source)
            canvas.sourceNodeList, canvas.targetNodeList = a,b
            
            if canvas.sourceNodeList and canvas.targetNodeList:
                canvas.makeConnectionShape(canvas.sourceNodeList[0], canvas.targetNodeList[0])

        del connectionlist[0]

def getDiagramFromXMLSES(xmlses_file="", canvas=None):
    """
    """

    def GetParent(node):
        for s in blocklist:
            if node.attributes['parentuid'].value == s.attributes['uid'].value:
                return s
        return False

    def GetChild(node):
        for s in blocklist:
            if s.attributes['parentuid'].value == node.attributes['uid'].value:
                return s
        return node    

    def HasChild(node):
        for s in blocklist:
            if s.attributes['parentuid'].value == node.attributes['uid'].value:
                return True
        return False

    def GetNodeFromUID(uid):
        ''' Return node form uid
        '''
        for b in blocklist:
            if b.attributes['uid'].value == uid:
                return b
    
    def InsertElemFromUID(elem,uid,D):
        ''' Update and return the new D with new elem into the components of the uid coupled model
            elem: new element
            uid: uid of the coupled model 
            D: the dictionary to udpade
        '''
        if D != {}:
            for k,v in list(D.items()):
                if v['uid'] == uid:
                    D[k]['components'].append(elem)
                    return D
                else:
                    for d in [a for a in v['components'] if isinstance(a,dict)]:    
                        InsertElemFromUID(elem,uid,{d['node']:d})

    def GetDiagram(canvas, D, parent_block=None):
        ''' Build the DEVSimpy diagram with the creation of the block models (atomic and coupled) and ports (input and output)
        '''
        if D != {}:
            for k,v in list(D.items()):
                if 'components' in v:
                    ### coupled model
                    name = k.attributes['name'].value
                    temp = tempfile.NamedTemporaryFile(suffix='.py', delete=False)
                    temp.write(WizardGUI.coupledCode('CoupledModel'))
                    temp.seek(0)

                    nbi,nbo = [len(a) for a in GetNbPort(k)]
                    cp_block = Components.BlockFactory.CreateBlock(x=100, y=100, inputs = nbi, outputs = nbo, name=name, python_file=temp.name, canvas=canvas)
                    cp_block.label = name
                    ### if True, the flag for bad python file is activated
                    #cp_block.bad_filename_path_flag = True
                    
                    parent_block.AddShape(cp_block)
                    
                    #print cp_block

                    for d in v['components']:
                        if isinstance(d, dict):
                            GetDiagram(canvas,{d['node']:d}, cp_block)
                        else:
                            GetDiagram(canvas,{d:{}}, cp_block)

                    return parent_block
                else:
                    ### atomic model
                    name = k.attributes['name'].value
                    temp = tempfile.NamedTemporaryFile(suffix='.py', delete=False)
                    temp.write(WizardGUI.atomicCode('AtomicModel'))
                    temp.seek(0)
                    
                    nbi,nbo = [len(a) for a in GetNbPort(k)]
                    am_block = Components.BlockFactory.CreateBlock(x=250, y=100*(1+parent_block.nbCodeBlock), inputs = nbi, outputs = nbo, name=name, python_file=temp.name, canvas=canvas)
                    am_block.label = name

                    ### if True, the flag for bad python file is activated
                    #cp_block.bad_filename_path_flag = True

                    #print am_block
                    parent_block.AddShape(am_block)

                    return True

    def GetNbPort(node):
        ''' Get a tuple that contain a list of input an output ports
        '''
        iport = []
        oport = []
        name = node.attributes['name'].value

        if not HasChild(node):
            connectionlist = xmldoc.getElementsByTagName('coupling')
        else:
            connectionlist = GetChild(node).getElementsByTagName('coupling')

        for c in connectionlist:
            if name == c.attributes['sinkname'].value:
                p = u''.join([i for i in c.attributes['sinkport'].value if i.isdigit()])
                if p ==u'': p=u'1'
                if HasChild(node):
                    if p not in oport:
                        oport.append(p)
                else:
                    if p not in iport:
                        iport.append(p)    
            elif name == c.attributes['sourcename'].value:
                p = u''.join([i for i in c.attributes['sourceport'].value if i.isdigit()])
                if p ==u'': p=u'1'
                if HasChild(node):
                    if p not in iport:
                        iport.append(p)
                else:
                    if p not in oport:
                        oport.append(p)

        ### if numer od ports processing faild with name, try with uid (node correspond by uid often for multiaspect)
        if iport==oport==[]:
            uid = node.attributes['uid'].value
            for c in connectionlist:
                if uid == c.attributes['sinkuid'].value:
                    p = u''.join([i for i in c.attributes['sinkport'].value if i.isdigit()])
                    if p ==u'': p=u'1'
                    if HasChild(node):
                        if p not in oport:
                            oport.append(p)
                    else:
                        if p not in iport:
                            iport.append(p)    
                elif uid == c.attributes['sourceuid'].value:
                    p = u''.join([i for i in c.attributes['sourceport'].value if i.isdigit()])
                    if p ==u'': p=u'1'
                    if HasChild(node):
                        if p not in iport:
                            iport.append(p)
                    else:
                        if p not in oport:
                            oport.append(p)

        #print name, iport, oport
        
        return (iport, oport)
    
    def GetNodeLists(canvas, source, target):
        """
        """

        # deselect and select target in order to get its list of node (because the node are generated dynamicly)
        canvas.deselect()
        canvas.select(target)
        canvas.select(source)

        nodesList = [n for n in canvas.nodes if not isinstance(n, Container.ResizeableNode)]

        # list of node list for
        sourceNodeList = [n for n in nodesList if n.item == source and isinstance(n, Container.ONode)]
        targetNodeList = [n for n in nodesList if n.item == target and isinstance(n, Container.INode)]

        canvas.deselect()

        return (sourceNodeList, targetNodeList)

    def GetDiagramCoupling(canvas):
        ''' Build the DEVSimpy diagram coupling 
        '''
        ### make connection
        connectionlist = xmldoc.getElementsByTagName('coupling')

        ### all blocks
        blocks = canvas.diagram.GetFlatBlockShapeList()

        ### make connection
        while(connectionlist):
            
            ### take the first connection object (deleted at the end of while)
            s = connectionlist[0]

            ### Get the names of blocks
            source_name = GetNodeFromUID(s.attributes['sourceuid'].value).attributes['name'].value
            target_name = GetNodeFromUID(s.attributes['sinkuid'].value).attributes['name'].value
            diagram_name = s.parentNode.attributes['name'].value

            ### Get the port id removing caracter form str if port has specified with label
            source_port_num = u''.join([i for i in s.attributes['sourceport'].value if i.isdigit()])
            target_port_num = u''.join([i for i in s.attributes['sinkport'].value if i.isdigit()])

            ### if no port number is specified in the XML, there is one port and its id is 1 (not 0 but it can be depending on the rule chose by the SES modeler!) 
            if source_port_num ==u'': source_port_num=u'1'
            if target_port_num ==u'': target_port_num=u'1'

            ### find the graphic block of the source, target and diagram
            source = target = diagram = None
            for b in blocks:
                if b.label == source_name:
                    source = b
                if b.label == target_name:
                    target = b
                if b.label == diagram_name:
                    diagram = b

            ### if source or target is the diagram, we change them with the corresponding iPort or oPort 
            if diagram == source:
                for s in [a for a in source.GetShapeList() if isinstance(a,Container.iPort)]:
                    if int(s.id) == int(source_port_num)-1:
                        source = s
                        break

            if diagram == target:
                for t in [a for a in target.GetShapeList() if isinstance(a,Container.oPort)]:
                    if int(t.id) == int(target_port_num)-1:
                        target = t
                        break

            #print "------------------------------------------------"
            #print source, target
            
            a,b = GetNodeLists(canvas, source, target)

            #print a,b

            if len(a) == 1: source_port_num = u'1'
            if len(b) == 1: target_port_num = u'1'
            #print source_name,int(source_port_num)-1,target_name,int(target_port_num)-1
            sourceNode, targetNode = a[int(source_port_num)-1],b[int(target_port_num)-1]
    
            ### item of node must be overwritted
            sourceNode.item = source
            targetNode.item = target

            ### add the connexion to the diagram
            ci = Container.ConnectionShape()
            ci.setInput(sourceNode.item, sourceNode.index)
            ci.x[0], ci.y[0] = sourceNode.item.getPortXY('output', sourceNode.index)
            ci.x[1], ci.y[1] = targetNode.item.getPortXY('input', targetNode.index)
            ci.setOutput(targetNode.item, targetNode.index)
            diagram.shapes.insert(0, ci)

            #print "------------------------------------------------"

            ### delete the first selected cinnection objet
            del connectionlist[0]
        
        return canvas.GetDiagram()

    def XMLToDict(blocklist):        
        ### dictionary building
        D = {}
        ### Add high level coupled models
        for cm in [a for a in blocklist if a.attributes['parentuid'].value == '1' and a.attributes['type'].value == "Aspect Node"]:
            ### change the name with parent (comparing uid and parentuid)
            cm.attributes['name'].value = GetParent(cm).attributes['name'].value
            name = cm.attributes['name'].value
            uid = cm.attributes['uid'].value
            D[cm] = {'node':cm, 'uid':uid, 'name':name, 'components':[]}

        ### Add other sub coupled models
        for uid in range(2,100):
            for sub_cm in [a for a in blocklist if str(a.attributes['parentuid'].value) == str(uid) and a.attributes['type'].value == "Aspect Node"]:
                uid = sub_cm.attributes['uid'].value
                name = sub_cm.attributes['name'].value
                sub_uid =  GetParent(sub_cm).attributes['parentuid'].value
                sub_cm.attributes['name'].value = GetParent(sub_cm).attributes['name'].value

                InsertElemFromUID({'node':GetParent(sub_cm),'uid':uid,'name':name,'components':[]}, sub_uid,D)

        ### Add atomic models
        for am in [a for a in blocklist if a.attributes['type'].value == "Entity Node" and GetChild(a).attributes['type'].value != "Aspect Node"]:
            am_parent_uid = am.attributes['parentuid'].value
            InsertElemFromUID(am,am_parent_uid,D)

        return D

    from . import WizardGUI

    try:
        xmldoc = minidom.parse(xmlses_file)
    except Exception as info:
        sys.stdout.write('Error importing %s: %s\n' % (xmlses_file, info))
        sys.stdout.write('Please check the XML SES file\n')
        return False

    ### blocklist contains all the treenode xml nodes
    global blocklist
    blocklist = xmldoc.getElementsByTagName('treenode')

    D = XMLToDict(blocklist)
    #import pprint 
    #pprint.pprint(D)
    
    if D != {}:
        try:
            ### Make the DEVSimPy diagram
            dia = GetDiagram(canvas, D, parent_block=canvas)
        except Exception as info:
            sys.stdout.write(_('Error making the diagram from XML SES: %s\n')%info)
            return False
        else:
            try:
                ### Make the DEVSimPy diagram coupling
                dia = GetDiagramCoupling(canvas)
            except Exception as info:
                sys.stdout.write(_('Error making the coupling into the diagram from XML SES: %s\n')%info)
                return dia
            else:
                return dia
            return dia
    else:
        sys.stdout.write(_("XML SES file seems don't contain models!\n"))
        return False

if __name__ == '__main__':
    import wx

    ### ------------------------------------------------------------
    class TestApp(wx.App):
        """ Testing application
        """

        def OnInit(self):

            from . import DetachedFrame
            import builtins
            import gettext
            from .DomainInterface.DomainStructure import DomainStructure
            from .DomainInterface.DomainBehavior import DomainBehavior

            builtins.__dict__['ICON_PATH']='icons'
            builtins.__dict__['ICON_PATH_16_16']=os.path.join(ICON_PATH,'16x16')
            builtins.__dict__['NB_HISTORY_UNDO']= 5
            builtins.__dict__['DOMAIN_PATH']='Domain'
            
            builtins.__dict__['FONT_SIZE']=12
            builtins.__dict__['_'] = gettext.gettext
            builtins.__dict__['LOCAL_EDITOR'] = False

            diagram = Container.Diagram()
            
            self.frame = DetachedFrame.DetachedFrame(None, -1, "Test", diagram)
            newPage = Container.ShapeCanvas(self.frame, wx.NewIdRef(), name='Test')
            newPage.SetDiagram(diagram)

            path = os.path.join(os.path.expanduser("~"),'Downloads','Watershed.xml')
            #path = os.path.join(os.path.expanduser("~"),'Downloads','example.xmlsestree')
            getDiagramFromXMLSES(path, canvas=newPage)
            #diagram.SetParent(newPage)

            self.frame.Show()

            return True

        def OnQuit(self, event):
            self.Close()

    app = TestApp(0)
    app.MainLoop()